Skip to content

Commit 57c6731

Browse files
committed
fix: prefer main signature for tied overload matches
fix #1111
1 parent 2d88675 commit 57c6731

3 files changed

Lines changed: 147 additions & 10 deletions

File tree

crates/emmylua_code_analysis/src/semantic/infer/infer_call/mod.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -932,6 +932,33 @@ mod tests {
932932
assert_eq!(ws.expr_ty("payload"), ws.ty("string"));
933933
}
934934

935+
#[test]
936+
fn test_generic_main_signature_preferred_for_callback_arg_overload() {
937+
let mut ws = VirtualWorkspace::new();
938+
ws.def(
939+
r#"
940+
---@generic T
941+
---@param fov (fun(): T?)
942+
---@return T?
943+
---@overload fun(fov: T): T
944+
function fn_or_val(fov)
945+
end
946+
947+
---@type fun(): string?
948+
local fn
949+
950+
local foo = fn_or_val(fn)
951+
result = foo
952+
value_result = fn_or_val("bar")
953+
"#,
954+
);
955+
956+
let foo = ws.expr_ty("result");
957+
assert_eq!(ws.humanize_type(foo), "string?");
958+
let value = ws.expr_ty("value_result");
959+
assert_eq!(ws.humanize_type(value), "string");
960+
}
961+
935962
#[test]
936963
fn test_union_call_ignores_unresolved_alias_member() {
937964
let mut ws = VirtualWorkspace::new();

crates/emmylua_code_analysis/src/semantic/overload_resolve/collect_overloads.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,9 @@ fn collect_callable_overload_groups_inner(
9191
let Some(signature) = db.get_signature_index().get(sig_id) else {
9292
return Ok(());
9393
};
94-
let mut overloads = signature.overloads.to_vec();
95-
overloads.push(signature.to_doc_func_type());
94+
// 主签名描述了函数实现本身, 当它和 overload 同时可匹配时应作为同等匹配下的优先候选.
95+
let mut overloads = vec![signature.to_doc_func_type()];
96+
overloads.extend(signature.overloads.iter().cloned());
9697
groups.push(overloads);
9798
}
9899
LuaType::Instance(instance) => {

crates/emmylua_code_analysis/src/semantic/overload_resolve/resolve_signature_by_args.rs

Lines changed: 117 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::cmp::Ordering;
12
use std::sync::Arc;
23

34
use crate::{
@@ -112,14 +113,6 @@ pub fn resolve_signature_by_args(
112113
*opt_func = None;
113114
continue;
114115
}
115-
116-
if !has_declined_no_flow_arg
117-
&& match_result > ParamMatchResult::Any
118-
&& arg_index + 1 == expr_len
119-
&& param_index + 1 == func.get_params().len()
120-
{
121-
return Ok(func.clone());
122-
}
123116
}
124117

125118
if current_match_result == ParamMatchResult::Not {
@@ -149,6 +142,18 @@ pub fn resolve_signature_by_args(
149142
_ => {}
150143
}
151144

145+
if !has_declined_no_flow_arg
146+
&& let Some(func) = choose_more_specific_callable(
147+
db,
148+
&rest_need_resolve_funcs,
149+
expr_types,
150+
is_colon_call,
151+
declined_no_flow_args,
152+
)
153+
{
154+
return Ok(func);
155+
}
156+
152157
let start_param_index = expr_len;
153158
let mut max_param_len = 0;
154159
for func in rest_need_resolve_funcs.iter().flatten() {
@@ -236,6 +241,110 @@ pub fn resolve_signature_by_args(
236241
}
237242
}
238243

244+
fn choose_more_specific_callable(
245+
db: &DbIndex,
246+
funcs: &[Option<Arc<LuaFunctionType>>],
247+
expr_types: &[LuaType],
248+
is_colon_call: bool,
249+
declined_no_flow_args: &[bool],
250+
) -> Option<Arc<LuaFunctionType>> {
251+
if expr_types.is_empty()
252+
|| expr_types.iter().enumerate().all(|(i, expr_type)| {
253+
declined_no_flow_args.get(i).copied().unwrap_or(false)
254+
|| expr_type.is_any()
255+
|| expr_type.is_unknown()
256+
})
257+
{
258+
return None;
259+
}
260+
261+
let mut best: Option<Arc<LuaFunctionType>> = None;
262+
let mut has_strict_better = false;
263+
for func in funcs.iter().flatten() {
264+
let Some(best_func) = best.as_ref() else {
265+
best = Some(func.clone());
266+
continue;
267+
};
268+
269+
match compare_callable_specificity(
270+
db,
271+
func,
272+
best_func,
273+
expr_types,
274+
is_colon_call,
275+
declined_no_flow_args,
276+
) {
277+
Some(Ordering::Greater) => {
278+
best = Some(func.clone());
279+
has_strict_better = true;
280+
}
281+
Some(Ordering::Less) => {
282+
has_strict_better = true;
283+
}
284+
Some(Ordering::Equal) => {}
285+
None => return None,
286+
}
287+
}
288+
289+
if has_strict_better { best } else { None }
290+
}
291+
292+
fn compare_callable_specificity(
293+
db: &DbIndex,
294+
a: &LuaFunctionType,
295+
b: &LuaFunctionType,
296+
expr_types: &[LuaType],
297+
is_colon_call: bool,
298+
declined_no_flow_args: &[bool],
299+
) -> Option<Ordering> {
300+
let mut result = Ordering::Equal;
301+
for (arg_index, expr_type) in expr_types.iter().enumerate() {
302+
if declined_no_flow_args
303+
.get(arg_index)
304+
.copied()
305+
.unwrap_or(false)
306+
|| expr_type.is_any()
307+
|| expr_type.is_unknown()
308+
{
309+
continue;
310+
}
311+
312+
let param_index = get_call_param_index(a, arg_index, is_colon_call)?;
313+
let a_param = get_func_param_type(a, param_index)?;
314+
let b_param = get_func_param_type(b, param_index)?;
315+
let param_order = compare_param_specificity(db, &a_param, &b_param);
316+
match (result, param_order) {
317+
(Ordering::Equal, order) => result = order,
318+
(Ordering::Greater, Ordering::Less) | (Ordering::Less, Ordering::Greater) => {
319+
return None;
320+
}
321+
_ => {}
322+
}
323+
}
324+
325+
Some(result)
326+
}
327+
328+
fn compare_param_specificity(db: &DbIndex, a: &LuaType, b: &LuaType) -> Ordering {
329+
if a == b {
330+
return Ordering::Equal;
331+
}
332+
333+
match (a.is_any() || a.is_unknown(), b.is_any() || b.is_unknown()) {
334+
(true, false) => return Ordering::Less,
335+
(false, true) => return Ordering::Greater,
336+
_ => {}
337+
}
338+
339+
let a_sub_b = check_type_compact(db, b, a).is_ok();
340+
let b_sub_a = check_type_compact(db, a, b).is_ok();
341+
match (a_sub_b, b_sub_a) {
342+
(true, false) => Ordering::Greater,
343+
(false, true) => Ordering::Less,
344+
_ => Ordering::Equal,
345+
}
346+
}
347+
239348
pub(crate) fn is_func_last_param_variadic(func: &LuaFunctionType) -> bool {
240349
if let Some(last_param) = func.get_params().last() {
241350
last_param.0 == "..."

0 commit comments

Comments
 (0)