Skip to content
Open
Show file tree
Hide file tree
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
21 changes: 21 additions & 0 deletions crates/emmylua_code_analysis/src/compilation/test/flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,27 @@ print(a.field)
));
}

#[test]
fn test_doc_function_assignment_narrowing() {
let mut ws = VirtualWorkspace::new();

let code = r#"
local i --- @type integer|fun():string
i = function() end
_ = i()
A = i
"#;

ws.def(code);

assert!(ws.check_code_for(DiagnosticCode::CallNonCallable, code));
assert!(ws.check_code_for(DiagnosticCode::NeedCheckNil, code));

let a = ws.expr_ty("A");
let a_desc = ws.humanize_type_detailed(a);
assert_eq!(a_desc, "fun()");
}

#[test]
fn test_issue_224() {
let mut ws = VirtualWorkspace::new();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::{DbIndex, LuaType, TypeOps, get_real_type, semantic::type_check::is_s
pub use false_or_nil_type::{narrow_false_or_nil, remove_false_or_nil};

// need to be optimized
// `source` is the current/antecedent type, `target` is the narrowing candidate (e.g. assignment RHS).
pub fn narrow_down_type(db: &DbIndex, source: LuaType, target: LuaType) -> Option<LuaType> {
if source == target {
return Some(source);
Expand Down Expand Up @@ -73,6 +74,12 @@ pub fn narrow_down_type(db: &DbIndex, source: LuaType, target: LuaType) -> Optio
return Some(source);
}
}
LuaType::Signature(_) => {
if real_source_ref.is_function() {
// Prefer the assigned closure signature, even when the antecedent is a doc function.
return Some(target.clone());
}
}
Comment on lines 77 to 82

Choose a reason for hiding this comment

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

medium

This change correctly preserves the declared Signature type when a function is assigned, which is great for cases where you want to maintain the API contract.

However, the new test test_doc_function_assignment_narrowing uses a DocFunction from a type annotation (--- @type integer|fun():string), not a Signature.

The test expects the type to be narrowed to the assigned function's type (fun() -> nil), which implies that for DocFunction, the narrowing logic should probably use the source type (from the RHS of the assignment). This is different from the logic for Signature where you are correctly returning target to preserve the declaration.

Could you clarify this behavior? It seems a similar change might be needed for DocFunction but with a different implementation (returning source instead of target).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

In narrow_down_type, source is the antecedent/declared type and target is the assignment RHS type. For a closure RHS we infer Signature, and we intentionally return the target signature even when the antecedent is a DocFunction. That matches the runtime value (the assigned closure) and is why the new test fails without this change. So no extra DocFunction-specific branch is needed here; is_function() already covers it.

LuaType::Thread => {
if real_source_ref.is_thread() {
return Some(source);
Expand Down