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
38 changes: 38 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 @@ -207,6 +207,44 @@ fn set_index_expr_owner(analyzer: &mut LuaAnalyzer, var_expr: LuaVarExpr) -> Opt

match analyzer.infer_expr(&prefix_expr.clone()) {
Ok(prefix_type) => {
// Prefer declared global types for name prefixes when choosing a member owner.
// This keeps stdlib members (like table.unpack) attached to their type defs.
let prefix_type = if let LuaExpr::NameExpr(name_expr) = &prefix_expr {
let mut explicit_type = None;
if let Some(name) = name_expr.get_name_text() {
// Avoid attaching members to stdlib globals when a local shadows the name.
let is_shadowed = analyzer
.db
.get_decl_index()
.get_decl_tree(&file_id)
.and_then(|tree| tree.find_local_decl(&name, name_expr.get_position()))
.map(|decl| decl.is_local() || decl.is_implicit_self())
.unwrap_or(false);
if !is_shadowed
&& let Some(decl_ids) =
analyzer.db.get_global_index().get_global_decl_ids(&name)
{
// Pick the first resolvable global type cache as the owner type.
for decl_id in decl_ids {
if let Some(type_cache) = analyzer
.db
.get_type_index()
.get_type_cache(&(*decl_id).into())
{
explicit_type = Some(type_cache.as_type().clone());
break;
}
}
}
}
Comment on lines +213 to +239

Choose a reason for hiding this comment

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

medium

This block of code for determining the explicit_type can be refactored to be more concise and idiomatic by using a more functional style with and_then and find_map. This change will reduce nesting, remove the need for a mutable variable, and improve the overall readability of the logic.

                let explicit_type = name_expr.get_name_text().and_then(|name| {
                    // Avoid attaching members to stdlib globals when a local shadows the name.
                    let is_shadowed = analyzer
                        .db
                        .get_decl_index()
                        .get_decl_tree(&file_id)
                        .and_then(|tree| tree.find_local_decl(&name, name_expr.get_position()))
                        .map(|decl| decl.is_local() || decl.is_implicit_self())
                        .unwrap_or(false);

                    if is_shadowed {
                        None
                    } else {
                        analyzer
                            .db
                            .get_global_index()
                            .get_global_decl_ids(&name)
                            .and_then(|decl_ids| {
                                // Pick the first resolvable global type cache as the owner type.
                                decl_ids.iter().find_map(|decl_id| {
                                    analyzer
                                        .db
                                        .get_type_index()
                                        .get_type_cache(&(*decl_id).into())
                                        .map(|type_cache| type_cache.as_type().clone())
                                })
                            })
                    }
                });


// Fall back to the inferred prefix type when no explicit type exists.
explicit_type.unwrap_or(prefix_type)
} else {
// Non-name prefixes keep the inferred prefix type.
prefix_type
};

index_expr.get_index_key()?;
let member_id = LuaMemberId::new(index_expr.get_syntax_id(), file_id);
let member_owner = match prefix_type {
Expand Down
25 changes: 24 additions & 1 deletion crates/emmylua_code_analysis/src/semantic/type_check/test.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#[cfg(test)]
mod test {
use crate::{DiagnosticCode, VirtualWorkspace};
use crate::{DiagnosticCode, LuaType, VirtualWorkspace};

#[test]
fn test_string() {
Expand Down Expand Up @@ -208,4 +208,27 @@ mod test {
"#
));
}

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

ws.def_file(
"def.lua",
r#"
table = table

---@cast table unknown
AFTER_CAST = table

---@return integer
function table.__sentinel()
return 1
end
"#,
);

assert_eq!(ws.expr_ty("AFTER_CAST"), LuaType::Unknown);
assert_eq!(ws.expr_ty("table.__sentinel()"), ws.ty("integer"));
}
}