Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,12 @@ fn is_in_global_member(analyzer: &DeclAnalyzer, index_expr: &LuaIndexExpr) -> Op
return Some(false);
}

// Treat globals as global-path owners so member defs on globals are discoverable.
let decl = analyzer.find_decl(&name_text, name.get_position());
return Some(decl.is_none());
return Some(match decl {
Some(decl) => decl.is_global(),
None => true,
});
}
_ => {}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,12 @@ pub fn bind_if_stat(binder: &mut FlowBinder, if_stat: LuaIfStat, current: FlowId
binder.add_antecedent(post_if_label, else_label);
}

if let Some(flow_node) = binder.get_flow(post_if_label)
&& flow_node.antecedent.is_none()
{
return binder.unreachable;
}

finish_flow_label(binder, post_if_label, else_label)
}

Expand Down
31 changes: 31 additions & 0 deletions crates/emmylua_code_analysis/src/compilation/analyzer/lua/stats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub fn analyze_local_stat(analyzer: &mut LuaAnalyzer, local_stat: LuaLocalStat)
let position = local_name.get_position();
let decl_id = LuaDeclId::new(analyzer.file_id, position);
// 标记了延迟定义属性, 此时将跳过绑定类型, 等待第一次赋值时再绑定类型
// Skip binding when delayed_definition is present; wait for first assignment.
if has_delayed_definition_attribute(analyzer, decl_id) {
return Some(());
}
Expand Down Expand Up @@ -51,6 +52,7 @@ pub fn analyze_local_stat(analyzer: &mut LuaAnalyzer, local_stat: LuaLocalStat)
expr_type = multi.get_type(0)?.clone();
}
let decl_id = LuaDeclId::new(analyzer.file_id, position);
// Delay when a call arg includes a table; the table may not be analyzed yet.
// 当`call`参数包含表时, 表可能未被分析, 需要延迟
if let LuaType::Instance(instance) = &expr_type
&& instance.get_base().is_unknown()
Expand Down Expand Up @@ -168,6 +170,7 @@ fn call_expr_has_effect_table_arg(expr: &LuaExpr) -> Option<()> {
if let LuaExpr::TableExpr(table_expr) = arg
&& !table_expr.is_empty()
{
// Non-empty table literals can introduce members we haven't indexed yet.
return Some(());
}
}
Expand Down Expand Up @@ -204,6 +207,32 @@ fn set_index_expr_owner(analyzer: &mut LuaAnalyzer, var_expr: LuaVarExpr) -> Opt
let file_id = analyzer.file_id;
let index_expr = LuaIndexExpr::cast(var_expr.syntax().clone())?;
let prefix_expr = index_expr.get_prefix_expr()?;
// Fast path: index into a known global type like `Class.field`.
if let LuaExpr::NameExpr(name_expr) = &prefix_expr {
if let Some(name) = name_expr.get_name_text()
&& let Some(decl_ids) = analyzer.db.get_global_index().get_global_decl_ids(&name)
{
// Resolve the global name to candidate decls and bind to any typed class/alias.
for decl_id in decl_ids {
if let Some(type_cache) = analyzer
.db
.get_type_index()
.get_type_cache(&(*decl_id).into())
{
match type_cache.as_type() {
LuaType::Def(def_id) | LuaType::Ref(def_id) => {
// Prefer binding members directly to known global types.
let member_id = LuaMemberId::new(index_expr.get_syntax_id(), file_id);
let member_owner = LuaMemberOwner::Type(def_id.clone());
add_member(analyzer.db, member_owner, member_id);
return Some(());
}
_ => {}
}
}
}
}
}

match analyzer.infer_expr(&prefix_expr.clone()) {
Ok(prefix_type) => {
Expand Down Expand Up @@ -266,6 +295,7 @@ pub fn analyze_assign_stat(analyzer: &mut LuaAnalyzer, assign_stat: LuaAssignSta
let type_owner = get_var_owner(analyzer, var.clone());
set_index_expr_owner(analyzer, var.clone());

// Short-circuit for patterns like a = a or b.
if special_assign_pattern(analyzer, type_owner.clone(), var.clone(), expr.clone()).is_some()
{
continue;
Expand Down Expand Up @@ -488,6 +518,7 @@ fn special_assign_pattern(
var: LuaVarExpr,
expr: LuaExpr,
) -> Option<()> {
// Handle "x = x or y" by merging only the right-hand type.
let access_path = var.get_access_path()?;
let binary_expr = if let LuaExpr::BinaryExpr(binary_expr) = expr {
binary_expr
Expand Down
116 changes: 116 additions & 0 deletions crates/emmylua_code_analysis/src/compilation/test/flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,24 @@ mod test {
));
}

#[test]
fn test_assert_cache_index_type() {
let mut ws = VirtualWorkspace::new_with_init_std_lib();

ws.def(
r#"
local M = {}
M.cache = {} ---@type table<integer,string?>
local i ---@type integer
A = assert(M.cache[i])
"#,
);

let ty = ws.expr_ty("A");
let desc = ws.humanize_type(ty);
assert_eq!(desc, "string");
}

#[test]
fn test_issue_107() {
let mut ws = VirtualWorkspace::new();
Expand Down Expand Up @@ -258,6 +276,80 @@ print(a.field)
));
}

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

let code = r#"
local i ---@type integer|fun():string

-- rhs is not integer|fun():string, so should throw a warning but still narrow to fun()
i = function() end

A = i
"#;

ws.def(code);

let i = ws.expr_ty("A");
let i_desc = ws.humanize_type(i);
assert_eq!(i_desc, "fun()");
}

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

let code = r#"
---@type std.RawGet<table, string>
local i

i = function() end

A = i
"#;

ws.def(code);

let i = ws.expr_ty("A");
let i_desc = ws.humanize_type(i);
assert_eq!(i_desc, "fun()");
}

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

ws.def(
r#"
local i ---@type integer|fun():string
i = function() return '' end
A = i
"#,
);

let i = ws.expr_ty("A");
let i_desc = ws.humanize_type(i);
assert_eq!(i_desc, "fun() -> \"\"");
}

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

ws.def(
r#"
local j ---@type integer|fun():string
j = true
A = j
"#,
);

let j = ws.expr_ty("A");
let j_desc = ws.humanize_type_detailed(j);
assert_eq!(j_desc, "true");
}

#[test]
fn test_issue_224() {
let mut ws = VirtualWorkspace::new();
Expand Down Expand Up @@ -588,6 +680,30 @@ end
assert_eq!(c, c_expected);
}

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

ws.def(
r#"
local r --- @type string?
local a --- @type boolean
if not r then
if a then
error()
else
error()
end
end

b = r -- should be string
"#,
);

let b = ws.expr_ty("b");
assert_eq!(b, LuaType::String);
}

#[test]
fn test_unknown_type() {
let mut ws = VirtualWorkspace::new();
Expand Down
Loading
Loading