From c68beb801b5cc7b1d221633594b32053b11cb92e Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Fri, 28 Mar 2025 10:33:23 +0800 Subject: [PATCH 01/18] fix remove_type remove self --- .../src/compilation/test/flow.rs | 18 ++++++++++++++++++ .../src/db_index/type/type_ops/remove_type.rs | 6 +++++- .../src/semantic/infer/infer_binary.rs | 6 +++++- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/crates/emmylua_code_analysis/src/compilation/test/flow.rs b/crates/emmylua_code_analysis/src/compilation/test/flow.rs index 47b5f4a17..4bd057a24 100644 --- a/crates/emmylua_code_analysis/src/compilation/test/flow.rs +++ b/crates/emmylua_code_analysis/src/compilation/test/flow.rs @@ -343,4 +343,22 @@ end let a_desc = ws.humanize_type(a); assert_eq!(a_desc, "table"); } + + #[test] + fn test_docint() { + let mut ws = VirtualWorkspace::new(); + + ws.def( + r#" + local stack = 0 + if stack ~= 0 then + a = stack + end + "#, + ); + + let a = ws.expr_ty("a"); + let a_desc = ws.humanize_type(a); + assert_eq!(a_desc, "integer"); + } } diff --git a/crates/emmylua_code_analysis/src/db_index/type/type_ops/remove_type.rs b/crates/emmylua_code_analysis/src/db_index/type/type_ops/remove_type.rs index b02316a83..6718b260f 100644 --- a/crates/emmylua_code_analysis/src/db_index/type/type_ops/remove_type.rs +++ b/crates/emmylua_code_analysis/src/db_index/type/type_ops/remove_type.rs @@ -2,7 +2,11 @@ use crate::{LuaType, LuaUnionType}; pub fn remove_type(source: LuaType, removed_type: LuaType) -> Option { if source == removed_type { - return None; + match source { + LuaType::IntegerConst(_) => return Some(LuaType::Integer), + LuaType::FloatConst(_) => return Some(LuaType::Number), + _ => return None, + } } match (&source, &removed_type) { diff --git a/crates/emmylua_code_analysis/src/semantic/infer/infer_binary.rs b/crates/emmylua_code_analysis/src/semantic/infer/infer_binary.rs index a567c080c..b2f5b7c8f 100644 --- a/crates/emmylua_code_analysis/src/semantic/infer/infer_binary.rs +++ b/crates/emmylua_code_analysis/src/semantic/infer/infer_binary.rs @@ -160,7 +160,11 @@ fn infer_binary_expr_add(db: &DbIndex, left: LuaType, right: LuaType) -> InferRe } }; } - match (left.is_nil(), right.is_nil()) { + + match ( + left.is_nil() || left.is_any() || left.is_unknown(), + right.is_nil() || right.is_any() || right.is_unknown(), + ) { (true, false) => return Ok(right), (false, true) => return Ok(left), _ => {} From 469875ac294332deb9cad812adbbd6862b413e0b Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Fri, 28 Mar 2025 13:37:15 +0800 Subject: [PATCH 02/18] =?UTF-8?q?type=20check=20=E5=85=81=E8=AE=B8=20self?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../diagnostic/test/param_type_check_test.rs | 21 +++++++++++++++++++ .../src/semantic/type_check/func_type.rs | 3 +++ 2 files changed, 24 insertions(+) diff --git a/crates/emmylua_code_analysis/src/diagnostic/test/param_type_check_test.rs b/crates/emmylua_code_analysis/src/diagnostic/test/param_type_check_test.rs index b15632e2f..1680a21b7 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/test/param_type_check_test.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/test/param_type_check_test.rs @@ -445,4 +445,25 @@ mod test { "# )) } + + #[test] + fn test_4() { + let mut ws = VirtualWorkspace::new(); + + assert!(ws.check_code_for( + DiagnosticCode::ParamTypeNotMatch, + r#" + ---@class D13.Meta + ---@field __defineGet fun(self: self, key: string, f: fun(self: self): any) + + ---@class D13.Impl: D13.Meta + local impl = {} + + impl:__defineGet("value", function(self) + return 1 + end) + + "# + )); + } } diff --git a/crates/emmylua_code_analysis/src/semantic/type_check/func_type.rs b/crates/emmylua_code_analysis/src/semantic/type_check/func_type.rs index 9e4ff35f7..883bc6e62 100644 --- a/crates/emmylua_code_analysis/src/semantic/type_check/func_type.rs +++ b/crates/emmylua_code_analysis/src/semantic/type_check/func_type.rs @@ -91,6 +91,9 @@ fn check_doc_func_type_compact_for_params( match (source_param_type, compact_param_type) { (Some(source_type), Some(compact_type)) => { + if source_type.is_self_infer() && compact_type.is_self_infer() { + continue; + } if check_general_type_compact( db, source_type, From 9dc43c3ff7368e6bde9dd961eb7158e44cfd1d5e Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Fri, 28 Mar 2025 14:18:55 +0800 Subject: [PATCH 03/18] std update --- crates/emmylua_code_analysis/resources/std/global.lua | 6 ++---- crates/emmylua_code_analysis/resources/std/math.lua | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/crates/emmylua_code_analysis/resources/std/global.lua b/crates/emmylua_code_analysis/resources/std/global.lua index 5a9ad8007..1ed034b7a 100644 --- a/crates/emmylua_code_analysis/resources/std/global.lua +++ b/crates/emmylua_code_analysis/resources/std/global.lua @@ -390,12 +390,10 @@ function setmetatable(table, metatable) end --- represents 10, 'B' represents 11, and so forth, with 'Z' representing 35. If --- the string `e` is not a valid numeral in the given base, the function --- returns **nil**. ----@overload fun(e:number, base?: int):number ----@overload fun(e:string, base?: int):number? ----@overload fun(e:any, base?:int):nil +---@overload fun(e: string, base: integer):integer? ---@param e any ----@param base? int ---@return number? +---@nodiscard function tonumber(e, base) end --- diff --git a/crates/emmylua_code_analysis/resources/std/math.lua b/crates/emmylua_code_analysis/resources/std/math.lua index 24c9ddc8e..ab32cbe67 100644 --- a/crates/emmylua_code_analysis/resources/std/math.lua +++ b/crates/emmylua_code_analysis/resources/std/math.lua @@ -186,7 +186,7 @@ function math.tan(x) return 0 end --- --- If the value `x` is convertible to an integer, returns that integer. --- Otherwise, returns `nil`. ----@param x number +---@param x any ---@return integer? function math.tointeger(x) end From 75318bf95778eb45cbbced6379fc362106b98ea7 Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Fri, 28 Mar 2025 14:41:28 +0800 Subject: [PATCH 04/18] diagnostic: fix #290 --- .../checker/incomplete_signature_doc.rs | 2 +- .../test/incomplete_signature_doc_test.rs | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/crates/emmylua_code_analysis/src/diagnostic/checker/incomplete_signature_doc.rs b/crates/emmylua_code_analysis/src/diagnostic/checker/incomplete_signature_doc.rs index 5d83667c2..393952dfb 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/checker/incomplete_signature_doc.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/checker/incomplete_signature_doc.rs @@ -129,7 +129,7 @@ fn check_params( }; let name = name_token.get_name_text(); - if !doc_param_names.contains(name) { + if !doc_param_names.contains(name) && name != "_" { let message = if is_global { t!( "Missing @param annotation for parameter `%{name}` in global function `%{function_name}`.", diff --git a/crates/emmylua_code_analysis/src/diagnostic/test/incomplete_signature_doc_test.rs b/crates/emmylua_code_analysis/src/diagnostic/test/incomplete_signature_doc_test.rs index 7b84de774..4986fd185 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/test/incomplete_signature_doc_test.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/test/incomplete_signature_doc_test.rs @@ -3,6 +3,22 @@ mod tests { use crate::{DiagnosticCode, VirtualWorkspace}; + #[test] + fn test_290() { + let mut ws = VirtualWorkspace::new(); + ws.enable_full_diagnostic(); + + assert!(ws.check_code_for( + DiagnosticCode::IncompleteSignatureDoc, + r#" + ---@param a string + local function foo(_, a) + _ = a + end + "# + )); + } + #[test] fn test_return() { let mut ws = VirtualWorkspace::new(); From 25a031f8f15980cbe7f5ad858417caab7628984d Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Fri, 28 Mar 2025 19:38:42 +0800 Subject: [PATCH 05/18] std update --- .../emmylua_code_analysis/resources/std/global.lua | 2 +- crates/emmylua_code_analysis/resources/std/table.lua | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/crates/emmylua_code_analysis/resources/std/global.lua b/crates/emmylua_code_analysis/resources/std/global.lua index 1ed034b7a..230ad16a2 100644 --- a/crates/emmylua_code_analysis/resources/std/global.lua +++ b/crates/emmylua_code_analysis/resources/std/global.lua @@ -394,7 +394,7 @@ function setmetatable(table, metatable) end ---@param e any ---@return number? ---@nodiscard -function tonumber(e, base) end +function tonumber(e) end --- --- Receives a value of any type and converts it to a string in a human-readable diff --git a/crates/emmylua_code_analysis/resources/std/table.lua b/crates/emmylua_code_analysis/resources/std/table.lua index 10969fa7e..d6456883b 100644 --- a/crates/emmylua_code_analysis/resources/std/table.lua +++ b/crates/emmylua_code_analysis/resources/std/table.lua @@ -21,14 +21,12 @@ table = {} --- `list[i]..sep..list[i+1] ... sep..list[j]`. The default value for --- `sep` is the empty string, the default for `i` is 1, and the default for --- `j` is #list. If `i` is greater than `j`, returns the empty string. ----@overload fun(list:string[]):string ----@overload fun(list:string[], sep:string):string ----@overload fun(list:string[], sep:string, i:integer):string ----@param list string[] ----@param sep string ----@param i integer ----@param j integer +---@param list table +---@param sep? string +---@param i? integer +---@param j? integer ---@return string +---@nodiscard function table.concat(list, sep, i, j) end --- From f9e9f98ae1c2b1789f16e1fc2b8cea753bf10013 Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Fri, 28 Mar 2025 20:08:01 +0800 Subject: [PATCH 06/18] diagnostic: optimize duplicate_require --- .../src/diagnostic/checker/duplicate_require.rs | 5 ++++- .../src/diagnostic/test/duplicate_require_test.rs | 13 +++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/crates/emmylua_code_analysis/src/diagnostic/checker/duplicate_require.rs b/crates/emmylua_code_analysis/src/diagnostic/checker/duplicate_require.rs index f6623e163..ba2ce0a59 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/checker/duplicate_require.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/checker/duplicate_require.rs @@ -1,4 +1,4 @@ -use emmylua_parser::{LuaAstNode, LuaBlock, LuaCallExpr}; +use emmylua_parser::{LuaAstNode, LuaBlock, LuaCallExpr, LuaIndexExpr}; use rowan::TextRange; use crate::{DiagnosticCode, LuaType, SemanticModel}; @@ -27,6 +27,9 @@ fn check_require_call_expr( call_expr: LuaCallExpr, require_calls: &mut Vec<(TextRange, String)>, ) -> Option<()> { + if let Some(_) = call_expr.get_parent::() { + return Some(()); + } let args_list = call_expr.get_args_list()?; let arg_expr = args_list.get_args().next()?; diff --git a/crates/emmylua_code_analysis/src/diagnostic/test/duplicate_require_test.rs b/crates/emmylua_code_analysis/src/diagnostic/test/duplicate_require_test.rs index 69ce50db5..8988fc64e 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/test/duplicate_require_test.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/test/duplicate_require_test.rs @@ -30,4 +30,17 @@ mod tests { "#, )); } + + #[test] + fn test_field() { + let mut ws = VirtualWorkspace::new(); + // 作用域不同 + assert!(ws.check_code_for( + DiagnosticCode::DuplicateRequire, + r#" + require("a").a + require("a") + "#, + )); + } } From 7f464dba89f3e327345224f19af85042c3032b92 Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Fri, 28 Mar 2025 21:05:48 +0800 Subject: [PATCH 07/18] =?UTF-8?q?fix=20flow,=20=E7=A6=81=E6=AD=A2`ref`?= =?UTF-8?q?=E9=87=8D=E6=96=B0=E5=88=86=E9=85=8D=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/db_index/type/type_assert.rs | 4 ++++ .../test/assign_type_mismatch_test.rs | 24 +++++++++++++++++++ .../src/semantic/infer/infer_index.rs | 22 +++++++++++++++-- 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/crates/emmylua_code_analysis/src/db_index/type/type_assert.rs b/crates/emmylua_code_analysis/src/db_index/type/type_assert.rs index dad46c1a8..af98533e5 100644 --- a/crates/emmylua_code_analysis/src/db_index/type/type_assert.rs +++ b/crates/emmylua_code_analysis/src/db_index/type/type_assert.rs @@ -57,4 +57,8 @@ impl TypeAssertion { _ => Ok(source), } } + + pub fn is_reassign(&self) -> bool { + matches!(self, TypeAssertion::Reassign(_)) + } } diff --git a/crates/emmylua_code_analysis/src/diagnostic/test/assign_type_mismatch_test.rs b/crates/emmylua_code_analysis/src/diagnostic/test/assign_type_mismatch_test.rs index e1d31bf74..97d4b7d94 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/test/assign_type_mismatch_test.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/test/assign_type_mismatch_test.rs @@ -634,4 +634,28 @@ return t "# )); } + + #[test] + fn test_issue_295() { + let mut ws = VirtualWorkspace::new(); + assert!(!ws.check_code_for( + DiagnosticCode::AssignTypeMismatch, + r#" + + ---@enum SubscriberFlags + local SubscriberFlags = { + Tracking = 1 << 0, + } + ---@class Subscriber + ---@field flags SubscriberFlags + + ---@type Subscriber + local subscriber + + subscriber.flags = subscriber.flags & ~SubscriberFlags.Tracking + + subscriber.flags = 9 + "# + )); + } } diff --git a/crates/emmylua_code_analysis/src/semantic/infer/infer_index.rs b/crates/emmylua_code_analysis/src/semantic/infer/infer_index.rs index ddffdbb87..c7c68d1ff 100644 --- a/crates/emmylua_code_analysis/src/semantic/infer/infer_index.rs +++ b/crates/emmylua_code_analysis/src/semantic/infer/infer_index.rs @@ -65,12 +65,27 @@ fn infer_member_type_pass_flow( prefix_type: &LuaType, mut member_type: LuaType, ) -> InferResult { - // temp fix flow - // TODO: flow analysis should not generate corresponding `flow_chain` if the prefix type is an array + let mut allow_reassign = true; match &prefix_type { + // TODO: flow analysis should not generate corresponding `flow_chain` if the prefix type is an array LuaType::Array(_) => { return Ok(member_type.clone()); } + LuaType::Ref(decl_id) => { + if let Some(members) = db + .get_member_index() + .get_members(&LuaMemberOwner::Type(decl_id.clone())) + { + if let Some(key) = index_expr.get_index_key() { + if let Some(_) = members + .iter() + .find(|m| m.get_key().to_path() == key.get_path_part()) + { + allow_reassign = false + } + } + } + } _ => {} } @@ -82,6 +97,9 @@ fn infer_member_type_pass_flow( let root = index_expr.get_root(); if let Some(path) = index_expr.get_access_path() { for type_assert in flow_chain.get_type_asserts(&path, index_expr.get_position(), None) { + if type_assert.is_reassign() && !allow_reassign { + continue; + } member_type = type_assert .tighten_type(db, cache, &root, member_type) .unwrap_or(LuaType::Unknown); From 87bd4e8540b53e2ccbd4eeacfa19e8859b5b6a4f Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Fri, 28 Mar 2025 23:21:08 +0800 Subject: [PATCH 08/18] fix #287 --- .../src/db_index/type/humanize_type.rs | 33 ++++++++++++++----- .../src/semantic/infer/infer_name.rs | 12 ++++--- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/crates/emmylua_code_analysis/src/db_index/type/humanize_type.rs b/crates/emmylua_code_analysis/src/db_index/type/humanize_type.rs index 729c6a9a3..069fc8841 100644 --- a/crates/emmylua_code_analysis/src/db_index/type/humanize_type.rs +++ b/crates/emmylua_code_analysis/src/db_index/type/humanize_type.rs @@ -1,3 +1,5 @@ +use std::collections::HashSet; + use crate::{ DbIndex, GenericTpl, LuaAliasCallType, LuaFunctionType, LuaGenericType, LuaInstanceType, LuaIntersectionType, LuaMemberKey, LuaMemberOwner, LuaMultiReturn, LuaObjectType, @@ -170,15 +172,30 @@ fn humanize_union_type(db: &DbIndex, union: &LuaUnionType, level: RenderLevel) - return "union<...>".to_string(); } }; - let dots = if types.len() > num { "..." } else { "" }; + // 需要确保顺序 + let mut seen = HashSet::new(); + let mut type_strings = Vec::new(); + for ty in types.iter() { + let type_str = humanize_type(db, ty, level.next_level()); + if seen.insert(type_str.clone()) { + type_strings.push(type_str); + } + } - let type_str = types - .iter() - .take(num) - .map(|ty| humanize_type(db, ty, level.next_level())) - .collect::>() - .join("|"); - format!("({}{})", type_str, dots) + // 取指定数量的类型 + let display_types: Vec<_> = type_strings.into_iter().take(num).collect(); + let type_str = display_types.join("|"); + let dots = if display_types.len() < types.len() { + "..." + } else { + "" + }; + + if display_types.len() == 1 { + type_str + } else { + format!("({}{})", type_str, dots) + } } fn humanize_multi_line_union_type( diff --git a/crates/emmylua_code_analysis/src/semantic/infer/infer_name.rs b/crates/emmylua_code_analysis/src/semantic/infer/infer_name.rs index 956e9b95a..26d320a1a 100644 --- a/crates/emmylua_code_analysis/src/semantic/infer/infer_name.rs +++ b/crates/emmylua_code_analysis/src/semantic/infer/infer_name.rs @@ -1,4 +1,4 @@ -use emmylua_parser::{LuaAstNode, LuaNameExpr}; +use emmylua_parser::{LuaAssignStat, LuaAstNode, LuaNameExpr}; use crate::{ db_index::{DbIndex, LuaDeclOrMemberId}, @@ -37,13 +37,15 @@ pub fn infer_name_expr( let flow_chain = db.get_flow_index().get_flow_chain(file_id, flow_id); let root = name_expr.get_root(); if let Some(flow_chain) = flow_chain { - for type_assert in - flow_chain.get_type_asserts(name, name_expr.get_position(), Some(decl_id.position)) - { + let mut position = name_expr.get_position(); + // 如果是赋值语句, 那么我们使用赋值语句的结束位置来获取类型 + if let Some(assign_stat) = name_expr.get_parent::() { + position = assign_stat.get_range().end(); + } + for type_assert in flow_chain.get_type_asserts(name, position, Some(decl_id.position)) { decl_type = type_assert.tighten_type(db, cache, &root, decl_type)?; } } - Ok(decl_type) } else { infer_global_type(db, name) From 8d04108b4450d0324555ad6e838550b45feb2cfd Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Sat, 29 Mar 2025 01:20:25 +0800 Subject: [PATCH 09/18] fix #286 --- .../diagnostic/test/param_type_check_test.rs | 26 +++++++++++++++++++ .../src/semantic/infer/infer_binary.rs | 22 +++++++++++----- 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/crates/emmylua_code_analysis/src/diagnostic/test/param_type_check_test.rs b/crates/emmylua_code_analysis/src/diagnostic/test/param_type_check_test.rs index 1680a21b7..d7a7fc6b0 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/test/param_type_check_test.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/test/param_type_check_test.rs @@ -466,4 +466,30 @@ mod test { "# )); } + + #[test] + fn test_issue_286() { + let mut ws = VirtualWorkspace::new(); + assert!(ws.check_code_for( + DiagnosticCode::ParamTypeNotMatch, + r#" + local a --- @type boolean + local b --- @type integer? + local c = a and b or nil + -- type of c is (nil|true), should be (integer|nil) + local d = a and b + -- type of d is (boolean|nil), should be (false|integer|nil) + + ---@param p integer? + local function f1(p) + end + f1(c) + + ---@param p false|integer|nil + local function f2(p) + end + f2(d) + "# + )); + } } diff --git a/crates/emmylua_code_analysis/src/semantic/infer/infer_binary.rs b/crates/emmylua_code_analysis/src/semantic/infer/infer_binary.rs index b2f5b7c8f..2cf371796 100644 --- a/crates/emmylua_code_analysis/src/semantic/infer/infer_binary.rs +++ b/crates/emmylua_code_analysis/src/semantic/infer/infer_binary.rs @@ -16,12 +16,12 @@ pub fn infer_binary_expr( ) -> InferResult { let op = expr.get_op_token().ok_or(InferFailReason::None)?.get_op(); let (left, right) = expr.get_exprs().ok_or(InferFailReason::None)?; - let left_type = infer_expr(db, cache, left)?; - let right_type = infer_expr(db, cache, right)?; + let left_type = infer_expr(db, cache, left.clone())?; + let right_type = infer_expr(db, cache, right.clone())?; match (&left_type, &right_type) { - (LuaType::Union(u), right) => infer_union(db, &u, right, op), - (left, LuaType::Union(u)) => infer_union(db, &u, left, op), + (LuaType::Union(u), right) => infer_union(db, &u, right, op, true), + (left, LuaType::Union(u)) => infer_union(db, &u, left, op, false), _ => infer_binary_expr_type(db, left_type, right_type, op), } } @@ -58,11 +58,21 @@ fn infer_binary_expr_type( } } -fn infer_union(db: &DbIndex, u: &LuaUnionType, right: &LuaType, op: BinaryOperator) -> InferResult { +fn infer_union( + db: &DbIndex, + u: &LuaUnionType, + right: &LuaType, + op: BinaryOperator, + union_is_left: bool, +) -> InferResult { let mut unique_union_types = Vec::new(); for ty in u.get_types() { - let inferred_ty = infer_binary_expr_type(db, ty.clone(), right.clone(), op)?; + let inferred_ty = if union_is_left { + infer_binary_expr_type(db, ty.clone(), right.clone(), op)? + } else { + infer_binary_expr_type(db, right.clone(), ty.clone(), op)? + }; flatten_and_insert(inferred_ty, &mut unique_union_types); } From 6032c978d1c9a7e025e3ec920d4bef21689bea17 Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Sat, 29 Mar 2025 16:50:41 +0800 Subject: [PATCH 10/18] table def support table field --- .../src/compilation/analyzer/decl/exprs.rs | 35 +++++--- .../src/compilation/analyzer/decl/mod.rs | 28 ++++-- .../src/compilation/analyzer/unresolve/mod.rs | 28 +++++- .../compilation/analyzer/unresolve/resolve.rs | 85 ++++++++++++++++++- .../src/db_index/member/lua_member.rs | 9 ++ .../src/db_index/type/humanize_type.rs | 2 + .../src/diagnostic/checker/check_field.rs | 76 ++++++++++------- .../diagnostic/test/undefined_field_test.rs | 43 +++++++--- .../src/semantic/type_check/ref_type.rs | 1 + .../providers/type_special_provider.rs | 2 + .../src/syntax/node/lua/stat.rs | 14 +++ 11 files changed, 258 insertions(+), 65 deletions(-) diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/decl/exprs.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/decl/exprs.rs index 1b32e110b..ee569f962 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/decl/exprs.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/decl/exprs.rs @@ -4,8 +4,10 @@ use emmylua_parser::{ }; use crate::{ + compilation::analyzer::unresolve::UnResolveTableField, db_index::{LuaDecl, LuaMember, LuaMemberKey, LuaMemberOwner}, - FileId, InFiled, LuaDeclExtra, LuaDeclId, LuaMemberFeature, LuaMemberId, LuaSignatureId, + FileId, InFiled, InferFailReason, LuaDeclExtra, LuaDeclId, LuaMemberFeature, LuaMemberId, + LuaSignatureId, }; use super::DeclAnalyzer; @@ -180,18 +182,33 @@ fn analyze_closure_params( Some(()) } -pub fn analyze_table_expr(analyzer: &mut DeclAnalyzer, expr: LuaTableExpr) -> Option<()> { - if expr.is_object() { +pub fn analyze_table_expr(analyzer: &mut DeclAnalyzer, table_expr: LuaTableExpr) -> Option<()> { + if table_expr.is_object() { let file_id = analyzer.get_file_id(); let owner_id = LuaMemberOwner::Element(InFiled { file_id, - value: expr.get_range(), + value: table_expr.get_range(), }); + let decl_feature = if analyzer.is_meta { + LuaMemberFeature::MetaDefine + } else { + LuaMemberFeature::FileDefine + }; - for field in expr.get_fields() { + for field in table_expr.get_fields() { if let Some(field_key) = field.get_field_key() { - let key: LuaMemberKey = field_key.into(); + let key: LuaMemberKey = field_key.clone().into(); if key.is_none() { + if let Some(field_expr) = field_key.get_expr() { + let unresolve_member = UnResolveTableField { + file_id: analyzer.get_file_id(), + table_expr: table_expr.clone(), + field: field.clone(), + decl_feature, + reason: InferFailReason::UnResolveExpr(field_expr.clone()), + }; + analyzer.add_unresolved(unresolve_member.into()); + } continue; } @@ -201,12 +218,6 @@ pub fn analyze_table_expr(analyzer: &mut DeclAnalyzer, expr: LuaTableExpr) -> Op field.get_syntax_id(), ); - let decl_feature = if analyzer.is_meta { - LuaMemberFeature::MetaDefine - } else { - LuaMemberFeature::FileDefine - }; - let member_id = LuaMemberId::new(field.get_syntax_id(), file_id); let member = LuaMember::new(owner_id.clone(), member_id, key, decl_feature, None); analyzer.db.get_member_index_mut().add_member(member); diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/decl/mod.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/decl/mod.rs index 0ec76119b..428a44ac0 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/decl/mod.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/decl/mod.rs @@ -7,7 +7,7 @@ use crate::{ profile::Profile, }; -use super::AnalyzeContext; +use super::{unresolve::UnResolve, AnalyzeContext}; use emmylua_parser::{LuaAst, LuaAstNode, LuaChunk, LuaFuncStat, LuaSyntaxKind, LuaVarExpr}; use rowan::{TextRange, TextSize, WalkEvent}; @@ -18,14 +18,18 @@ use crate::{ pub(crate) fn analyze(db: &mut DbIndex, context: &mut AnalyzeContext) { let _p = Profile::cond_new("decl analyze", context.tree_list.len() > 1); - for in_filed_tree in context.tree_list.iter() { + let tree_list = context.tree_list.clone(); + for in_filed_tree in tree_list.iter() { db.get_reference_index_mut() .create_local_reference(in_filed_tree.file_id); let mut analyzer = DeclAnalyzer::new(db, in_filed_tree.file_id, in_filed_tree.value.clone()); analyzer.analyze(); - let decl_tree = analyzer.get_decl_tree(); + let (decl_tree, unresolved) = analyzer.get_decl_tree(); db.get_decl_index_mut().add_decl_tree(decl_tree); + for unresolve in unresolved { + context.add_unresolve(unresolve); + } } } @@ -138,6 +142,7 @@ pub struct DeclAnalyzer<'a> { decl: LuaDeclarationTree, scopes: Vec, is_meta: bool, + pub unresolved: Vec, } impl<'a> DeclAnalyzer<'a> { @@ -148,6 +153,7 @@ impl<'a> DeclAnalyzer<'a> { decl: LuaDeclarationTree::new(file_id), scopes: Vec::new(), is_meta: false, + unresolved: Vec::new(), } } @@ -161,8 +167,16 @@ impl<'a> DeclAnalyzer<'a> { } } - pub fn get_decl_tree(self) -> LuaDeclarationTree { - self.decl + pub fn get_file_id(&self) -> FileId { + self.decl.file_id() + } + + pub fn add_unresolved(&mut self, unresolved: UnResolve) { + self.unresolved.push(unresolved); + } + + pub fn get_decl_tree(self) -> (LuaDeclarationTree, Vec) { + (self.decl, self.unresolved) } pub fn create_scope(&mut self, range: TextRange, kind: LuaScopeKind) { @@ -206,10 +220,6 @@ impl<'a> DeclAnalyzer<'a> { pub fn find_decl(&self, name: &str, position: TextSize) -> Option<&LuaDecl> { self.decl.find_local_decl(name, position) } - - pub fn get_file_id(&self) -> FileId { - self.decl.file_id() - } } fn is_method_func_stat(stat: &LuaFuncStat) -> Option { diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/mod.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/mod.rs index d51ca0660..53b50fdcc 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/mod.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/mod.rs @@ -7,15 +7,17 @@ mod resolve_closure; use crate::{ db_index::{DbIndex, LuaDeclId, LuaMemberId, LuaSignatureId}, profile::Profile, - FileId, InferFailReason, LuaSemanticDeclId, + FileId, InferFailReason, LuaMemberFeature, LuaSemanticDeclId, }; use check_reason::{resolve_all_reason, resolve_as_any}; -use emmylua_parser::{LuaAssignStat, LuaCallExpr, LuaExpr, LuaFuncStat, LuaTableField}; +use emmylua_parser::{ + LuaAssignStat, LuaCallExpr, LuaExpr, LuaFuncStat, LuaTableExpr, LuaTableField, +}; use infer_manager::InferCacheManager; pub use merge_type::{merge_decl_expr_type, merge_member_type}; use resolve::{ try_resolve_decl, try_resolve_iter_var, try_resolve_member, try_resolve_module, - try_resolve_module_ref, try_resolve_return_point, + try_resolve_module_ref, try_resolve_return_point, try_resolve_table_field, }; use resolve_closure::{ try_resolve_closure_params, try_resolve_closure_parent_params, try_resolve_closure_return, @@ -92,6 +94,9 @@ fn try_resolve( try_resolve_closure_parent_params(db, config, un_resolve_closure_params) .unwrap_or(false) } + UnResolve::TableField(un_resolve_table_field) => { + try_resolve_table_field(db, config, un_resolve_table_field).unwrap_or(false) + } UnResolve::None => continue, }; @@ -116,6 +121,7 @@ pub enum UnResolve { ClosureReturn(Box), ClosureParentParams(Box), ModuleRef(Box), + TableField(Box), } #[allow(dead_code)] @@ -140,6 +146,7 @@ impl UnResolve { UnResolve::ClosureParentParams(un_resolve_closure_params) => { Some(un_resolve_closure_params.file_id) } + UnResolve::TableField(un_resolve_table_field) => Some(un_resolve_table_field.file_id), UnResolve::ModuleRef(_) => None, UnResolve::None => None, } @@ -279,3 +286,18 @@ impl From for UnResolve { UnResolve::ClosureParentParams(Box::new(un_resolve_closure_params)) } } + +#[derive(Debug)] +pub struct UnResolveTableField { + pub file_id: FileId, + pub table_expr: LuaTableExpr, + pub field: LuaTableField, + pub decl_feature: LuaMemberFeature, + pub reason: InferFailReason, +} + +impl From for UnResolve { + fn from(un_resolve_table_field: UnResolveTableField) -> Self { + UnResolve::TableField(Box::new(un_resolve_table_field)) + } +} diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/resolve.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/resolve.rs index 1e8b4d952..7366ec20d 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/resolve.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/resolve.rs @@ -1,13 +1,17 @@ +use emmylua_parser::{LuaAstNode, LuaExpr, LuaLocalStat}; + use crate::{ compilation::analyzer::lua::analyze_return_point, db_index::{DbIndex, LuaMemberOwner, LuaType}, semantic::{infer_expr, LuaInferCache}, - LuaSemanticDeclId, SignatureReturnStatus, + InFiled, InferFailReason, LuaDeclId, LuaMember, LuaMemberId, LuaMemberKey, LuaSemanticDeclId, + SignatureReturnStatus, }; use super::{ check_reason::check_reach_reason, merge_decl_expr_type, merge_member_type, UnResolveDecl, UnResolveIterVar, UnResolveMember, UnResolveModule, UnResolveModuleRef, UnResolveReturn, + UnResolveTableField, }; pub fn try_resolve_decl( @@ -107,6 +111,85 @@ pub fn try_resolve_member( Some(true) } +pub fn try_resolve_table_field( + db: &mut DbIndex, + cache: &mut LuaInferCache, + unresolve_table_field: &mut UnResolveTableField, +) -> Option { + if !check_reach_reason(db, cache, &unresolve_table_field.reason).unwrap_or(false) { + return None; + } + if !matches!( + unresolve_table_field.reason, + InferFailReason::UnResolveExpr(_) + ) { + return None; + } + let field = unresolve_table_field.field.clone(); + let field_key = field.get_field_key()?; + let field_expr = field_key.get_expr()?; + let field_type = match infer_expr(db, cache, field_expr.clone()) { + Ok(t) => t, + Err(reason) => { + unresolve_table_field.reason = reason; + return None; + } + }; + let member_key: LuaMemberKey = match field_type { + LuaType::StringConst(s) => LuaMemberKey::Name((*s).clone()), + LuaType::IntegerConst(i) => LuaMemberKey::Integer(i), + _ => { + if field_type.is_table() { + LuaMemberKey::SyntaxId(field_expr.get_syntax_id()) + } else { + return None; + } + } + }; + let file_id = unresolve_table_field.file_id; + let table_expr = unresolve_table_field.table_expr.clone(); + let owner_id = LuaMemberOwner::Element(InFiled { + file_id, + value: table_expr.get_range(), + }); + + db.get_reference_index_mut().add_index_reference( + member_key.clone(), + file_id, + field.get_syntax_id(), + ); + + let decl_type = field.get_value_expr().map_or(None, |expr| { + let expr_type = infer_expr(db, cache, expr).ok()?; + Some(expr_type) + }); + + let member_id = LuaMemberId::new(field.get_syntax_id(), file_id); + let member = LuaMember::new( + owner_id.clone(), + member_id, + member_key, + unresolve_table_field.decl_feature, + decl_type, + ); + db.get_member_index_mut().add_member(member); + + // 如果是`Def`则更新 + let local_name = table_expr + .get_parent::()? + .get_value_local_name(LuaExpr::TableExpr(table_expr.clone()))?; + let decl_id = LuaDeclId::new(file_id, local_name.get_position()); + let decl_type = db.get_decl_index_mut().get_decl_mut(&decl_id)?.get_type()?; + if let LuaType::Def(id) = decl_type { + let owner = LuaMemberOwner::Type(id.clone()); + db.get_member_index_mut() + .set_member_owner(owner.clone(), member_id.file_id, member_id); + db.get_member_index_mut() + .add_member_to_owner(owner.clone(), member_id); + } + Some(true) +} + pub fn try_resolve_module( db: &mut DbIndex, cache: &mut LuaInferCache, diff --git a/crates/emmylua_code_analysis/src/db_index/member/lua_member.rs b/crates/emmylua_code_analysis/src/db_index/member/lua_member.rs index 99c9b674d..d0e7714db 100644 --- a/crates/emmylua_code_analysis/src/db_index/member/lua_member.rs +++ b/crates/emmylua_code_analysis/src/db_index/member/lua_member.rs @@ -141,6 +141,7 @@ pub enum LuaMemberKey { None, Integer(i64), Name(SmolStr), + SyntaxId(LuaSyntaxId), } impl LuaMemberKey { @@ -156,6 +157,10 @@ impl LuaMemberKey { matches!(self, LuaMemberKey::Integer(_)) } + pub fn is_syntax_id(&self) -> bool { + matches!(self, LuaMemberKey::SyntaxId(_)) + } + pub fn get_name(&self) -> Option<&str> { match self { LuaMemberKey::Name(name) => Some(name.as_ref()), @@ -177,6 +182,7 @@ impl LuaMemberKey { format!("[{}]", i) } LuaMemberKey::None => "".to_string(), + LuaMemberKey::SyntaxId(_) => "".to_string(), } } } @@ -198,6 +204,9 @@ impl Ord for LuaMemberKey { (Integer(_), _) => std::cmp::Ordering::Less, (_, Integer(_)) => std::cmp::Ordering::Greater, (Name(a), Name(b)) => a.cmp(b), + (Name(_), _) => std::cmp::Ordering::Less, + (_, Name(_)) => std::cmp::Ordering::Greater, + (SyntaxId(_), SyntaxId(_)) => std::cmp::Ordering::Equal, } } } diff --git a/crates/emmylua_code_analysis/src/db_index/type/humanize_type.rs b/crates/emmylua_code_analysis/src/db_index/type/humanize_type.rs index 069fc8841..e9092aa0f 100644 --- a/crates/emmylua_code_analysis/src/db_index/type/humanize_type.rs +++ b/crates/emmylua_code_analysis/src/db_index/type/humanize_type.rs @@ -354,6 +354,7 @@ fn humanize_object_type(db: &DbIndex, object: &LuaObjectType, level: RenderLevel LuaMemberKey::Integer(i) => format!("[{}]: {}", i, ty_str), LuaMemberKey::Name(s) => format!("{}: {}", s, ty_str), LuaMemberKey::None => ty_str, + LuaMemberKey::SyntaxId(_) => ty_str, } }) .collect::>() @@ -669,5 +670,6 @@ fn build_table_member_string( LuaMemberKey::Name(name) => format!("{name}{separator}{member_value}"), LuaMemberKey::Integer(i) => format!("[{i}]{separator}{member_value}"), LuaMemberKey::None => member_value, + LuaMemberKey::SyntaxId(_) => member_value, } } diff --git a/crates/emmylua_code_analysis/src/diagnostic/checker/check_field.rs b/crates/emmylua_code_analysis/src/diagnostic/checker/check_field.rs index 857c1e021..0789a35ad 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/checker/check_field.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/checker/check_field.rs @@ -1,8 +1,8 @@ use std::collections::HashSet; -use emmylua_parser::{LuaAst, LuaAstNode, LuaIndexExpr, LuaIndexKey, LuaVarExpr}; +use emmylua_parser::{LuaAst, LuaAstNode, LuaExpr, LuaIndexExpr, LuaIndexKey, LuaVarExpr}; -use crate::{DiagnosticCode, InferFailReason, LuaType, SemanticModel}; +use crate::{DiagnosticCode, InferFailReason, LuaMemberKey, LuaType, SemanticModel}; use super::{humanize_lint_type, Checker, DiagnosticContext}; @@ -169,41 +169,59 @@ fn is_valid_member( (LuaType::Tuple(_), LuaType::Integer | LuaType::IntegerConst(_)) => return Some(()), _ => {} } - // 解决`key`类型为联合名称时的报错(通常是`pairs`返回的`key`) - let mut key_path_set = HashSet::new(); - get_index_key_names(&mut key_path_set, &key_type); - if key_path_set.is_empty() { + let (key_name_set, key_type_set) = get_key_types(&key_type); + if key_name_set.is_empty() && key_type_set.is_empty() { return None; } - let member_path_set: HashSet<_> = semantic_model - .infer_member_infos(prefix_typ)? - .iter() - .map(|info| info.key.to_path()) - .collect(); - - if member_path_set.is_empty() { - return None; - } - if key_path_set.is_subset(&member_path_set) { - return Some(()); + let members = semantic_model.infer_member_infos(prefix_typ)?; + for info in members { + match &info.key { + LuaMemberKey::SyntaxId(syntax_id) => { + let node = syntax_id.to_node_from_root(semantic_model.get_root().syntax())?; + let expr = LuaExpr::cast(node)?; + if let Ok(typ) = semantic_model.infer_expr(expr) { + if key_type_set.contains(&typ) { + return Some(()); + } + } + } + _ => { + let key_name = info.key.to_path(); + if key_name_set.contains(&key_name) { + return Some(()); + } + } + } } None } -fn get_index_key_names(name_set: &mut HashSet, typ: &LuaType) { - match typ { - LuaType::StringConst(name) => { - name_set.insert(name.as_ref().to_string()); - } - LuaType::IntegerConst(i) => { - name_set.insert(format!("[{}]", i)); +fn get_key_types(typ: &LuaType) -> (HashSet, HashSet) { + let mut type_set = HashSet::new(); + let mut name_set = HashSet::new(); + let mut stack = vec![typ.clone()]; + + while let Some(current_type) = stack.pop() { + match ¤t_type { + LuaType::StringConst(name) => { + name_set.insert(name.as_ref().to_string()); + } + LuaType::IntegerConst(i) => { + name_set.insert(format!("[{}]", i)); + } + LuaType::Union(union_typ) => { + for t in union_typ.get_types() { + stack.push(t.clone()); + } + } + _ => { + if current_type.is_table() { + type_set.insert(current_type); + } + } } - LuaType::Union(union_typ) => union_typ - .get_types() - .iter() - .for_each(|typ| get_index_key_names(name_set, typ)), - _ => {} } + (name_set, type_set) } diff --git a/crates/emmylua_code_analysis/src/diagnostic/test/undefined_field_test.rs b/crates/emmylua_code_analysis/src/diagnostic/test/undefined_field_test.rs index 7139c4401..ab0b29270 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/test/undefined_field_test.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/test/undefined_field_test.rs @@ -101,20 +101,20 @@ mod test { fn test_class_key_to_class_key() { let mut ws = VirtualWorkspace::new(); - assert!(!ws.check_code_for( - DiagnosticCode::UndefinedField, - r#" - --- @type table - local FUNS = {} + // assert!(!ws.check_code_for( + // DiagnosticCode::UndefinedField, + // r#" + // --- @type table + // local FUNS = {} - ---@class D10.AAA + // ---@class D10.AAA - ---@type D10.AAA - local Test1 + // ---@type D10.AAA + // local Test1 - local a = FUNS[Test1] - "# - )); + // local a = FUNS[Test1] + // "# + // )); assert!(ws.check_code_for( DiagnosticCode::UndefinedField, @@ -158,4 +158,25 @@ mod test { "# )); } + + #[test] + fn test_index_key_define() { + let mut ws = VirtualWorkspace::new(); + + assert!(ws.check_code_for( + DiagnosticCode::UndefinedField, + r#" + local Flags = { + A = {}, + } + + ---@class (constructor) RefImpl + local a = { + [Flags.A] = true, + } + + print(a[Flags.A]) + "# + )); + } } diff --git a/crates/emmylua_code_analysis/src/semantic/type_check/ref_type.rs b/crates/emmylua_code_analysis/src/semantic/type_check/ref_type.rs index 13868bd9b..a28dd53d0 100644 --- a/crates/emmylua_code_analysis/src/semantic/type_check/ref_type.rs +++ b/crates/emmylua_code_analysis/src/semantic/type_check/ref_type.rs @@ -66,6 +66,7 @@ pub fn check_ref_type_compact( LuaMemberKey::Name(name) => LuaType::DocStringConst(name.clone().into()), LuaMemberKey::Integer(i) => LuaType::IntegerConst(i.clone()), LuaMemberKey::None => continue, + LuaMemberKey::SyntaxId(_) => continue, }; union_types.push(fake_type); diff --git a/crates/emmylua_ls/src/handlers/completion/providers/type_special_provider.rs b/crates/emmylua_ls/src/handlers/completion/providers/type_special_provider.rs index d8cefa419..7bff5eeef 100644 --- a/crates/emmylua_ls/src/handlers/completion/providers/type_special_provider.rs +++ b/crates/emmylua_ls/src/handlers/completion/providers/type_special_provider.rs @@ -90,6 +90,7 @@ fn add_type_ref_completion( LuaMemberKey::Name(str) => to_enum_label(builder, str.as_str()), LuaMemberKey::Integer(i) => i.to_string(), LuaMemberKey::None => continue, + LuaMemberKey::SyntaxId(_) => continue, }; let completion_item = CompletionItem { @@ -476,6 +477,7 @@ fn add_enum_members_completion( LuaMemberKey::Name(str) => format!("{}.{}", variable_name, str.to_string()), LuaMemberKey::Integer(i) => format!("{}[{}]", variable_name, i), LuaMemberKey::None => continue, + LuaMemberKey::SyntaxId(_) => continue, }; let description = format!("{}", type_id.get_name()); diff --git a/crates/emmylua_parser/src/syntax/node/lua/stat.rs b/crates/emmylua_parser/src/syntax/node/lua/stat.rs index 477a0e0ce..c1a43d942 100644 --- a/crates/emmylua_parser/src/syntax/node/lua/stat.rs +++ b/crates/emmylua_parser/src/syntax/node/lua/stat.rs @@ -159,6 +159,20 @@ impl LuaLocalStat { pub fn get_value_exprs(&self) -> LuaAstChildren { self.children() } + + pub fn get_value_local_name(&self, value: LuaExpr) -> Option { + let local_names = self.get_local_name_list(); + let value_exprs = self.get_value_exprs().collect::>(); + + for (i, local_name) in local_names.enumerate() { + if let Some(value_expr) = value_exprs.get(i) { + if value_expr.syntax() == value.syntax() { + return Some(local_name); + } + } + } + None + } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] From d821930cf23b4799fc031999c20cc14fa3cfc307 Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Sun, 30 Mar 2025 14:53:10 +0800 Subject: [PATCH 11/18] fix array #292 --- .../src/diagnostic/checker/check_field.rs | 64 ++++++++++++++----- .../diagnostic/test/undefined_field_test.rs | 15 +++++ .../src/semantic/infer/infer_index.rs | 24 +++++++ 3 files changed, 87 insertions(+), 16 deletions(-) diff --git a/crates/emmylua_code_analysis/src/diagnostic/checker/check_field.rs b/crates/emmylua_code_analysis/src/diagnostic/checker/check_field.rs index 0789a35ad..54a67c1bd 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/checker/check_field.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/checker/check_field.rs @@ -1,6 +1,7 @@ use std::collections::HashSet; use emmylua_parser::{LuaAst, LuaAstNode, LuaExpr, LuaIndexExpr, LuaIndexKey, LuaVarExpr}; +use internment::ArcIntern; use crate::{DiagnosticCode, InferFailReason, LuaMemberKey, LuaType, SemanticModel}; @@ -161,7 +162,12 @@ fn is_valid_member( return None; } }, - _ => return None, + LuaIndexKey::String(name) => LuaType::StringConst(ArcIntern::new(name.get_value().into())), + LuaIndexKey::Integer(i) => LuaType::IntegerConst(i.get_int_value()), + LuaIndexKey::Name(name) => { + LuaType::StringConst(ArcIntern::new(name.get_name_text().into())) + } + LuaIndexKey::Idx(i) => LuaType::IntegerConst(i.clone() as i64), }; // 允许特定类型组合通过 @@ -169,27 +175,33 @@ fn is_valid_member( (LuaType::Tuple(_), LuaType::Integer | LuaType::IntegerConst(_)) => return Some(()), _ => {} } + // 解决`key`类型为联合名称时的报错(通常是`pairs`返回的`key`) let (key_name_set, key_type_set) = get_key_types(&key_type); if key_name_set.is_empty() && key_type_set.is_empty() { return None; } - let members = semantic_model.infer_member_infos(prefix_typ)?; - for info in members { - match &info.key { - LuaMemberKey::SyntaxId(syntax_id) => { - let node = syntax_id.to_node_from_root(semantic_model.get_root().syntax())?; - let expr = LuaExpr::cast(node)?; - if let Ok(typ) = semantic_model.infer_expr(expr) { - if key_type_set.contains(&typ) { - return Some(()); + let prefix_types = get_prefix_types(prefix_typ); + for prefix_type in prefix_types { + if let Some(members) = semantic_model.infer_member_infos(&prefix_type) { + for info in members { + match &info.key { + LuaMemberKey::SyntaxId(syntax_id) => { + let node = + syntax_id.to_node_from_root(semantic_model.get_root().syntax())?; + let expr = LuaExpr::cast(node)?; + if let Ok(typ) = semantic_model.infer_expr(expr) { + if key_type_set.contains(&typ) { + return Some(()); + } + } + } + _ => { + let key_name = info.key.to_path(); + if key_name_set.contains(&key_name) { + return Some(()); + } } - } - } - _ => { - let key_name = info.key.to_path(); - if key_name_set.contains(&key_name) { - return Some(()); } } } @@ -198,6 +210,26 @@ fn is_valid_member( None } +fn get_prefix_types(prefix_typ: &LuaType) -> HashSet { + let mut type_set = HashSet::new(); + let mut stack = vec![prefix_typ.clone()]; + + while let Some(current_type) = stack.pop() { + match ¤t_type { + LuaType::Union(union_typ) => { + for t in union_typ.get_types() { + stack.push(t.clone()); + } + } + LuaType::Any | LuaType::Unknown | LuaType::Nil => {} + _ => { + type_set.insert(current_type.clone()); + } + } + } + type_set +} + fn get_key_types(typ: &LuaType) -> (HashSet, HashSet) { let mut type_set = HashSet::new(); let mut name_set = HashSet::new(); diff --git a/crates/emmylua_code_analysis/src/diagnostic/test/undefined_field_test.rs b/crates/emmylua_code_analysis/src/diagnostic/test/undefined_field_test.rs index ab0b29270..1f37c158d 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/test/undefined_field_test.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/test/undefined_field_test.rs @@ -179,4 +179,19 @@ mod test { "# )); } + + #[test] + fn test_issue_292() { + let mut ws = VirtualWorkspace::new(); + + assert!(ws.check_code_for( + DiagnosticCode::UndefinedField, + r#" + --- @type {head:string}[]? + local b + ---@diagnostic disable-next-line: need-check-nil + _ = b[1].head == 'b' + "# + )); + } } diff --git a/crates/emmylua_code_analysis/src/semantic/infer/infer_index.rs b/crates/emmylua_code_analysis/src/semantic/infer/infer_index.rs index c7c68d1ff..993a68984 100644 --- a/crates/emmylua_code_analysis/src/semantic/infer/infer_index.rs +++ b/crates/emmylua_code_analysis/src/semantic/infer/infer_index.rs @@ -142,6 +142,30 @@ pub fn infer_member_by_member_key( LuaType::Global => infer_global_field_member(db, cache, index_expr), LuaType::Instance(inst) => infer_instance_member(db, cache, inst, index_expr, infer_guard), LuaType::Namespace(ns) => infer_namespace_member(db, cache, ns, index_expr), + LuaType::Array(array_type) => infer_array_member(db, cache, array_type, index_expr), + _ => Err(InferFailReason::FieldDotFound), + } +} + +fn infer_array_member( + db: &DbIndex, + cache: &mut LuaInferCache, + array_type: &LuaType, + index_expr: LuaIndexMemberExpr, +) -> Result { + let key = index_expr.get_index_key().ok_or(InferFailReason::None)?; + match key { + LuaIndexKey::Integer(_) => { + return Ok(array_type.clone()); + } + LuaIndexKey::Expr(expr) => { + let expr_type = infer_expr(db, cache, expr.clone())?; + if expr_type.is_integer() { + return Ok(array_type.clone()); + } else { + return Err(InferFailReason::FieldDotFound); + } + } _ => Err(InferFailReason::FieldDotFound), } } From 3c8553e3e50ef9fe480f823ac78d251efc8beaf2 Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Sun, 30 Mar 2025 14:55:41 +0800 Subject: [PATCH 12/18] clean code --- .../src/diagnostic/test/duplicate_require_test.rs | 1 - .../emmylua_code_analysis/src/semantic/type_check/func_type.rs | 3 --- 2 files changed, 4 deletions(-) diff --git a/crates/emmylua_code_analysis/src/diagnostic/test/duplicate_require_test.rs b/crates/emmylua_code_analysis/src/diagnostic/test/duplicate_require_test.rs index 8988fc64e..d369e71fa 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/test/duplicate_require_test.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/test/duplicate_require_test.rs @@ -34,7 +34,6 @@ mod tests { #[test] fn test_field() { let mut ws = VirtualWorkspace::new(); - // 作用域不同 assert!(ws.check_code_for( DiagnosticCode::DuplicateRequire, r#" diff --git a/crates/emmylua_code_analysis/src/semantic/type_check/func_type.rs b/crates/emmylua_code_analysis/src/semantic/type_check/func_type.rs index 883bc6e62..9e4ff35f7 100644 --- a/crates/emmylua_code_analysis/src/semantic/type_check/func_type.rs +++ b/crates/emmylua_code_analysis/src/semantic/type_check/func_type.rs @@ -91,9 +91,6 @@ fn check_doc_func_type_compact_for_params( match (source_param_type, compact_param_type) { (Some(source_type), Some(compact_type)) => { - if source_type.is_self_infer() && compact_type.is_self_infer() { - continue; - } if check_general_type_compact( db, source_type, From ead217d83027c2c9d1ac036f19a41fb490665e95 Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Sun, 30 Mar 2025 22:07:44 +0800 Subject: [PATCH 13/18] fix analyzer call_expr args has table #296 --- .../src/compilation/analyzer/lua/stats.rs | 32 ++++++++++++++++++- .../diagnostic/test/missing_fields_test.rs | 25 +++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/lua/stats.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/lua/stats.rs index 40a06315b..52122b14c 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/lua/stats.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/lua/stats.rs @@ -44,8 +44,24 @@ pub fn analyze_local_stat(analyzer: &mut LuaAnalyzer, local_stat: LuaLocalStat) if let LuaType::MuliReturn(multi) = expr_type { expr_type = multi.get_type(0)?.clone(); } - let decl_id = LuaDeclId::new(analyzer.file_id, position); + // 当`call`参数包含表时, 表可能未被分析, 需要延迟 + if let LuaType::Instance(instance) = &expr_type { + if instance.get_base().is_unknown() { + if call_expr_has_effect_table_arg(expr).is_some() { + let unresolve = UnResolveDecl { + file_id: analyzer.file_id, + decl_id, + expr: expr.clone(), + ret_idx: 0, + reason: InferFailReason::UnResolveExpr(expr.clone()), + }; + analyzer.add_unresolved(unresolve.into()); + continue; + } + } + } + merge_decl_expr_type(analyzer.db, &mut analyzer.infer_cache, decl_id, expr_type); } Err(InferFailReason::None) => { @@ -134,6 +150,20 @@ pub fn analyze_local_stat(analyzer: &mut LuaAnalyzer, local_stat: LuaLocalStat) Some(()) } +fn call_expr_has_effect_table_arg(expr: &LuaExpr) -> Option<()> { + if let LuaExpr::CallExpr(call_expr) = expr { + let args_list = call_expr.get_args_list()?; + for arg in args_list.get_args() { + if let LuaExpr::TableExpr(table_expr) = arg { + if !table_expr.is_empty() { + return Some(()); + } + } + } + } + None +} + #[derive(Debug, Clone)] enum TypeOwner { Decl(LuaDeclId), diff --git a/crates/emmylua_code_analysis/src/diagnostic/test/missing_fields_test.rs b/crates/emmylua_code_analysis/src/diagnostic/test/missing_fields_test.rs index bc5e0b1c8..4caadeebd 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/test/missing_fields_test.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/test/missing_fields_test.rs @@ -163,4 +163,29 @@ foo({}) "# )); } + + #[test] + fn test_issue_296() { + let mut ws = VirtualWorkspace::new(); + assert!(!ws.check_code_for( + DiagnosticCode::UndefinedField, + r#" + ---@generic T + ---@param table table + ---@param metatable {__index: T} + ---@return T + local function abc(table, metatable) end + + ---@class B + local B + + --- @return B + function newB() + local self = abc({}, { __index = B }) + self:notmethod() + return self + end + "# + )); + } } From 523ddc8f054dd8788482095bc98e7f1194fc4d53 Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Sun, 30 Mar 2025 22:25:56 +0800 Subject: [PATCH 14/18] fix #285 --- .../compilation/analyzer/flow/var_analyze.rs | 6 +++--- .../test/assign_type_mismatch_test.rs | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/flow/var_analyze.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/flow/var_analyze.rs index 1a5a8b16c..37a001c3d 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/flow/var_analyze.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/flow/var_analyze.rs @@ -61,7 +61,7 @@ pub fn analyze_ref_assign( } let (var_exprs, value_exprs) = assign_stat.get_var_and_expr_list(); - let index = var_exprs + let var_index = var_exprs .iter() .position(|it| it.get_position() == var_expr.get_position())?; @@ -69,12 +69,12 @@ pub fn analyze_ref_assign( return None; } - let (value_expr, idx) = if let Some(expr) = value_exprs.get(index) { + let (value_expr, idx) = if let Some(expr) = value_exprs.get(var_index) { (expr.clone(), 0) } else { ( value_exprs.last()?.clone(), - (index - value_exprs.len()) as i32, + (var_index - (value_exprs.len() - 1)) as i32, ) }; diff --git a/crates/emmylua_code_analysis/src/diagnostic/test/assign_type_mismatch_test.rs b/crates/emmylua_code_analysis/src/diagnostic/test/assign_type_mismatch_test.rs index 97d4b7d94..5388cb1c1 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/test/assign_type_mismatch_test.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/test/assign_type_mismatch_test.rs @@ -658,4 +658,22 @@ return t "# )); } + + #[test] + fn test_issue_285() { + let mut ws = VirtualWorkspace::new(); + assert!(ws.check_code_for( + DiagnosticCode::AssignTypeMismatch, + r#" + --- @return string, integer + local function foo() end + + local text, err + text, err = foo() + + ---@type integer + local b = err + "# + )); + } } From 5e295b4caaeeea7144b54a822355a44360269245 Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Mon, 31 Mar 2025 13:21:17 +0800 Subject: [PATCH 15/18] add test #287 --- .../diagnostic/test/param_type_check_test.rs | 19 +++++++++++++++++++ .../src/semantic/infer/infer_name.rs | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/crates/emmylua_code_analysis/src/diagnostic/test/param_type_check_test.rs b/crates/emmylua_code_analysis/src/diagnostic/test/param_type_check_test.rs index d7a7fc6b0..93a86e130 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/test/param_type_check_test.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/test/param_type_check_test.rs @@ -492,4 +492,23 @@ mod test { "# )); } + + #[test] + fn test_issue_287() { + let mut ws = VirtualWorkspace::new(); + assert!(ws.check_code_for( + DiagnosticCode::ParamTypeNotMatch, + r#" + ---@param a table + local function f(a) + end + + ---@type table? + local a + a = a or {} + + f(a) + "# + )); + } } diff --git a/crates/emmylua_code_analysis/src/semantic/infer/infer_name.rs b/crates/emmylua_code_analysis/src/semantic/infer/infer_name.rs index 26d320a1a..184c0c6fb 100644 --- a/crates/emmylua_code_analysis/src/semantic/infer/infer_name.rs +++ b/crates/emmylua_code_analysis/src/semantic/infer/infer_name.rs @@ -38,7 +38,7 @@ pub fn infer_name_expr( let root = name_expr.get_root(); if let Some(flow_chain) = flow_chain { let mut position = name_expr.get_position(); - // 如果是赋值语句, 那么我们使用赋值语句的结束位置来获取类型 + // 如果是赋值语句, 那么我们使用赋值语句的结束位置来获取类型, 应用于`hover`左值 if let Some(assign_stat) = name_expr.get_parent::() { position = assign_stat.get_range().end(); } From 76b113b0049b86fc359badd6ccb34693e26f5f42 Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Mon, 31 Mar 2025 13:31:47 +0800 Subject: [PATCH 16/18] fix #302 --- .../diagnostic/test/missing_fields_test.rs | 26 +++++++++++++++++++ .../src/semantic/infer/infer_table.rs | 11 +++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/crates/emmylua_code_analysis/src/diagnostic/test/missing_fields_test.rs b/crates/emmylua_code_analysis/src/diagnostic/test/missing_fields_test.rs index 4caadeebd..0d80395c4 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/test/missing_fields_test.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/test/missing_fields_test.rs @@ -188,4 +188,30 @@ foo({}) "# )); } + + #[test] + fn test_issue_302() { + let mut ws = VirtualWorkspace::new(); + assert!(ws.check_code_for( + DiagnosticCode::MissingFields, + r#" + ---@class data + data = {} + data.raw = {} + data.is_demo = false + + --- @param _self data + function data.extend(_self, _otherdata) + -- Impl + end + + data:extend({ + { + type = "item", + name = "my-item", + }, + }) + "# + )); + } } diff --git a/crates/emmylua_code_analysis/src/semantic/infer/infer_table.rs b/crates/emmylua_code_analysis/src/semantic/infer/infer_table.rs index 6878913b2..d207e8865 100644 --- a/crates/emmylua_code_analysis/src/semantic/infer/infer_table.rs +++ b/crates/emmylua_code_analysis/src/semantic/infer/infer_table.rs @@ -200,13 +200,22 @@ fn infer_table_type_by_calleee( None, )?; let param_types = func_type.get_params(); - let call_arg_number = call_arg_list + let mut call_arg_number = call_arg_list .children::() .into_iter() .enumerate() .find(|(_, arg)| arg.get_position() == table_expr.get_position()) .ok_or(InferFailReason::None)? .0; + match (func_type.is_colon_define(), call_expr.is_colon_call()) { + (true, true) | (false, false) => {} + (false, true) => { + call_arg_number += 1; + } + (true, false) => { + call_arg_number -= 1; + } + } Ok(param_types .get(call_arg_number) .ok_or(InferFailReason::None)? From 6f2e7ac367e55c81dadcf6ebe338b80d29b15871 Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Mon, 31 Mar 2025 14:36:16 +0800 Subject: [PATCH 17/18] reassign type allow `nil` --- .../emmylua_code_analysis/src/db_index/type/types.rs | 8 ++++++++ .../src/semantic/infer/infer_index.rs | 11 ++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/crates/emmylua_code_analysis/src/db_index/type/types.rs b/crates/emmylua_code_analysis/src/db_index/type/types.rs index ec03dd830..f0366c30e 100644 --- a/crates/emmylua_code_analysis/src/db_index/type/types.rs +++ b/crates/emmylua_code_analysis/src/db_index/type/types.rs @@ -400,6 +400,14 @@ impl LuaType { pub fn is_member_owner(&self) -> bool { matches!(self, LuaType::Ref(_) | LuaType::TableConst(_)) } + + pub fn has_nil(&self) -> bool { + match self { + LuaType::Nil => true, + LuaType::Union(u) => u.types.iter().any(|t| t.has_nil()), + _ => false, + } + } } #[derive(Debug, Clone, Hash, PartialEq, Eq)] diff --git a/crates/emmylua_code_analysis/src/semantic/infer/infer_index.rs b/crates/emmylua_code_analysis/src/semantic/infer/infer_index.rs index 993a68984..a2c63e4b0 100644 --- a/crates/emmylua_code_analysis/src/semantic/infer/infer_index.rs +++ b/crates/emmylua_code_analysis/src/semantic/infer/infer_index.rs @@ -97,12 +97,17 @@ fn infer_member_type_pass_flow( let root = index_expr.get_root(); if let Some(path) = index_expr.get_access_path() { for type_assert in flow_chain.get_type_asserts(&path, index_expr.get_position(), None) { + let new_type = type_assert + .tighten_type(db, cache, &root, member_type.clone()) + .unwrap_or(LuaType::Unknown); if type_assert.is_reassign() && !allow_reassign { + // 允许仅去除 nil + if member_type.has_nil() && !new_type.is_nil() { + member_type = new_type; + } continue; } - member_type = type_assert - .tighten_type(db, cache, &root, member_type) - .unwrap_or(LuaType::Unknown); + member_type = new_type; } } } From 3b7c498a790b7898ad6e8e40cb8a9e877a7c2ff4 Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Mon, 31 Mar 2025 14:56:57 +0800 Subject: [PATCH 18/18] fix some --- .../src/compilation/analyzer/unresolve/resolve.rs | 2 +- crates/emmylua_code_analysis/src/db_index/type/types.rs | 8 -------- .../src/semantic/infer/infer_index.rs | 2 +- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/resolve.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/resolve.rs index 7366ec20d..9559c7814 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/resolve.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/resolve.rs @@ -123,7 +123,7 @@ pub fn try_resolve_table_field( unresolve_table_field.reason, InferFailReason::UnResolveExpr(_) ) { - return None; + return Some(true); } let field = unresolve_table_field.field.clone(); let field_key = field.get_field_key()?; diff --git a/crates/emmylua_code_analysis/src/db_index/type/types.rs b/crates/emmylua_code_analysis/src/db_index/type/types.rs index f0366c30e..ec03dd830 100644 --- a/crates/emmylua_code_analysis/src/db_index/type/types.rs +++ b/crates/emmylua_code_analysis/src/db_index/type/types.rs @@ -400,14 +400,6 @@ impl LuaType { pub fn is_member_owner(&self) -> bool { matches!(self, LuaType::Ref(_) | LuaType::TableConst(_)) } - - pub fn has_nil(&self) -> bool { - match self { - LuaType::Nil => true, - LuaType::Union(u) => u.types.iter().any(|t| t.has_nil()), - _ => false, - } - } } #[derive(Debug, Clone, Hash, PartialEq, Eq)] diff --git a/crates/emmylua_code_analysis/src/semantic/infer/infer_index.rs b/crates/emmylua_code_analysis/src/semantic/infer/infer_index.rs index a2c63e4b0..e7862cd0f 100644 --- a/crates/emmylua_code_analysis/src/semantic/infer/infer_index.rs +++ b/crates/emmylua_code_analysis/src/semantic/infer/infer_index.rs @@ -102,7 +102,7 @@ fn infer_member_type_pass_flow( .unwrap_or(LuaType::Unknown); if type_assert.is_reassign() && !allow_reassign { // 允许仅去除 nil - if member_type.has_nil() && !new_type.is_nil() { + if member_type.is_nullable() && !new_type.is_nullable() { member_type = new_type; } continue;