Skip to content

Commit 6e67f2c

Browse files
committed
fix(flow): preserve pcall narrowing for normalized alias payloads
Fix a `pcall()` success-guard regression where payload types coming from multiline aliases could lose their concrete type after normalization. The underlying issue was that several lookup paths assumed a normalized multiline alias would still be `LuaType::Union`. Recurse on the normalized result instead, and preserve `never` when all branches are `never`.
1 parent 827e0b3 commit 6e67f2c

7 files changed

Lines changed: 68 additions & 18 deletions

File tree

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1454,6 +1454,26 @@ end
14541454
assert_eq!(b_desc, "table");
14551455
}
14561456

1457+
#[test]
1458+
fn test_multiline_alias_never_member_preserves_string_call() {
1459+
let mut ws = VirtualWorkspace::new_with_init_std_lib();
1460+
1461+
ws.def(
1462+
r#"
1463+
---@alias OnlyString
1464+
---| string
1465+
---| never
1466+
1467+
---@type OnlyString
1468+
local value
1469+
1470+
result = value:sub(1)
1471+
"#,
1472+
);
1473+
1474+
assert_eq!(ws.expr_ty("result"), ws.ty("string"));
1475+
}
1476+
14571477
#[test]
14581478
fn test_issue_364() {
14591479
let mut ws = VirtualWorkspace::new();

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,4 +175,37 @@ mod test {
175175
);
176176
assert_eq!(ws.expr_ty("narrowed"), ws.ty("integer"));
177177
}
178+
179+
#[test]
180+
fn test_pcall_same_payload_type_stays_string_under_success_guard() {
181+
let mut ws = VirtualWorkspace::new_with_init_std_lib();
182+
183+
ws.def(
184+
r#"
185+
---@param value any
186+
---@return string
187+
local function inspect(value)
188+
return tostring(value)
189+
end
190+
191+
---@param err any
192+
---@return string
193+
local function format_error_value(err)
194+
if type(err) == 'string' then
195+
return err
196+
end
197+
198+
local ok, inspected = pcall(inspect, err)
199+
if ok then
200+
narrowed = inspected
201+
return inspected
202+
end
203+
204+
return tostring(err)
205+
end
206+
"#,
207+
);
208+
209+
assert_eq!(ws.expr_ty("narrowed"), ws.ty("string"));
210+
}
178211
}

crates/emmylua_code_analysis/src/db_index/type/types.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -528,24 +528,30 @@ impl LuaType {
528528
_ => {
529529
let mut result_types = Vec::new();
530530
let mut hash_set = HashSet::new();
531+
let mut saw_never = false;
531532
for typ in types {
532533
match typ {
533534
LuaType::Union(u) => {
534535
for t in u.into_vec() {
535-
if hash_set.insert(t.clone()) {
536+
if t.is_never() {
537+
saw_never = true;
538+
} else if hash_set.insert(t.clone()) {
536539
result_types.push(t);
537540
}
538541
}
539542
}
540543
_ => {
541-
if hash_set.insert(typ.clone()) {
544+
if typ.is_never() {
545+
saw_never = true;
546+
} else if hash_set.insert(typ.clone()) {
542547
result_types.push(typ);
543548
}
544549
}
545550
}
546551
}
547552

548553
match result_types.len() {
554+
0 if saw_never => LuaType::Never,
549555
0 => LuaType::Nil,
550556
1 => result_types[0].clone(),
551557
_ => LuaType::Union(LuaUnionType::from_vec(result_types).into()),
@@ -1576,7 +1582,7 @@ impl LuaMultiLineUnion {
15761582
types.push(t.clone());
15771583
}
15781584

1579-
LuaType::Union(Arc::new(LuaUnionType::from_vec(types)))
1585+
LuaType::from_vec(types)
15801586
}
15811587

15821588
pub fn contain_tpl(&self) -> bool {

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

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -176,11 +176,7 @@ pub fn infer_member_by_member_key(
176176
}
177177
LuaType::MultiLineUnion(multi_union) => {
178178
let union_type = multi_union.to_union();
179-
if let LuaType::Union(union_type) = union_type {
180-
infer_union_member(db, cache, &union_type, index_expr, infer_guard)
181-
} else {
182-
Err(InferFailReason::FieldNotFound)
183-
}
179+
infer_member_by_member_key(db, cache, &union_type, index_expr, infer_guard)
184180
}
185181
LuaType::Intersection(intersection_type) => {
186182
infer_intersection_member(db, cache, intersection_type, index_expr, infer_guard)

crates/emmylua_code_analysis/src/semantic/infer/narrow/condition_flow/call_flow.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -142,10 +142,9 @@ pub fn get_type_at_call_expr(
142142
fn needs_antecedent_same_var_colon_lookup(member_type: &LuaType) -> bool {
143143
let candidate_members = match member_type {
144144
LuaType::Union(union_type) => union_type.into_vec(),
145-
LuaType::MultiLineUnion(multi_union) => match multi_union.to_union() {
146-
LuaType::Union(union_type) => union_type.into_vec(),
147-
_ => return false,
148-
},
145+
LuaType::MultiLineUnion(multi_union) => {
146+
return needs_antecedent_same_var_colon_lookup(&multi_union.to_union());
147+
}
149148
_ => return false,
150149
};
151150

crates/emmylua_code_analysis/src/semantic/member/find_members.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -121,11 +121,7 @@ fn find_members_guard(
121121
LuaType::Union(union_type) => find_union_members(db, union_type, ctx, filter),
122122
LuaType::MultiLineUnion(multi_union) => {
123123
let union_type = multi_union.to_union();
124-
if let LuaType::Union(union_type) = union_type {
125-
find_union_members(db, &union_type, ctx, filter)
126-
} else {
127-
None
128-
}
124+
find_members_guard(db, &union_type, ctx, filter)
129125
}
130126
LuaType::Intersection(intersection_type) => {
131127
find_intersection_members(db, intersection_type, ctx, filter)

crates/emmylua_code_analysis/src/semantic/type_check/complex_type/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ pub fn check_complex_type_compact(
129129
}
130130
LuaType::MultiLineUnion(multi_union) => {
131131
let union = multi_union.to_union();
132-
return check_complex_type_compact(
132+
return check_general_type_compact(
133133
context,
134134
&union,
135135
compact_type,

0 commit comments

Comments
 (0)