From 8d1e6556887403b59e350b6a3798bb78b4cd5de6 Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Tue, 25 Mar 2025 14:08:43 +0800 Subject: [PATCH 01/22] update tpl_pattern_match --- .../resources/std/global.lua | 2 +- .../diagnostic/test/undefined_field_test.rs | 23 +++++++++ .../generic/instantiate_type_generic.rs | 2 +- .../src/semantic/generic/tpl_pattern.rs | 47 ++++++++++++++++++- 4 files changed, 71 insertions(+), 3 deletions(-) diff --git a/crates/emmylua_code_analysis/resources/std/global.lua b/crates/emmylua_code_analysis/resources/std/global.lua index 80cb0c06b..0e414642c 100644 --- a/crates/emmylua_code_analysis/resources/std/global.lua +++ b/crates/emmylua_code_analysis/resources/std/global.lua @@ -114,7 +114,7 @@ function getmetatable(object) end --- will iterate over the key–value pairs (1,`t[1]`), (2,`t[2]`), ..., up to --- the first absent index. ---@generic V ----@param t V[] | table +---@param t V[] | table | {[any]: V} ---@return fun(tbl: any):int, std.NotNull function ipairs(t) end 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 4418f63f5..25600bb55 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 @@ -2,6 +2,29 @@ mod test { use crate::{DiagnosticCode, VirtualWorkspace}; + #[test] + fn test_1() { + let mut ws = VirtualWorkspace::new(); + assert!(ws.check_code_for( + DiagnosticCode::UndefinedField, + r#" + ---@alias std.NotNull T - ? + + ---@generic V + ---@param t {[any]: V} + ---@return fun(tbl: any):int, std.NotNull + function ipairs(t) end + + ---@type {[integer]: string|table} + local a = {} + + for i, extendsName in ipairs(a) do + print(extendsName.a) + end + "# + )); + } + #[test] fn test() { let mut ws = VirtualWorkspace::new(); diff --git a/crates/emmylua_code_analysis/src/semantic/generic/instantiate_type_generic.rs b/crates/emmylua_code_analysis/src/semantic/generic/instantiate_type_generic.rs index ec7f17cbc..4e50cbb78 100644 --- a/crates/emmylua_code_analysis/src/semantic/generic/instantiate_type_generic.rs +++ b/crates/emmylua_code_analysis/src/semantic/generic/instantiate_type_generic.rs @@ -363,7 +363,7 @@ fn instantiate_alias_call( if operands.len() != 2 { return LuaType::Unknown; } - + // 如果类型为`Union`且只有一个类型, 则会解开`Union`包装 return TypeOps::Remove.apply(&operands[0], &operands[1]); } LuaAliasCallKind::Add => { diff --git a/crates/emmylua_code_analysis/src/semantic/generic/tpl_pattern.rs b/crates/emmylua_code_analysis/src/semantic/generic/tpl_pattern.rs index af8b77075..7ec87b53b 100644 --- a/crates/emmylua_code_analysis/src/semantic/generic/tpl_pattern.rs +++ b/crates/emmylua_code_analysis/src/semantic/generic/tpl_pattern.rs @@ -1,12 +1,15 @@ use std::ops::Deref; use emmylua_parser::LuaSyntaxNode; +use itertools::Itertools; use smol_str::SmolStr; use crate::{ + check_type_compact, db_index::{DbIndex, LuaGenericType, LuaType}, semantic::{member::infer_member_map, LuaInferCache}, - LuaFunctionType, LuaMemberKey, LuaMemberOwner, LuaMultiReturn, LuaTupleType, LuaUnionType, + LuaFunctionType, LuaMemberKey, LuaMemberOwner, LuaMultiReturn, LuaObjectType, LuaTupleType, + LuaUnionType, }; use super::type_substitutor::TypeSubstitutor; @@ -158,12 +161,50 @@ fn tpl_pattern_match( LuaType::Tuple(tuple) => { tuple_tpl_pattern_match(db, cache, root, tuple, target, substitutor); } + LuaType::Object(obj) => { + object_tpl_pattern_match(db, cache, root, obj, target, substitutor); + } _ => {} } Some(()) } +fn object_tpl_pattern_match( + db: &DbIndex, + cache: &mut LuaInferCache, + root: &LuaSyntaxNode, + origin_obj: &LuaObjectType, + target: &LuaType, + substitutor: &mut TypeSubstitutor, +) -> Option<()> { + match target { + LuaType::Object(target_object) => { + // 先匹配 fields + for (k, v) in origin_obj.get_fields().iter().sorted_by_key(|(k, _)| *k) { + let target_value = target_object.get_fields().get(k); + if let Some(target_value) = target_value { + tpl_pattern_match(db, cache, root, v, target_value, substitutor); + } + } + // 再匹配索引访问 + let target_index_access = target_object.get_index_access(); + for (origin_key, v) in origin_obj.get_index_access() { + // 先匹配 key 类型进行转换 + let target_access = target_index_access + .iter() + .find(|(target_key, _)| check_type_compact(db, origin_key, target_key).is_ok()); + if let Some(target_access) = target_access { + tpl_pattern_match(db, cache, root, origin_key, &target_access.0, substitutor); + tpl_pattern_match(db, cache, root, v, &target_access.1, substitutor); + } + } + } + _ => {} + } + Some(()) +} + fn array_tpl_pattern_match( db: &DbIndex, cache: &mut LuaInferCache, @@ -306,6 +347,10 @@ fn table_generic_tpl_pattern_match( }; values.push(v.clone()); } + for (k, v) in obj.get_index_access() { + keys.push(k.clone()); + values.push(v.clone()); + } let key_type = LuaType::Union(LuaUnionType::new(keys).into()); let value_type = LuaType::Union(LuaUnionType::new(values).into()); From 8562f4100a2e7bfa44fae70586a4ac0655e24f99 Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Tue, 25 Mar 2025 15:11:16 +0800 Subject: [PATCH 02/22] =?UTF-8?q?=E5=BD=93=E5=89=8D=E6=89=93=E5=BC=80?= =?UTF-8?q?=E7=9A=84=E6=96=87=E4=BB=B6=E5=88=97=E8=A1=A8=E4=B8=8D=E8=A7=A6?= =?UTF-8?q?=E5=8F=91=20watched=5Ffiles?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../emmylua_ls/src/context/workspace_manager.rs | 4 ++++ .../text_document/text_document_handler.rs | 13 +++++++++++-- .../text_document/watched_file_handler.rs | 15 +++++++++------ 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/crates/emmylua_ls/src/context/workspace_manager.rs b/crates/emmylua_ls/src/context/workspace_manager.rs index 8317a6fa6..1887b7a4c 100644 --- a/crates/emmylua_ls/src/context/workspace_manager.rs +++ b/crates/emmylua_ls/src/context/workspace_manager.rs @@ -1,3 +1,4 @@ +use std::collections::HashSet; use std::{path::PathBuf, sync::Arc, time::Duration}; use super::{ClientProxy, FileDiagnostic, StatusBar}; @@ -5,6 +6,7 @@ use crate::handlers::{init_analysis, ClientConfig}; use emmylua_code_analysis::update_code_style; use emmylua_code_analysis::{load_configs, EmmyLuaAnalysis, Emmyrc}; use log::{debug, info}; +use lsp_types::Uri; use tokio::sync::{Mutex, RwLock}; use tokio_util::sync::CancellationToken; @@ -17,6 +19,7 @@ pub struct WorkspaceManager { pub client_config: ClientConfig, pub workspace_folders: Vec, pub watcher: Option, + pub current_open_files: HashSet, } impl WorkspaceManager { @@ -35,6 +38,7 @@ impl WorkspaceManager { update_token: Arc::new(Mutex::new(None)), file_diagnostic, watcher: None, + current_open_files: HashSet::new(), } } diff --git a/crates/emmylua_ls/src/handlers/text_document/text_document_handler.rs b/crates/emmylua_ls/src/handlers/text_document/text_document_handler.rs index e89cd5146..4aeffc773 100644 --- a/crates/emmylua_ls/src/handlers/text_document/text_document_handler.rs +++ b/crates/emmylua_ls/src/handlers/text_document/text_document_handler.rs @@ -25,6 +25,10 @@ pub async fn on_did_open_text_document( .await; } + let mut workspace = context.workspace_manager.write().await; + workspace.current_open_files.insert(uri); + drop(workspace); + Some(()) } @@ -77,8 +81,13 @@ pub async fn on_did_change_text_document( } pub async fn on_did_close_document( - _: ServerContextSnapshot, - _: DidCloseTextDocumentParams, + context: ServerContextSnapshot, + params: DidCloseTextDocumentParams, ) -> Option<()> { + let mut workspace = context.workspace_manager.write().await; + workspace + .current_open_files + .remove(¶ms.text_document.uri); + drop(workspace); Some(()) } diff --git a/crates/emmylua_ls/src/handlers/text_document/watched_file_handler.rs b/crates/emmylua_ls/src/handlers/text_document/watched_file_handler.rs index 5e812abe2..ee9393ecd 100644 --- a/crates/emmylua_ls/src/handlers/text_document/watched_file_handler.rs +++ b/crates/emmylua_ls/src/handlers/text_document/watched_file_handler.rs @@ -7,6 +7,7 @@ pub async fn on_did_change_watched_files( context: ServerContextSnapshot, params: DidChangeWatchedFilesParams, ) -> Option<()> { + let workspace = context.workspace_manager.read().await; let mut analysis = context.analysis.write().await; let emmyrc = analysis.get_emmyrc(); let encoding = &emmyrc.workspace.encoding; @@ -17,12 +18,14 @@ pub async fn on_did_change_watched_files( let file_type = get_file_type(&file_event.uri); match file_type { Some(WatchedFileType::Lua) => { - collect_lua_files( - &mut watched_lua_files, - file_event.uri, - file_event.typ, - encoding, - ); + if !workspace.current_open_files.contains(&file_event.uri) { + collect_lua_files( + &mut watched_lua_files, + file_event.uri, + file_event.typ, + encoding, + ); + } } Some(WatchedFileType::Editorconfig) => { if file_event.typ == FileChangeType::DELETED { From ceab5e0f56f06baa0a5eab85457cb22d5c11b8d4 Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Tue, 25 Mar 2025 15:31:34 +0800 Subject: [PATCH 03/22] diagnostic: fix RedundantParameter --- .../diagnostic/checker/check_param_count.rs | 36 ++++++++++++++++--- .../test/redundant_parameter_test.rs | 16 +++++++++ 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/crates/emmylua_code_analysis/src/diagnostic/checker/check_param_count.rs b/crates/emmylua_code_analysis/src/diagnostic/checker/check_param_count.rs index 734ff27b7..906fd750b 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/checker/check_param_count.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/checker/check_param_count.rs @@ -45,14 +45,18 @@ fn check_closure_expr( semantic_model.get_file_id(), &closure_expr, ))?; - let source_params_len = match source_typ { - LuaType::DocFunction(func_type) => func_type.get_params().len(), + let source_params_len = match &source_typ { + LuaType::DocFunction(func_type) => { + let params = func_type.get_params(); + get_params_len(params) + } LuaType::Signature(signature_id) => { let signature = context.db.get_signature_index().get(&signature_id)?; - signature.get_type_params().len() + let params = signature.get_type_params(); + get_params_len(¶ms) } _ => return Some(()), - }; + }?; // 只检查右值参数多于左值参数的情况, 右值参数少于左值参数的情况是能够接受的 if source_params_len > right_value.params.len() { @@ -166,7 +170,14 @@ fn check_call_expr( // Check for redundant parameters else if call_args_count > params.len() { // 参数定义中最后一个参数是 `...` - if params.last().map_or(false, |(name, _)| name == "...") { + if params.last().map_or(false, |(name, typ)| { + name == "..." + || if let Some(typ) = typ { + typ.is_variadic() + } else { + false + } + }) { return Some(()); } @@ -198,3 +209,18 @@ fn check_call_expr( Some(()) } + +fn get_params_len(params: &[(String, Option)]) -> Option { + if let Some((name, typ)) = params.last() { + // 如果最后一个参数是可变参数, 则直接返回, 不需要检查 + if name == "..." { + return None; + } + if let Some(typ) = typ { + if typ.is_variadic() { + return None; + } + } + } + Some(params.len()) +} diff --git a/crates/emmylua_code_analysis/src/diagnostic/test/redundant_parameter_test.rs b/crates/emmylua_code_analysis/src/diagnostic/test/redundant_parameter_test.rs index 2c25872d7..7f507d422 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/test/redundant_parameter_test.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/test/redundant_parameter_test.rs @@ -49,6 +49,22 @@ mod test { )); } + #[test] + fn test_1() { + let mut ws = VirtualWorkspace::new(); + + assert!(ws.check_code_for( + DiagnosticCode::RedundantParameter, + r#" + ---@type fun(...)[] + local a = {} + + a[1] = function(ccc, ...) + end + "# + )); + } + #[test] fn test_dots() { let mut ws = VirtualWorkspace::new(); From a9b6fc8e15b759b7dbff9a12cc7acb6f1489656f Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Tue, 25 Mar 2025 16:36:32 +0800 Subject: [PATCH 04/22] diagnostic: update UndefinedField --- .../src/diagnostic/checker/check_field.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) 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 3ff93a133..4e9558233 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/checker/check_field.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/checker/check_field.rs @@ -92,3 +92,22 @@ fn check_index_expr( Some(()) } + +#[allow(dead_code)] +fn is_valid_prefix_type(typ: &LuaType) -> bool { + let mut current_typ = typ; + loop { + match current_typ { + LuaType::Any + | LuaType::Unknown + | LuaType::Table + | LuaType::TplRef(_) + | LuaType::StrTplRef(_) + | LuaType::TableConst(_) => return false, + LuaType::Instance(instance_typ) => { + current_typ = instance_typ.get_base(); + } + _ => return true, + } + } +} From b17d16c898efb53ea8c2eb3d716cf0698eca6a04 Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Tue, 25 Mar 2025 18:39:21 +0800 Subject: [PATCH 05/22] diagnostic: update signature match algorithm --- .../diagnostic/test/missing_parameter_test.rs | 19 +++ .../src/semantic/overload_resolve/mod.rs | 109 +++++++++++++++--- 2 files changed, 114 insertions(+), 14 deletions(-) diff --git a/crates/emmylua_code_analysis/src/diagnostic/test/missing_parameter_test.rs b/crates/emmylua_code_analysis/src/diagnostic/test/missing_parameter_test.rs index 7a359949f..d2bba5834 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/test/missing_parameter_test.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/test/missing_parameter_test.rs @@ -2,6 +2,25 @@ mod test { use crate::{DiagnosticCode, VirtualWorkspace}; + #[test] + fn test_issue_276() { + let mut ws = VirtualWorkspace::new(); + + assert!(ws.check_code_for( + DiagnosticCode::MissingParameter, + r#" + --- @param a string + --- @param b? string + --- @param c? string + --- @return string + --- @overload fun(a: string, b: string): number + local function myfun2(a, b, c) end + + local a = myfun2('string') + "# + )); + } + #[test] fn test_issue_249() { let mut ws = VirtualWorkspace::new(); diff --git a/crates/emmylua_code_analysis/src/semantic/overload_resolve/mod.rs b/crates/emmylua_code_analysis/src/semantic/overload_resolve/mod.rs index 89da7399d..6e82589b4 100644 --- a/crates/emmylua_code_analysis/src/semantic/overload_resolve/mod.rs +++ b/crates/emmylua_code_analysis/src/semantic/overload_resolve/mod.rs @@ -1,4 +1,4 @@ -use std::{cmp::Ordering, sync::Arc}; +use std::sync::Arc; use emmylua_parser::LuaCallExpr; @@ -69,7 +69,7 @@ fn resolve_signature_by_args( ) -> InferCallFuncResult { let arg_count = arg_count.unwrap_or(0); let mut opt_funcs = Vec::with_capacity(overloads.len()); - // 函数本身签名在尾部 + for func in overloads { let params = func.get_params(); if params.len() < arg_count { @@ -81,36 +81,117 @@ fn resolve_signature_by_args( } else { 0 }; - let mut match_count = 0; + let mut total_weight = 0; // 总权重 let expr_len = expr_types.len(); + // 检查每个参数的匹配情况 for (i, param) in params.iter().enumerate() { if i == 0 && jump_param > 0 { - continue; + continue; // 处理冒号调用时的偏移 } + let param_type = param.1.as_ref().unwrap_or(&LuaType::Any); let expr_idx = i - jump_param; + if expr_idx >= expr_len { - break; + // 没有传入参数, 但参数是可空类型 + if param_type.is_nullable() { + total_weight += 1; + } + continue; } - let param_type = param.1.as_ref().unwrap_or(&LuaType::Any); let expr_type = &expr_types[expr_idx]; if *param_type == LuaType::Any || check_type_compact(db, param_type, expr_type).is_ok() { - match_count += 1; + total_weight += 100; // 类型完全匹配 } } - opt_funcs.push((func, params.len(), match_count)); + // 如果参数数量完全匹配, 则认为其权重更高 + if total_weight > 0 && params.len() == expr_types.len() { + total_weight += 50000; + } + + opt_funcs.push((func, total_weight)); } - // 优先降序匹配`match_count`使匹配度最高的函数排在前面, 其次升序匹配`params.len()`使参数个数最少的函数排在前面. - opt_funcs.sort_by(|a, b| match b.2.cmp(&a.2) { - Ordering::Equal => a.1.cmp(&b.1), - other => other, - }); + // 按权重降序排序 + opt_funcs.sort_by(|a, b| b.1.cmp(&a.1)); + + // 返回权重最高的签名,若无则取最后一个重载作为默认 opt_funcs .first() - .map(|(func, _, _)| Arc::clone(func)) + .filter(|(_, weight)| *weight > i32::MIN) // 确保不是无效签名 + .map(|(func, _)| Arc::clone(func)) .or_else(|| overloads.last().cloned()) .ok_or(InferFailReason::None) } + +// fn resolve_signature_by_args( +// db: &DbIndex, +// overloads: &[Arc], +// expr_types: &[LuaType], +// is_colon_call: bool, +// arg_count: Option, +// ) -> Option> { +// let arg_count = arg_count.unwrap_or(0); +// let mut opt_funcs = Vec::with_capacity(overloads.len()); +// // 函数本身签名在尾部 +// for func in overloads { +// let params = func.get_params(); +// if params.len() < arg_count { +// continue; +// } + +// let jump_param = if is_colon_call && !func.is_colon_define() { +// 1 +// } else { +// 0 +// }; +// let mut match_count = 0; +// let mut skip_param = 0; +// let expr_len = expr_types.len(); + +// for (i, param) in params.iter().enumerate() { +// if i == 0 && jump_param > 0 { +// continue; +// } +// let param_type = param.1.as_ref().unwrap_or(&LuaType::Any); +// let expr_idx = i - jump_param; +// if expr_idx >= expr_len { +// if param_type.is_nullable() { +// skip_param += 1; +// } +// continue; +// } + +// let expr_type = &expr_types[expr_idx]; +// if *param_type == LuaType::Any || check_type_compact(db, param_type, expr_type).is_ok() +// { +// match_count += 1; +// } +// } +// opt_funcs.push((func, params.len(), match_count, skip_param)); +// } + +// opt_funcs.sort_by(|a, b| { +// match b.2.cmp(&a.2) { +// // 比较 match_count 降序 +// Ordering::Equal => { +// // 计算有效参数个数 +// let a_effective_params = a.1 - a.3; // params.len() - skip_param +// let b_effective_params = b.1 - b.3; +// // 升序使有效参数个数最少的函数排在前面 +// match a_effective_params.cmp(&b_effective_params) { +// Ordering::Equal => a.1.cmp(&b.1), // 升序使总参数个数最少的函数排在前面 +// other => other, +// } +// } +// other => other, +// } +// }); + +// opt_funcs +// .first() +// .map(|(func, _, _, _)| Arc::clone(func)) +// .or_else(|| overloads.last().cloned()) +// } From 25f63357ce37e3af7b8150bd3e241b9ac55bdf2f Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Tue, 25 Mar 2025 22:45:56 +0800 Subject: [PATCH 06/22] table field -> FileDefine --- .../src/compilation/analyzer/decl/exprs.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 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 fbe9c4133..1b32e110b 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/decl/exprs.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/decl/exprs.rs @@ -202,9 +202,9 @@ pub fn analyze_table_expr(analyzer: &mut DeclAnalyzer, expr: LuaTableExpr) -> Op ); let decl_feature = if analyzer.is_meta { - LuaMemberFeature::MetaFieldDecl + LuaMemberFeature::MetaDefine } else { - LuaMemberFeature::FileFieldDecl + LuaMemberFeature::FileDefine }; let member_id = LuaMemberId::new(field.get_syntax_id(), file_id); From c8410481850080d928d1a9aa1758ebe8d458d0cd Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Wed, 26 Mar 2025 00:29:20 +0800 Subject: [PATCH 07/22] flow fix elseif --- .../compilation/analyzer/flow/var_analyze.rs | 39 ++++++++++++++----- .../src/compilation/test/flow.rs | 22 +++++++++++ 2 files changed, 51 insertions(+), 10 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 8ca50194c..1c3f4f6cb 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 @@ -132,16 +132,35 @@ fn broadcast_up( if let Some(ne_type_assert) = type_assert.get_negation() { if let Some(else_stat) = if_stat.get_else_clause() { let block_range = else_stat.get_range(); - flow_chain.add_type_assert(path, ne_type_assert, block_range, actual_range); - } else if is_block_has_return(if_stat.get_block()?).unwrap_or(false) { + flow_chain.add_type_assert( + path, + ne_type_assert.clone(), + block_range, + actual_range, + ); + } else if is_block_has_return(if_stat.get_block()).unwrap_or(false) { let parent_block = if_stat.get_parent::()?; let parent_range = parent_block.get_range(); let if_range = if_stat.get_range(); if if_range.end() < parent_range.end() { let range = TextRange::new(if_range.end(), parent_range.end()); - flow_chain.add_type_assert(path, ne_type_assert, range, actual_range); + flow_chain.add_type_assert( + path, + ne_type_assert.clone(), + range, + actual_range, + ); } } + for else_if_clause in if_stat.get_else_if_clause_list() { + let block_range = else_if_clause.get_range(); + flow_chain.add_type_assert( + path, + ne_type_assert.clone(), + block_range, + actual_range, + ); + } } } LuaAst::LuaWhileStat(while_stat) => { @@ -445,10 +464,12 @@ fn infer_lua_type_assert( Some(()) } -fn is_block_has_return(block: LuaBlock) -> Option { - for stat in block.get_stats() { - if is_stat_change_flow(stat.clone()).unwrap_or(false) { - return Some(true); +fn is_block_has_return(block: Option) -> Option { + if let Some(block) = block { + for stat in block.get_stats() { + if is_stat_change_flow(stat.clone()).unwrap_or(false) { + return Some(true); + } } } @@ -469,9 +490,7 @@ fn is_stat_change_flow(stat: LuaStat) -> Option { Some(false) } LuaStat::ReturnStat(_) => Some(true), - LuaStat::DoStat(do_stat) => { - Some(is_block_has_return(do_stat.get_block()?).unwrap_or(false)) - } + LuaStat::DoStat(do_stat) => Some(is_block_has_return(do_stat.get_block()).unwrap_or(false)), _ => Some(false), } } diff --git a/crates/emmylua_code_analysis/src/compilation/test/flow.rs b/crates/emmylua_code_analysis/src/compilation/test/flow.rs index e2e7a1c7a..8c359d72f 100644 --- a/crates/emmylua_code_analysis/src/compilation/test/flow.rs +++ b/crates/emmylua_code_analysis/src/compilation/test/flow.rs @@ -278,4 +278,26 @@ print(a.field) "# )); } + + #[test] + fn test_elseif() { + let mut ws = VirtualWorkspace::new(); + + assert!(ws.check_code_for( + DiagnosticCode::NeedCheckNil, + r#" +---@class D11 +---@field public a string + +---@type D11|nil +local a + +if not a then +elseif a.a then + print(a.a) +end + + "# + )); + } } From 9d472dd4e051db52a297f7e134ad8d6cee693030 Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Wed, 26 Mar 2025 02:22:16 +0800 Subject: [PATCH 08/22] optimize infer_union --- .../test/return_type_mismatch_test.rs | 18 ++++++++++ .../src/semantic/infer/infer_binary.rs | 36 ++++++++++++++----- 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/crates/emmylua_code_analysis/src/diagnostic/test/return_type_mismatch_test.rs b/crates/emmylua_code_analysis/src/diagnostic/test/return_type_mismatch_test.rs index 2b7dc708e..753f55975 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/test/return_type_mismatch_test.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/test/return_type_mismatch_test.rs @@ -225,4 +225,22 @@ mod tests { "# )); } + + #[test] + fn test_1() { + let mut ws = VirtualWorkspace::new(); + + assert!(ws.check_code_for( + DiagnosticCode::ReturnTypeMismatch, + r#" + ---@return string? + local function a() + ---@type int? + local ccc + return ccc and a() or nil + end + + "# + )); + } } 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 e2b735235..b0c574a2b 100644 --- a/crates/emmylua_code_analysis/src/semantic/infer/infer_binary.rs +++ b/crates/emmylua_code_analysis/src/semantic/infer/infer_binary.rs @@ -1,3 +1,5 @@ +use std::collections::HashSet; + use emmylua_parser::{BinaryOperator, LuaBinaryExpr}; use smol_str::SmolStr; @@ -59,17 +61,35 @@ fn infer_binary_expr_type( } fn infer_union(db: &DbIndex, u: &LuaUnionType, right: &LuaType, op: BinaryOperator) -> InferResult { - let mut union_types = vec![]; + let mut unique_union_types = HashSet::new(); + for ty in u.get_types() { - let ty = infer_binary_expr_type(db, ty.clone(), right.clone(), op)?; - union_types.push(ty); + let inferred_ty = infer_binary_expr_type(db, ty.clone(), right.clone(), op)?; + flatten_and_insert(inferred_ty, &mut unique_union_types); } - union_types.dedup(); - match union_types.len() { - 0 => Ok(LuaType::Unknown), - 1 => Ok(union_types[0].clone()), - _ => Ok(LuaType::Union(LuaUnionType::new(union_types).into())), + match unique_union_types.len() { + 0 => Some(LuaType::Unknown), + 1 => Some(unique_union_types.into_iter().next().unwrap()), + _ => Some(LuaType::Union( + LuaUnionType::new(unique_union_types.into_iter().collect()).into(), + )), + } +} + +fn flatten_and_insert(ty: LuaType, unique_union_types: &mut HashSet) { + let mut stack = vec![ty]; + while let Some(current_ty) = stack.pop() { + match current_ty { + LuaType::Union(u) => { + for inner_ty in u.get_types() { + stack.push(inner_ty.clone()); + } + } + _ => { + unique_union_types.insert(current_ty); + } + } } } From 040fb348abe15885ac09946239164db3b9ebb512 Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Wed, 26 Mar 2025 02:38:59 +0800 Subject: [PATCH 09/22] update type_check --- .../test/return_type_mismatch_test.rs | 28 +++++++++++++++---- .../src/semantic/type_check/complex_type.rs | 18 ++++++++++++ 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/crates/emmylua_code_analysis/src/diagnostic/test/return_type_mismatch_test.rs b/crates/emmylua_code_analysis/src/diagnostic/test/return_type_mismatch_test.rs index 753f55975..18bf2ed90 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/test/return_type_mismatch_test.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/test/return_type_mismatch_test.rs @@ -233,13 +233,29 @@ mod tests { assert!(ws.check_code_for( DiagnosticCode::ReturnTypeMismatch, r#" - ---@return string? - local function a() - ---@type int? - local ccc - return ccc and a() or nil - end + ---@return string? + local function a() + ---@type int? + local ccc + return ccc and a() or nil + end + "# + )); + } + + #[test] + fn test_2() { + let mut ws = VirtualWorkspace::new(); + assert!(ws.check_code_for( + DiagnosticCode::ReturnTypeMismatch, + r#" + ---@return any[] + local function a() + ---@type table|table + local ccc + return ccc + end "# )); } diff --git a/crates/emmylua_code_analysis/src/semantic/type_check/complex_type.rs b/crates/emmylua_code_analysis/src/semantic/type_check/complex_type.rs index 4b2b308e7..2291758e2 100644 --- a/crates/emmylua_code_analysis/src/semantic/type_check/complex_type.rs +++ b/crates/emmylua_code_analysis/src/semantic/type_check/complex_type.rs @@ -62,6 +62,24 @@ pub fn check_complex_type_compact( ); } LuaType::Table => return Ok(()), + LuaType::TableGeneric(compact_types) => { + if compact_types.len() == 2 { + for typ in compact_types.iter() { + if check_general_type_compact( + db, + source_base, + typ, + check_guard.next_level()?, + ) + .is_err() + { + return Err(TypeCheckFailReason::TypeNotMatch); + } + } + + return Ok(()); + } + } _ => {} }, LuaType::Tuple(tuple) => { From 46f15c281839521c117960d63c1513910a9ef3bb Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Wed, 26 Mar 2025 02:45:10 +0800 Subject: [PATCH 10/22] fix test --- .../src/semantic/infer/infer_binary.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) 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 b0c574a2b..6585fda0c 100644 --- a/crates/emmylua_code_analysis/src/semantic/infer/infer_binary.rs +++ b/crates/emmylua_code_analysis/src/semantic/infer/infer_binary.rs @@ -1,5 +1,3 @@ -use std::collections::HashSet; - use emmylua_parser::{BinaryOperator, LuaBinaryExpr}; use smol_str::SmolStr; @@ -61,7 +59,7 @@ fn infer_binary_expr_type( } fn infer_union(db: &DbIndex, u: &LuaUnionType, right: &LuaType, op: BinaryOperator) -> InferResult { - let mut unique_union_types = HashSet::new(); + 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)?; @@ -71,13 +69,11 @@ fn infer_union(db: &DbIndex, u: &LuaUnionType, right: &LuaType, op: BinaryOperat match unique_union_types.len() { 0 => Some(LuaType::Unknown), 1 => Some(unique_union_types.into_iter().next().unwrap()), - _ => Some(LuaType::Union( - LuaUnionType::new(unique_union_types.into_iter().collect()).into(), - )), + _ => Some(LuaType::Union(LuaUnionType::new(unique_union_types).into())), } } -fn flatten_and_insert(ty: LuaType, unique_union_types: &mut HashSet) { +fn flatten_and_insert(ty: LuaType, unique_union_types: &mut Vec) { let mut stack = vec![ty]; while let Some(current_ty) = stack.pop() { match current_ty { @@ -87,7 +83,9 @@ fn flatten_and_insert(ty: LuaType, unique_union_types: &mut HashSet) { } } _ => { - unique_union_types.insert(current_ty); + if !unique_union_types.contains(¤t_ty) { + unique_union_types.push(current_ty); + } } } } From 4a7f91d8e4132448f7e9feb399bf4aab75e5bfa3 Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Wed, 26 Mar 2025 11:55:36 +0800 Subject: [PATCH 11/22] std update --- .../resources/std/global.lua | 3 +-- .../resources/std/string.lua | 22 ++++++++++--------- .../resources/std/table.lua | 2 +- .../resources/std/utf8.lua | 14 +++++++----- 4 files changed, 22 insertions(+), 19 deletions(-) diff --git a/crates/emmylua_code_analysis/resources/std/global.lua b/crates/emmylua_code_analysis/resources/std/global.lua index 0e414642c..462add43d 100644 --- a/crates/emmylua_code_analysis/resources/std/global.lua +++ b/crates/emmylua_code_analysis/resources/std/global.lua @@ -87,8 +87,7 @@ function dofile(filename) end --- `error` function was called. Level 2 points the error to where the function --- that called `error` was called; and so on. Passing a level 0 avoids the --- addition of error position information to the message. ----@overload fun(message:string) ----@param message string +---@param message any ---@param level? integer function error(message, level) end diff --git a/crates/emmylua_code_analysis/resources/std/string.lua b/crates/emmylua_code_analysis/resources/std/string.lua index b16ae06d9..0e3d8b1a7 100644 --- a/crates/emmylua_code_analysis/resources/std/string.lua +++ b/crates/emmylua_code_analysis/resources/std/string.lua @@ -75,12 +75,14 @@ function string.dump(func, strip) end --- --- If the pattern has captures, then in a successful match the captured values --- are also returned, after the two indices. ----@overload fun(s:string, pattern:string):integer, integer, string... ----@param s string ----@param pattern string ----@param init? integer ----@param plain? boolean ----@return integer, integer, string... +---@param s string|number +---@param pattern string|number +---@param init? integer +---@param plain? boolean +---@return integer|nil start +---@return integer|nil end +---@return any|nil ... captured +---@nodiscard function string.find(s, pattern, init, plain) end --- @@ -276,11 +278,11 @@ function string.reverse(s) end --- corrected to 1. If `j` is greater than the string length, it is corrected to --- that length. If, after these corrections, `i` is greater than `j`, the --- function returns the empty string. ----@overload fun(s:string, i:integer):string ----@param s string ----@param i integer ----@param j integer +---@param s string|number +---@param i integer +---@param j? integer ---@return string +---@nodiscard function string.sub(s, i, j) end ---@version >5.3 diff --git a/crates/emmylua_code_analysis/resources/std/table.lua b/crates/emmylua_code_analysis/resources/std/table.lua index f3567178b..10969fa7e 100644 --- a/crates/emmylua_code_analysis/resources/std/table.lua +++ b/crates/emmylua_code_analysis/resources/std/table.lua @@ -109,9 +109,9 @@ function table.sort(list, comp) end --- return `list[i]`, `list[i+1]`, `···`, `list[j]` --- By default, i is 1 and j is #list. ---@generic T +---@param list [T...] | table ---@param i? integer ---@param j? integer ----@param list [T...] ---@return T... function table.unpack(list, i, j) end diff --git a/crates/emmylua_code_analysis/resources/std/utf8.lua b/crates/emmylua_code_analysis/resources/std/utf8.lua index d55a4f524..83f620150 100644 --- a/crates/emmylua_code_analysis/resources/std/utf8.lua +++ b/crates/emmylua_code_analysis/resources/std/utf8.lua @@ -60,12 +60,14 @@ function utf8.codepoint(s, i, j) end --- positions `i` and `j` (both inclusive). The default for `i` is 1 and for --- `j` is -1. If it finds any invalid byte sequence, returns a false value --- plus the position of the first invalid byte. ----@overload fun(s:string):number ----@param s string ----@param i? number ----@param j? number ----@return number -function utf8.len(s, i, j) end +---@param s string +---@param i? integer +---@param j? integer +---@param lax? boolean +---@return integer? +---@return integer? errpos +---@nodiscard +function utf8.len(s, i, j, lax) end --- --- Returns the position (in bytes) where the encoding of the `n`-th character From 4a9dca6f26ebca27b8f5e45d3af36bccbe1f5067 Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Wed, 26 Mar 2025 12:48:26 +0800 Subject: [PATCH 12/22] fix bug --- .../src/diagnostic/checker/missing_fields.rs | 2 +- .../src/semantic/overload_resolve/mod.rs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/emmylua_code_analysis/src/diagnostic/checker/missing_fields.rs b/crates/emmylua_code_analysis/src/diagnostic/checker/missing_fields.rs index 5c0bbdad0..1a3174091 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/checker/missing_fields.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/checker/missing_fields.rs @@ -149,7 +149,7 @@ fn record_required_fields( return; } - if decl_type.is_nullable() { + if decl_type.is_nullable() || decl_type.is_any() { optional_type.insert(name); return; } diff --git a/crates/emmylua_code_analysis/src/semantic/overload_resolve/mod.rs b/crates/emmylua_code_analysis/src/semantic/overload_resolve/mod.rs index 6e82589b4..35a0b7660 100644 --- a/crates/emmylua_code_analysis/src/semantic/overload_resolve/mod.rs +++ b/crates/emmylua_code_analysis/src/semantic/overload_resolve/mod.rs @@ -83,11 +83,12 @@ fn resolve_signature_by_args( }; let mut total_weight = 0; // 总权重 let expr_len = expr_types.len(); - // 检查每个参数的匹配情况 for (i, param) in params.iter().enumerate() { if i == 0 && jump_param > 0 { - continue; // 处理冒号调用时的偏移 + // 非冒号定义但是冒号调用, 直接认为匹配 + total_weight += 100; + continue; } let param_type = param.1.as_ref().unwrap_or(&LuaType::Any); let expr_idx = i - jump_param; From 324ae80eb156fee53c6c3f34ccea46ae5669e43a Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Wed, 26 Mar 2025 13:38:03 +0800 Subject: [PATCH 13/22] diagnostic: fix #262 --- crates/emmylua_code_analysis/locales/lint.yml | 8 ++-- .../src/db_index/type/types.rs | 8 ++++ .../diagnostic/checker/param_type_check.rs | 45 ++++++++----------- .../diagnostic/test/missing_fields_test.rs | 17 +++++++ .../src/semantic/type_check/ref_type.rs | 2 +- 5 files changed, 49 insertions(+), 31 deletions(-) diff --git a/crates/emmylua_code_analysis/locales/lint.yml b/crates/emmylua_code_analysis/locales/lint.yml index ccca63fec..cc63d42e8 100644 --- a/crates/emmylua_code_analysis/locales/lint.yml +++ b/crates/emmylua_code_analysis/locales/lint.yml @@ -68,10 +68,10 @@ Should not reassign to iter variable: zh_CN: '不应重新赋值给迭代变量' zh_HK: '不應重新指定迭代變數' -expected `%{source}` but found `%{found}`: - en: expected `%{source}` but found `%{found}` - zh_CN: '预期 `%{source}`,但得到 `%{found}`' - zh_HK: '期望 `%{source}`,但得到 `%{found}`' +expected `%{source}` but found `%{found}`. %{reason}: + en: expected `%{source}` but found `%{found}`. %{reason} + zh_CN: '预期 `%{source}`,但得到 `%{found}`。 %{reason}' + zh_HK: '期望 `%{source}`,但得到 `%{found}`。 %{reason}' function %{name} may be nil: en: function %{name} may be nil zh_CN: '函数 %{name} 可能为 nil' 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 00d71c03a..c190d5010 100644 --- a/crates/emmylua_code_analysis/src/db_index/type/types.rs +++ b/crates/emmylua_code_analysis/src/db_index/type/types.rs @@ -262,6 +262,14 @@ impl LuaType { } } + pub fn is_optional(&self) -> bool { + match self { + LuaType::Nil | LuaType::Any | LuaType::Unknown => true, + LuaType::Union(u) => u.types.iter().any(|t| t.is_optional()), + _ => false, + } + } + pub fn is_always_truthy(&self) -> bool { match self { LuaType::Nil | LuaType::Boolean | LuaType::Any | LuaType::Unknown => false, diff --git a/crates/emmylua_code_analysis/src/diagnostic/checker/param_type_check.rs b/crates/emmylua_code_analysis/src/diagnostic/checker/param_type_check.rs index a73a28333..7ff2b8a59 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/checker/param_type_check.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/checker/param_type_check.rs @@ -138,32 +138,25 @@ fn add_type_check_diagnostic( let db = semantic_model.get_db(); match result { Ok(_) => return, - Err(reason) => match reason { - TypeCheckFailReason::TypeNotMatchWithReason(reason) => { - context.add_diagnostic(DiagnosticCode::ParamTypeNotMatch, range, reason, None); - } - TypeCheckFailReason::TypeNotMatch => { - context.add_diagnostic( - DiagnosticCode::ParamTypeNotMatch, - range, - t!( - "expected `%{source}` but found `%{found}`", - source = humanize_type(db, ¶m_type, RenderLevel::Simple), - found = humanize_type(db, &expr_type, RenderLevel::Simple) - ) - .to_string(), - None, - ); - } - TypeCheckFailReason::TypeRecursion => { - context.add_diagnostic( - DiagnosticCode::ParamTypeNotMatch, - range, - "type recursion".into(), - None, - ); - } - }, + Err(reason) => { + let reason_message = match reason { + TypeCheckFailReason::TypeNotMatchWithReason(reason) => reason, + TypeCheckFailReason::TypeNotMatch => "".to_string(), + TypeCheckFailReason::TypeRecursion => "type recursion".to_string(), + }; + context.add_diagnostic( + DiagnosticCode::ParamTypeNotMatch, + range, + t!( + "expected `%{source}` but found `%{found}`. %{reason}", + source = humanize_type(db, ¶m_type, RenderLevel::Simple), + found = humanize_type(db, &expr_type, RenderLevel::Simple), + reason = reason_message + ) + .to_string(), + None, + ); + } } } 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 696ba319e..e44ad09d9 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 @@ -132,4 +132,21 @@ mod tests { "# )); } + + #[test] + fn test_issue_262() { + let mut ws = VirtualWorkspace::new(); + assert!(ws.check_code_for( + DiagnosticCode::MissingFields, + r#" +--- @class D11.Opts +--- @field field? any + +--- @param opts D11.Opts +local function foo(opts) end + +foo({}) + "# + )); + } } 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 3350c4cee..77a057e35 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 @@ -170,7 +170,7 @@ fn check_ref_type_compact_table( .to_string(), )); } - } else if source_member_type.is_nullable() { + } else if source_member_type.is_optional() { continue; } else { return Err(TypeCheckFailReason::TypeNotMatchWithReason( From 961b223c48f78477e5c09d1a1c407af1bb5deaa5 Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Wed, 26 Mar 2025 13:57:20 +0800 Subject: [PATCH 14/22] diagnostic: typecheck function --- .../diagnostic/test/param_type_check_test.rs | 18 ++++++++++++++++++ .../src/semantic/type_check/func_type.rs | 1 + 2 files changed, 19 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 216f220ab..c973c51fa 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 @@ -329,4 +329,22 @@ mod test { "# )); } + + #[test] + fn test_function() { + let mut ws = VirtualWorkspace::new_with_init_std_lib(); + + assert!(ws.check_code_for( + DiagnosticCode::ParamTypeNotMatch, + r#" + ---@param sorter function + ---@return string[] + local function getTableKeys(sorter) + local keys = {} + table.sort(keys, sorter) + return keys + 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 4f49c928a..a65c7cb26 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 @@ -42,6 +42,7 @@ pub fn check_doc_func_type_compact( Ok(()) } + LuaType::Function => Ok(()), _ => Err(TypeCheckFailReason::TypeNotMatch), } } From 8b5af11f755ead0f81254ed314a4c177f9917fd6 Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Wed, 26 Mar 2025 14:19:45 +0800 Subject: [PATCH 15/22] update type_check --- .../resources/std/global.lua | 2 +- .../diagnostic/test/param_type_check_test.rs | 22 +++++++++++++++++++ .../src/semantic/type_check/complex_type.rs | 2 +- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/crates/emmylua_code_analysis/resources/std/global.lua b/crates/emmylua_code_analysis/resources/std/global.lua index 462add43d..3a2f9a85f 100644 --- a/crates/emmylua_code_analysis/resources/std/global.lua +++ b/crates/emmylua_code_analysis/resources/std/global.lua @@ -231,7 +231,7 @@ function next(table, index) end --- See function `next` for the caveats of modifying the table during its --- traversal. ---@generic K, V ----@param t table +---@param t table | V[] ---@return fun(tbl: any):K, std.NotNull function pairs(t) end --- 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 c973c51fa..7c7811263 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 @@ -347,4 +347,26 @@ mod test { "# )); } + + #[test] + fn test_table_array() { + let mut ws = VirtualWorkspace::new_with_init_std_lib(); + + assert!(ws.check_code_for( + DiagnosticCode::ParamTypeNotMatch, + r#" + ---@generic K, V + ---@param t table + ---@return table + local function revertMap(t) + end + + ---@param arr any[] + local function sortCallbackOfIndex(arr) + ---@type table + local indexMap = revertMap(arr) + end + "# + )); + } } diff --git a/crates/emmylua_code_analysis/src/semantic/type_check/complex_type.rs b/crates/emmylua_code_analysis/src/semantic/type_check/complex_type.rs index 2291758e2..6b9bf844c 100644 --- a/crates/emmylua_code_analysis/src/semantic/type_check/complex_type.rs +++ b/crates/emmylua_code_analysis/src/semantic/type_check/complex_type.rs @@ -211,7 +211,7 @@ pub fn check_complex_type_compact( if source_generic_param.len() == 2 { let key = &source_generic_param[0]; let value = &source_generic_param[1]; - if key.is_any() && check_type_compact(db, value, base).is_ok() { + if key.is_any() || key.is_integer() && check_type_compact(db, value, base).is_ok() { return Ok(()); } } From 3a73dad3c8fc06001d0b678f2663cbafb979bcec Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Wed, 26 Mar 2025 15:56:28 +0800 Subject: [PATCH 16/22] some update --- .../resources/std/debug.lua | 7 +++- .../diagnostic/test/param_type_check_test.rs | 40 +++++++++++++++++++ .../src/semantic/type_check/complex_type.rs | 5 ++- .../src/semantic/type_check/simple_type.rs | 1 + 4 files changed, 50 insertions(+), 3 deletions(-) diff --git a/crates/emmylua_code_analysis/resources/std/debug.lua b/crates/emmylua_code_analysis/resources/std/debug.lua index 6f56c8237..871a1678c 100644 --- a/crates/emmylua_code_analysis/resources/std/debug.lua +++ b/crates/emmylua_code_analysis/resources/std/debug.lua @@ -146,9 +146,11 @@ function debug.getregistry() end --- --- Variable names starting with '(' (open parenthesis) represent variables with --- no known names (variables from chunks saved without debug information). ----@param f integer +---@param f async fun(...):any... ---@param up integer ----@return table +---@return string name +---@return any value +---@nodiscard function debug.getupvalue(f, up) end --- @@ -236,6 +238,7 @@ function debug.setlocal(thread, level, var, value) end ---@param value T ---@param meta? table ---@return T value +---@overload fun(value: table, meta: T): T function debug.setmetatable(value, meta) end --- 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 7c7811263..510a935d0 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 @@ -369,4 +369,44 @@ mod test { "# )); } + + #[test] + fn test_table_class() { + let mut ws = VirtualWorkspace::new_with_init_std_lib(); + + assert!(ws.check_code_for( + DiagnosticCode::ParamTypeNotMatch, + r#" + ---@param t table + local function bar(t) + end + + ---@class D11.A + + ---@type D11.A|any + local a + + bar(a) + "# + )); + } + + #[test] + fn test_table_1() { + let mut ws = VirtualWorkspace::new_with_init_std_lib(); + + assert!(ws.check_code_for( + DiagnosticCode::ParamTypeNotMatch, + r#" + ---@param t table[] + local function bar(t) + end + + ---@type table|any + local a + + bar(a) + "# + )); + } } diff --git a/crates/emmylua_code_analysis/src/semantic/type_check/complex_type.rs b/crates/emmylua_code_analysis/src/semantic/type_check/complex_type.rs index 6b9bf844c..eb578b65d 100644 --- a/crates/emmylua_code_analysis/src/semantic/type_check/complex_type.rs +++ b/crates/emmylua_code_analysis/src/semantic/type_check/complex_type.rs @@ -80,6 +80,7 @@ pub fn check_complex_type_compact( return Ok(()); } } + LuaType::Any => return Ok(()), _ => {} }, LuaType::Tuple(tuple) => { @@ -211,7 +212,9 @@ pub fn check_complex_type_compact( if source_generic_param.len() == 2 { let key = &source_generic_param[0]; let value = &source_generic_param[1]; - if key.is_any() || key.is_integer() && check_type_compact(db, value, base).is_ok() { + if key.is_any() + || key.is_integer() && check_type_compact(db, value, base).is_ok() + { return Ok(()); } } diff --git a/crates/emmylua_code_analysis/src/semantic/type_check/simple_type.rs b/crates/emmylua_code_analysis/src/semantic/type_check/simple_type.rs index bd18500c1..6f3ed7c31 100644 --- a/crates/emmylua_code_analysis/src/semantic/type_check/simple_type.rs +++ b/crates/emmylua_code_analysis/src/semantic/type_check/simple_type.rs @@ -32,6 +32,7 @@ pub fn check_simple_type_compact( | LuaType::Global | LuaType::Userdata | LuaType::Instance(_) + | LuaType::Any ) { return Ok(()); } From c2163a620b5e4bab62eda49a129f20ce075654ff Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Wed, 26 Mar 2025 19:36:46 +0800 Subject: [PATCH 17/22] =?UTF-8?q?completion:=20local=E5=90=8E=E7=9A=84?= =?UTF-8?q?=E7=A9=BA=E6=A0=BCfunction=E8=A1=A5=E5=85=A8=E5=BA=94=E5=8F=AA?= =?UTF-8?q?=E5=85=81=E8=AE=B8=E4=B8=BB=E5=8A=A8=E8=A7=A6=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/handlers/completion/completion_builder.rs | 5 +++++ .../src/handlers/completion/providers/keywords_provider.rs | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/crates/emmylua_ls/src/handlers/completion/completion_builder.rs b/crates/emmylua_ls/src/handlers/completion/completion_builder.rs index 06581ca73..29d5493ff 100644 --- a/crates/emmylua_ls/src/handlers/completion/completion_builder.rs +++ b/crates/emmylua_ls/src/handlers/completion/completion_builder.rs @@ -70,4 +70,9 @@ impl<'a> CompletionBuilder<'a> { } self.env_range = (0, 0); } + + /// 是否是触发主动补全 + pub fn is_invoked(&self) -> bool { + self.trigger_kind == CompletionTriggerKind::INVOKED + } } diff --git a/crates/emmylua_ls/src/handlers/completion/providers/keywords_provider.rs b/crates/emmylua_ls/src/handlers/completion/providers/keywords_provider.rs index 1197a9e46..58dd8ec8a 100644 --- a/crates/emmylua_ls/src/handlers/completion/providers/keywords_provider.rs +++ b/crates/emmylua_ls/src/handlers/completion/providers/keywords_provider.rs @@ -11,6 +11,7 @@ pub fn add_completion(builder: &mut CompletionBuilder) -> Option<()> { if builder.is_cancelled() { return None; } + if is_full_match_keyword(builder).is_some() { add_stat_keyword_completions(builder, None); return Some(()); @@ -122,12 +123,17 @@ fn add_expr_keyword_completions(builder: &mut CompletionBuilder) -> Option<()> { } fn add_function_keyword_completions(builder: &mut CompletionBuilder) -> Option<()> { + // 非主动补全不添加 + if !builder.is_invoked() { + return None; + } let item = CompletionItem { label: "function".to_string(), kind: Some(lsp_types::CompletionItemKind::SNIPPET), insert_text: Some("function ${1:name}(${2:...})\n\t${0}\nend".to_string()), insert_text_format: Some(InsertTextFormat::SNIPPET), insert_text_mode: Some(InsertTextMode::ADJUST_INDENTATION), + sort_text: Some("0000".to_string()), // 优先级较高 ..CompletionItem::default() }; From a8202e4f4658e6c21b4006f3cd75542a3c332fd8 Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Wed, 26 Mar 2025 19:50:22 +0800 Subject: [PATCH 18/22] update std pairs --- .../resources/std/global.lua | 2 +- .../diagnostic/test/param_type_check_test.rs | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/crates/emmylua_code_analysis/resources/std/global.lua b/crates/emmylua_code_analysis/resources/std/global.lua index 3a2f9a85f..bb52db5df 100644 --- a/crates/emmylua_code_analysis/resources/std/global.lua +++ b/crates/emmylua_code_analysis/resources/std/global.lua @@ -231,7 +231,7 @@ function next(table, index) end --- See function `next` for the caveats of modifying the table during its --- traversal. ---@generic K, V ----@param t table | V[] +---@param t table | V[] | {[K]: V} ---@return fun(tbl: any):K, std.NotNull function pairs(t) end --- 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 510a935d0..f9e84b1bc 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 @@ -409,4 +409,28 @@ mod test { "# )); } + + #[test] + fn test_pairs() { + let mut ws = VirtualWorkspace::new_with_init_std_lib(); + + assert!(ws.check_code_for( + DiagnosticCode::ParamTypeNotMatch, + r#" + ---@diagnostic disable: missing-return + ---@generic K, V + ---@param t table | V[] | {[K]: V} + ---@return fun(tbl: any):K, std.NotNull + local function _pairs(t) end + + ---@class D10.A + + ---@type {[string]: D10.A, _id: D10.A} + local a + + for k, v in _pairs(a) do + end + "# + )); + } } From 54dc160bb6053e2145374e34e3058bf2897b61f8 Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Wed, 26 Mar 2025 20:16:20 +0800 Subject: [PATCH 19/22] diagnostic: fix AssignTypeMismatch --- .../test/assign_type_mismatch_test.rs | 22 +++++++++++++++++++ .../src/semantic/infer/infer_binary.rs | 5 +++++ 2 files changed, 27 insertions(+) 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 f5d3af5cb..fb3e06745 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 @@ -42,6 +42,28 @@ mod tests { )); } + #[test] + fn test_3() { + let mut ws = VirtualWorkspace::new(); + assert!(ws.check_code_for_namespace( + DiagnosticCode::AssignTypeMismatch, + r#" + ---@param s string + ---@param i? integer + ---@param j? integer + ---@param lax? boolean + ---@return integer? + ---@return integer? errpos + ---@nodiscard + local function get_len(s, i, j, lax) end + + local len = 0 + ---@diagnostic disable-next-line: need-check-nil + len = len + get_len("", 1, 1, true) + "# + )); + } + #[test] fn test_issue_193() { let mut ws = VirtualWorkspace::new(); 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 6585fda0c..179ac60df 100644 --- a/crates/emmylua_code_analysis/src/semantic/infer/infer_binary.rs +++ b/crates/emmylua_code_analysis/src/semantic/infer/infer_binary.rs @@ -166,6 +166,11 @@ fn infer_binary_expr_add(db: &DbIndex, left: LuaType, right: LuaType) -> InferRe } }; } + match (left.is_nil(), right.is_nil()) { + (true, false) => return Some(right), + (false, true) => return Some(left), + _ => {} + } infer_binary_custom_operator(db, &left, &right, LuaOperatorMetaMethod::Add) } From f2d6a989020b5e6737f477b3f5786b92df10133b Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Thu, 27 Mar 2025 06:53:47 +0800 Subject: [PATCH 20/22] std update --- .../resources/std/io.lua | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/crates/emmylua_code_analysis/resources/std/io.lua b/crates/emmylua_code_analysis/resources/std/io.lua index 602b5f9ef..9d1c1c403 100644 --- a/crates/emmylua_code_analysis/resources/std/io.lua +++ b/crates/emmylua_code_analysis/resources/std/io.lua @@ -94,13 +94,25 @@ function io.output(file) end ---@return file function io.popen(prog, mode) end +---@alias std.readmode +---| integer +---| string +---| "n" # Reads a number, returning a float or integer based on Lua's conversion grammar. +---| "a" # Reads the entire file starting from the current position. +---| "l" # Reads a line and ignores the end-of-line marker. +---| "L" # Reads a line and preserves the end-of-line marker. +---| "*n" # Reads a number, returning a float or integer based on Lua's conversion grammar. +---| "*a" # Reads the entire file starting from the current position. +---| "*l" # Reads a line and ignores the end-of-line marker. +---| "*L" # Reads a line and preserves the end-of-line marker. + --- --- Equivalent to `io.input():read(···)`. ---- @param format '*n' | '*a' | '*l' | integer ---- @return string | integer | nil ---- @overload fun(format:'*n'): integer ---- @overload fun(format:'*a' | '*l' | integer): string | nil -function io.read(format) end +---@param ... std.readmode +---@return any +---@return any ... +---@nodiscard +function io.read(...) end --- --- In case of success, returns a handle for a temporary file. This file is @@ -195,11 +207,11 @@ function file:lines(...) end --- *number*: reads a string with up to this number of bytes, returning **nil** --- on end of file. If `number` is zero, it reads nothing and returns an --- empty string, or **nil** on end of file. ---- @param format '*n' | '*a' | '*l' | integer ---- @return string | integer | nil ---- @overload fun(format:'*n'): integer ---- @overload fun(format:'*a' | '*l' | integer): string | nil -function file:read(format) end +---@param ... std.readmode +---@return any +---@return any ... +---@nodiscard +function file:read(...) end --- --- Sets and gets the file position, measured from the beginning of the From 6939d67998cb50ce1718bda016c7c05359a7a8ed Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Thu, 27 Mar 2025 07:00:53 +0800 Subject: [PATCH 21/22] fix code --- crates/emmylua_code_analysis/resources/std/string.lua | 2 +- .../src/semantic/infer/infer_binary.rs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/emmylua_code_analysis/resources/std/string.lua b/crates/emmylua_code_analysis/resources/std/string.lua index 0e3d8b1a7..b5a8dd652 100644 --- a/crates/emmylua_code_analysis/resources/std/string.lua +++ b/crates/emmylua_code_analysis/resources/std/string.lua @@ -81,7 +81,7 @@ function string.dump(func, strip) end ---@param plain? boolean ---@return integer|nil start ---@return integer|nil end ----@return any|nil ... captured +---@return string ... captured ---@nodiscard function string.find(s, pattern, init, plain) end 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 179ac60df..a54ae1681 100644 --- a/crates/emmylua_code_analysis/src/semantic/infer/infer_binary.rs +++ b/crates/emmylua_code_analysis/src/semantic/infer/infer_binary.rs @@ -67,9 +67,9 @@ fn infer_union(db: &DbIndex, u: &LuaUnionType, right: &LuaType, op: BinaryOperat } match unique_union_types.len() { - 0 => Some(LuaType::Unknown), - 1 => Some(unique_union_types.into_iter().next().unwrap()), - _ => Some(LuaType::Union(LuaUnionType::new(unique_union_types).into())), + 0 => Ok(LuaType::Unknown), + 1 => Ok(unique_union_types.into_iter().next().unwrap()), + _ => Ok(LuaType::Union(LuaUnionType::new(unique_union_types).into())), } } @@ -167,8 +167,8 @@ fn infer_binary_expr_add(db: &DbIndex, left: LuaType, right: LuaType) -> InferRe }; } match (left.is_nil(), right.is_nil()) { - (true, false) => return Some(right), - (false, true) => return Some(left), + (true, false) => return Ok(right), + (false, true) => return Ok(left), _ => {} } From 26b67ec8a289d157d7d369f4fc7e88e20eca51d6 Mon Sep 17 00:00:00 2001 From: xuhuanzy <501417909@qq.com> Date: Thu, 27 Mar 2025 08:12:40 +0800 Subject: [PATCH 22/22] diagnostic: fix MissingFields --- .../src/diagnostic/checker/check_field.rs | 5 +++++ .../src/diagnostic/test/missing_fields_test.rs | 14 ++++++++++++++ 2 files changed, 19 insertions(+) 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 4e9558233..4c1521b09 100644 --- a/crates/emmylua_code_analysis/src/diagnostic/checker/check_field.rs +++ b/crates/emmylua_code_analysis/src/diagnostic/checker/check_field.rs @@ -64,6 +64,11 @@ fn check_index_expr( let prefix_typ = semantic_model .infer_expr(index_expr.get_prefix_expr()?) .unwrap_or(LuaType::Unknown); + + if !is_valid_prefix_type(&prefix_typ) { + return Some(()); + } + let index_name = index_key.get_path_part(); match code { DiagnosticCode::InjectField => { 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 e44ad09d9..bc5e0b1c8 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 @@ -149,4 +149,18 @@ foo({}) "# )); } + + #[test] + fn test_1() { + let mut ws = VirtualWorkspace::new(); + assert!(ws.check_code_for( + DiagnosticCode::MissingFields, + r#" + ---@type table + local a = {} + + print(a[1]) + "# + )); + } }