Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,19 @@ pub fn check_generic_type_compact(
check_guard.next_level()?,
)
}
LuaType::Union(union_type) => {
// When compact_type is a Union, every member of the union must be
// assignable to the source generic type for the assignment to be safe.
for member_type in union_type.into_vec() {
check_generic_type_compact(
context,
source_generic,
&member_type,
check_guard.next_level()?,
)?;
}
Ok(())
}
Comment on lines +102 to +114

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Calling check_generic_type_compact directly on union members bypasses critical type-checking logic defined in check_general_type_compact, such as is_like_any (which handles any and unknown), escape_type (which resolves aliases), and fast_eq_check.

If a union contains unknown or an alias type, calling check_generic_type_compact directly will result in a false positive TypeNotMatch error because those types are not explicitly handled in the match statement of check_generic_type_compact.

To fix this, we should construct a LuaType::Generic from source_generic and call check_general_type_compact for each member of the union.

Additionally, it is highly recommended to add a test case to crates/emmylua_code_analysis/src/semantic/type_check/test.rs to verify this behavior:

#[test]
fn test_issue_1106() {
    let mut ws = VirtualWorkspace::new();
    ws.def(
        r#"
        ---@class Item
        ---@field id integer
        ---@class Box<T>
        ---@field value T
        ---@class Service
        ---@field execute fun(self:Service, box:Box<Item>)
        ---@field fetch fun(self:Service):Box<Item>
        "#,
    );

    assert!(ws.has_no_diagnostic(
        DiagnosticCode::ParamTypeMismatch,
        r#"
        local Service = {}
        function Service:execute(_box) end
        function Service:fetch() return {} --[[@as Box<Item>]] end
        function Service:run()
            local b = self:fetch()
            self:execute(b)
        end
        "#
    ));
}
Suggested change
LuaType::Union(union_type) => {
// When compact_type is a Union, every member of the union must be
// assignable to the source generic type for the assignment to be safe.
for member_type in union_type.into_vec() {
check_generic_type_compact(
context,
source_generic,
&member_type,
check_guard.next_level()?,
)?;
}
Ok(())
}
LuaType::Union(union_type) => {
// When compact_type is a Union, every member of the union must be
// assignable to the source generic type for the assignment to be safe.
let source_type = LuaType::Generic(source_generic.clone().into());
for member_type in union_type.into_vec() {
check_general_type_compact(
context,
&source_type,
&member_type,
check_guard.next_level()?,
)?;
}
Ok(())
}

_ => Err(TypeCheckFailReason::TypeNotMatch),
}
}
Expand Down
Loading