Skip to content

Commit 713aa46

Browse files
author
efve.zff
committed
fix: support union type in as_doc_function_type, to correctly infer pcall return types with overloaded members
1 parent 9f97f8c commit 713aa46

2 files changed

Lines changed: 80 additions & 1 deletion

File tree

crates/emmylua_code_analysis/src/compilation/test/pcall_test.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,4 +235,59 @@ mod test {
235235
assert_eq!(ws.humanize_type(success_result), "boolean");
236236
assert_eq!(ws.humanize_type(failure_result), "string");
237237
}
238+
239+
#[test]
240+
fn test_pcall_with_overloaded_module_function() {
241+
let mut ws = VirtualWorkspace::new_with_init_std_lib();
242+
243+
// When a module function has both @field annotation and actual implementation,
244+
// pcall should correctly infer the return type from the overloaded callable.
245+
ws.def(
246+
r#"
247+
---@class ShlexModule
248+
---@field split fun(s: string): string[]
249+
local Shlex = {}
250+
251+
---@param s string
252+
---@return string[]
253+
function Shlex.split(s)
254+
return {}
255+
end
256+
257+
ok, args = pcall(Shlex.split, "hello world")
258+
"#,
259+
);
260+
261+
let ok_ty = ws.expr_ty("ok");
262+
let args_ty = ws.expr_ty("args");
263+
assert_eq!(ok_ty, ws.ty("true|false"));
264+
assert_eq!(args_ty, ws.ty("string[]|string"));
265+
}
266+
267+
#[test]
268+
fn test_pcall_with_overloaded_module_function_scalar() {
269+
let mut ws = VirtualWorkspace::new_with_init_std_lib();
270+
271+
// Same scenario but with a scalar return type (integer).
272+
ws.def(
273+
r#"
274+
---@class ModScalar
275+
---@field compute fun(s: string): integer
276+
local M = {}
277+
278+
---@param s string
279+
---@return integer
280+
function M.compute(s)
281+
return 1
282+
end
283+
284+
ok, result = pcall(M.compute, "hello")
285+
"#,
286+
);
287+
288+
let ok_ty = ws.expr_ty("ok");
289+
let result_ty = ws.expr_ty("result");
290+
assert_eq!(ok_ty, ws.ty("true|false"));
291+
assert_eq!(result_ty, ws.ty("integer|string"));
292+
}
238293
}

crates/emmylua_code_analysis/src/semantic/generic/instantiate_type/instantiate_func_generic.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use crate::{
2525
},
2626
};
2727
use crate::{
28-
LuaMemberOwner, LuaSemanticDeclId, SemanticDeclLevel, infer_node_semantic_decl,
28+
LuaMemberOwner, LuaSemanticDeclId, LuaUnionType, SemanticDeclLevel, infer_node_semantic_decl,
2929
tpl_pattern_match_args,
3030
};
3131

@@ -131,6 +131,30 @@ pub fn as_doc_function_type(
131131
.ok_or(InferFailReason::None)?
132132
.to_doc_func_type(),
133133
),
134+
LuaType::Union(union) => {
135+
match union.as_ref() {
136+
LuaUnionType::Basic(basic) => {
137+
for member in basic.iter() {
138+
if let Some(func) = as_doc_function_type(db, &member)? {
139+
return Ok(Some(func));
140+
}
141+
}
142+
}
143+
LuaUnionType::Nullable(ty) => {
144+
if let Some(func) = as_doc_function_type(db, ty)? {
145+
return Ok(Some(func));
146+
}
147+
}
148+
LuaUnionType::Multi(types) => {
149+
for member in types {
150+
if let Some(func) = as_doc_function_type(db, member)? {
151+
return Ok(Some(func));
152+
}
153+
}
154+
}
155+
}
156+
None
157+
}
134158
_ => None,
135159
})
136160
}

0 commit comments

Comments
 (0)