Skip to content

Commit 6e97840

Browse files
author
efve.zff
committed
fix: handle intersection type as table subtype and in undefined-field check
- Add Intersection to table compact-type match in check_general_type_compact (mod.rs) - Add Intersection/Object handling in get_base_type_id (sub_type.rs) - Add Intersection handling in is_invalid_prefix_type and is_valid_member (check_field.rs)
1 parent 910ad35 commit 6e97840

3 files changed

Lines changed: 46 additions & 1 deletion

File tree

crates/emmylua_code_analysis/src/diagnostic/checker/check_field.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,9 @@ fn is_invalid_prefix_type(typ: &LuaType) -> bool {
116116
LuaType::Instance(instance_typ) => {
117117
current_typ = instance_typ.get_base();
118118
}
119+
LuaType::Intersection(intersection) => {
120+
return intersection.get_types().iter().any(is_invalid_prefix_type);
121+
}
119122
_ => return false,
120123
}
121124
}
@@ -141,6 +144,25 @@ pub(super) fn is_valid_member(
141144
return None;
142145
}
143146
}
147+
LuaType::Intersection(intersection) => {
148+
// If any component of the intersection would pass the member check on its own,
149+
// the intersection should also pass (e.g. unknown[] & { n: integer }).
150+
for component in intersection.get_types() {
151+
if is_valid_member(semantic_model, component, index_expr, index_key, code).is_some()
152+
{
153+
return Some(());
154+
}
155+
}
156+
// Even if no single component passes the early checks, the intersection's
157+
// member lookup may still succeed (e.g. Tuple([Unknown]) & Object).
158+
// Try inferring the index expression type directly.
159+
if semantic_model
160+
.get_index_decl_type(index_expr.clone())
161+
.is_some()
162+
{
163+
return Some(());
164+
}
165+
}
144166
_ => {}
145167
}
146168

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,25 @@ fn check_general_type_compact(
8181
);
8282
}
8383

84+
// When compact_type is an Intersection, the value satisfies all components simultaneously.
85+
// So it can be assigned to any target that accepts at least one component.
86+
// This must be handled before the source-based dispatch, because individual source branches
87+
// (e.g. Array, Ref) do not know how to decompose an intersection compact_type.
88+
if let LuaType::Intersection(compact_intersection) = compact_type {
89+
// Skip if source is also Intersection — that case is handled symmetrically in
90+
// check_intersection_type_compact.
91+
if !matches!(source, LuaType::Intersection(_)) {
92+
for component in compact_intersection.get_types() {
93+
if check_general_type_compact(context, source, component, check_guard.next_level()?)
94+
.is_ok()
95+
{
96+
return Ok(());
97+
}
98+
}
99+
return Err(TypeCheckFailReason::TypeNotMatch);
100+
}
101+
}
102+
84103
match source {
85104
LuaType::Unknown | LuaType::Any => Ok(()),
86105
// simple type

crates/emmylua_code_analysis/src/semantic/type_check/sub_type.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,11 @@ pub fn get_base_type_id(typ: &LuaType) -> Option<LuaTypeDeclId> {
8888
| LuaType::TableGeneric(_)
8989
| LuaType::TableConst(_)
9090
| LuaType::Tuple(_)
91-
| LuaType::Array(_) => Some(LuaTypeDeclId::global("table")),
91+
| LuaType::Array(_)
92+
| LuaType::Object(_) => Some(LuaTypeDeclId::global("table")),
93+
LuaType::Intersection(intersection) => {
94+
intersection.get_types().iter().find_map(get_base_type_id)
95+
}
9296
LuaType::DocFunction(_) | LuaType::Function | LuaType::Signature(_) => {
9397
Some(LuaTypeDeclId::global("function"))
9498
}

0 commit comments

Comments
 (0)