diff --git a/crates/emmylua_code_analysis/src/diagnostic/checker/call_non_callable.rs b/crates/emmylua_code_analysis/src/diagnostic/checker/call_non_callable.rs index a71f4da56..91153b273 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/checker/call_non_callable.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/checker/call_non_callable.rs @@ -150,6 +150,12 @@ fn has_non_callable_member(db: &DbIndex, typ: &LuaType) -> bool { LuaType::Any | LuaType::Unknown | LuaType::SelfInfer | LuaType::Global | LuaType::Nil => { false } + LuaType::TplRef(tpl) | LuaType::ConstTplRef(tpl) => tpl + .get_constraint() + .is_some_and(|constraint| has_non_callable_member(db, constraint)), + LuaType::StrTplRef(str_tpl) => str_tpl + .get_constraint() + .is_some_and(|constraint| has_non_callable_member(db, constraint)), LuaType::Union(union) => union .into_vec() .iter() @@ -174,7 +180,7 @@ fn collect_non_callable_union_types( if *real_type == LuaType::Nil { return; } - if real_type.is_function() || real_type.is_call() { + if !has_non_callable_member(db, real_type) { return; } if infer_call_expr_func( diff --git a/crates/emmylua_code_analysis/src/diagnostic/test/call_non_callable_test.rs b/crates/emmylua_code_analysis/src/diagnostic/test/call_non_callable_test.rs index 990d98262..4128e47c6 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/test/call_non_callable_test.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/test/call_non_callable_test.rs @@ -195,4 +195,22 @@ mod test { "# )); } + + #[test] + fn test_no_call_non_callable_for_generic_function_param_in_a_lua() { + let mut ws = VirtualWorkspace::new(); + assert!(ws.check_code_for( + DiagnosticCode::CallNonCallable, + r#" + --- @generic F: function + --- @param fn F + --- @return F + function once(fn) + return function(...) + return fn(...) + end + end + "#, + )); + } } diff --git a/crates/emmylua_code_analysis/src/semantic/infer/infer_call/mod.rs b/crates/emmylua_code_analysis/src/semantic/infer/infer_call/mod.rs index db5b976a2..a96f7a131 100644 --- a/crates/emmylua_code_analysis/src/semantic/infer/infer_call/mod.rs +++ b/crates/emmylua_code_analysis/src/semantic/infer/infer_call/mod.rs @@ -15,7 +15,7 @@ use crate::{ use crate::{ InferGuardRef, semantic::{ - generic::{TypeSubstitutor, instantiate_doc_function}, + generic::{TypeSubstitutor, get_tpl_ref_extend_type, instantiate_doc_function}, infer::narrow::get_type_at_call_expr_inline_cast, }, }; @@ -81,6 +81,14 @@ pub fn infer_call_expr_func( ), LuaType::Instance(inst) => infer_instance_type_doc_function(db, inst), LuaType::TableConst(meta_table) => infer_table_type_doc_function(db, meta_table.clone()), + LuaType::TplRef(_) | LuaType::ConstTplRef(_) | LuaType::StrTplRef(_) => infer_tpl_ref_call( + db, + cache, + call_expr.clone(), + &call_expr_type, + infer_guard, + args_count, + ), LuaType::Union(union) => { // 此时我们将其视为泛型实例化联合体 if union @@ -143,6 +151,23 @@ pub fn infer_call_expr_func( result } +fn infer_tpl_ref_call( + db: &DbIndex, + cache: &mut LuaInferCache, + call_expr: LuaCallExpr, + call_expr_type: &LuaType, + infer_guard: &InferGuardRef, + args_count: Option, +) -> InferCallFuncResult { + let prefix_expr = call_expr.get_prefix_expr().ok_or(InferFailReason::None)?; + let extend_type = get_tpl_ref_extend_type(db, cache, call_expr_type, prefix_expr, 0) + .ok_or(InferFailReason::None)?; + if &extend_type == call_expr_type { + return Err(InferFailReason::None); + } + infer_call_expr_func(db, cache, call_expr, extend_type, infer_guard, args_count) +} + fn infer_doc_function( db: &DbIndex, cache: &mut LuaInferCache,