diff --git a/.vscode/settings.json b/.vscode/settings.json index c052c5941..ea7222f3d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,5 +3,6 @@ "files.insertFinalNewline": true, "files.trimFinalNewlines": true, "files.trimTrailingWhitespace": true, - "files.trimTrailingWhitespaceInRegexAndStrings": true + "files.trimTrailingWhitespaceInRegexAndStrings": true, + "rust-analyzer.check.command": "clippy", } diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/lua/for_range_stat.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/lua/for_range_stat.rs index ed2a17a2f..d6605830b 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/lua/for_range_stat.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/lua/for_range_stat.rs @@ -1,4 +1,4 @@ -use emmylua_parser::{LuaAstNode, LuaAstToken, LuaExpr, LuaForRangeStat}; +use emmylua_parser::{LuaAstToken, LuaExpr, LuaForRangeStat}; use crate::{ DbIndex, InferFailReason, LuaDeclId, LuaInferCache, LuaOperatorMetaMethod, LuaType, @@ -79,7 +79,6 @@ pub fn infer_for_range_iter_expr_func( } let iter_func_expr = iter_exprs[0].clone(); - let root = iter_func_expr.get_root(); let first_expr_type = infer_expr(db, cache, iter_func_expr)?; let doc_function = match first_expr_type { LuaType::DocFunction(func) => func, @@ -150,7 +149,6 @@ pub fn infer_for_range_iter_expr_func( db, cache, substitutor: &mut substitutor, - root, call_expr: None, }; let params = doc_function diff --git a/crates/emmylua_code_analysis/src/db_index/signature/signature.rs b/crates/emmylua_code_analysis/src/db_index/signature/signature.rs index e224678fa..0315e6afb 100644 --- a/crates/emmylua_code_analysis/src/db_index/signature/signature.rs +++ b/crates/emmylua_code_analysis/src/db_index/signature/signature.rs @@ -185,7 +185,7 @@ pub struct LuaDocParamInfo { pub description: Option, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct LuaDocReturnInfo { pub name: Option, pub type_ref: LuaType, 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 aa6d5a87f..c41b3542f 100644 --- a/crates/emmylua_code_analysis/src/db_index/type/types.rs +++ b/crates/emmylua_code_analysis/src/db_index/type/types.rs @@ -687,7 +687,10 @@ impl LuaFunctionType { Some(owner_type) => { // 一些类型不应该被视为 method if let (LuaType::Ref(_) | LuaType::Def(_), _) = (owner_type, t) - && (t.is_any() || t.is_table() || t.is_class_tpl()) + && (t.is_any() + || t.is_table() + || t.is_class_tpl() + || t.is_str_tpl_ref()) { return false; } diff --git a/crates/emmylua_code_analysis/src/semantic/generic/instantiate_type/instantiate_func_generic.rs b/crates/emmylua_code_analysis/src/semantic/generic/instantiate_type/instantiate_func_generic.rs index e8b5d4565..84db95284 100644 --- a/crates/emmylua_code_analysis/src/semantic/generic/instantiate_type/instantiate_func_generic.rs +++ b/crates/emmylua_code_analysis/src/semantic/generic/instantiate_type/instantiate_func_generic.rs @@ -1,6 +1,6 @@ use std::{collections::HashSet, ops::Deref, sync::Arc}; -use emmylua_parser::{LuaAstNode, LuaCallExpr, LuaExpr}; +use emmylua_parser::{LuaCallExpr, LuaExpr}; use internment::ArcIntern; use crate::{ @@ -63,7 +63,6 @@ pub fn instantiate_func_generic( db, cache, substitutor: &mut substitutor, - root: call_expr.get_root(), call_expr: Some(call_expr.clone()), }; if !generic_tpls.is_empty() { diff --git a/crates/emmylua_code_analysis/src/semantic/generic/tpl_context.rs b/crates/emmylua_code_analysis/src/semantic/generic/tpl_context.rs index 3971df2ea..02200a4a6 100644 --- a/crates/emmylua_code_analysis/src/semantic/generic/tpl_context.rs +++ b/crates/emmylua_code_analysis/src/semantic/generic/tpl_context.rs @@ -1,4 +1,4 @@ -use emmylua_parser::{LuaCallExpr, LuaSyntaxNode}; +use emmylua_parser::LuaCallExpr; use crate::{DbIndex, LuaInferCache, TypeSubstitutor}; @@ -7,6 +7,5 @@ pub struct TplContext<'a> { pub db: &'a DbIndex, pub cache: &'a mut LuaInferCache, pub substitutor: &'a mut TypeSubstitutor, - pub root: LuaSyntaxNode, pub call_expr: Option, } diff --git a/crates/emmylua_code_analysis/src/semantic/infer/infer_call/mod.rs b/crates/emmylua_code_analysis/src/semantic/infer/infer_call/mod.rs index 60e0ae58c..515bbf797 100644 --- a/crates/emmylua_code_analysis/src/semantic/infer/infer_call/mod.rs +++ b/crates/emmylua_code_analysis/src/semantic/infer/infer_call/mod.rs @@ -13,7 +13,8 @@ use super::{ }; use crate::{ CacheEntry, DbIndex, InFiled, LuaFunctionType, LuaGenericType, LuaInstanceType, - LuaOperatorMetaMethod, LuaOperatorOwner, LuaSignatureId, LuaType, LuaTypeDeclId, LuaUnionType, + LuaOperatorMetaMethod, LuaOperatorOwner, LuaSignature, LuaSignatureId, LuaType, LuaTypeDeclId, + LuaUnionType, }; use crate::{ InferGuardRef, @@ -179,6 +180,7 @@ fn infer_signature_doc_function( if !signature.is_resolve_return() { return Err(InferFailReason::UnResolveSignatureReturn(signature_id)); } + let is_generic = signature_is_generic(db, cache, &signature, &call_expr).unwrap_or(false); let overloads = &signature.overloads; if overloads.is_empty() { let mut fake_doc_function = LuaFunctionType::new( @@ -187,7 +189,7 @@ fn infer_signature_doc_function( signature.get_type_params(), signature.get_return_type(), ); - if signature.is_generic() { + if is_generic { fake_doc_function = instantiate_func_generic(db, cache, &fake_doc_function, call_expr)?; } @@ -207,7 +209,7 @@ fn infer_signature_doc_function( cache, new_overloads, call_expr.clone(), - signature.is_generic(), + is_generic, args_count, ) } @@ -654,3 +656,23 @@ fn check_can_infer( Ok(()) } + +fn signature_is_generic( + db: &DbIndex, + cache: &mut LuaInferCache, + signature: &LuaSignature, + call_expr: &LuaCallExpr, +) -> Option { + if signature.is_generic() { + return Some(true); + } + let LuaExpr::IndexExpr(index_expr) = call_expr.get_prefix_expr()? else { + return None; + }; + let prefix_type = infer_expr(db, cache, index_expr.get_prefix_expr()?).ok()?; + match prefix_type { + // 对于 Generic 直接认为是泛型 + LuaType::Generic(_) => Some(true), + _ => Some(prefix_type.contain_tpl()), + } +} 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 aa775cd34..186713835 100644 --- a/crates/emmylua_code_analysis/src/semantic/infer/infer_index.rs +++ b/crates/emmylua_code_analysis/src/semantic/infer/infer_index.rs @@ -738,10 +738,6 @@ fn infer_generic_member( let generic_params = generic_type.get_params(); let substitutor = TypeSubstitutor::from_type_array(generic_params.clone()); - // TODO: this is just a hack to support inheritance from the generic objects - // like `---@class box: T`. Should be rewritten: generic types should - // be passed to the called instantiate_type_generic() in some kind of a - // context. if let LuaType::Ref(base_type_decl_id) = &base_type { let result = infer_generic_members_from_super_generics( db, diff --git a/crates/emmylua_code_analysis/src/semantic/mod.rs b/crates/emmylua_code_analysis/src/semantic/mod.rs index d9226aacc..4865b8627 100644 --- a/crates/emmylua_code_analysis/src/semantic/mod.rs +++ b/crates/emmylua_code_analysis/src/semantic/mod.rs @@ -51,8 +51,9 @@ use crate::{LuaFunctionType, LuaMemberId, LuaMemberKey, LuaTypeOwner}; pub use generic::*; pub use guard::{InferGuard, InferGuardRef}; pub use infer::InferFailReason; +pub use infer::infer_call_expr_func; +pub(crate) use infer::infer_expr; pub use infer::infer_param; -pub(crate) use infer::{infer_call_expr_func, infer_expr}; use overload_resolve::resolve_signature; pub use semantic_info::SemanticDeclLevel; pub use type_check::{TypeCheckFailReason, TypeCheckResult}; diff --git a/crates/emmylua_ls/src/handlers/completion/resolve_completion.rs b/crates/emmylua_ls/src/handlers/completion/resolve_completion.rs index 4ba6d0d23..39e63b9e0 100644 --- a/crates/emmylua_ls/src/handlers/completion/resolve_completion.rs +++ b/crates/emmylua_ls/src/handlers/completion/resolve_completion.rs @@ -61,7 +61,7 @@ pub fn update_function_signature_info( } } } - if let MarkedString::LanguageString(s) = &mut hover_builder.type_description { + if let MarkedString::LanguageString(s) = &mut hover_builder.primary { s.value = format!("{} (+{} overloads)", s.value, overload_count); } } @@ -78,7 +78,7 @@ fn build_vscode_completion_item( .signature_overload .and_then(|overloads| overloads.get(index).cloned()) }) - .unwrap_or_else(|| hover_builder.type_description.clone()); + .unwrap_or_else(|| hover_builder.primary.clone()); match type_description { MarkedString::String(s) => { @@ -138,7 +138,7 @@ fn build_other_completion_item( .signature_overload .and_then(|overloads| overloads.get(index).cloned()) }) - .unwrap_or_else(|| hover_builder.type_description.clone()); + .unwrap_or_else(|| hover_builder.primary.clone()); match type_description { MarkedString::String(s) => { diff --git a/crates/emmylua_ls/src/handlers/definition/mod.rs b/crates/emmylua_ls/src/handlers/definition/mod.rs index 5e7ceb3f4..8ad2c879a 100644 --- a/crates/emmylua_ls/src/handlers/definition/mod.rs +++ b/crates/emmylua_ls/src/handlers/definition/mod.rs @@ -25,7 +25,6 @@ use super::RegisterCapabilities; use crate::context::ServerContextSnapshot; use crate::handlers::definition::goto_path::goto_path; use crate::util::find_ref_at; -pub use goto_function::extract_semantic_decl_from_signature; pub async fn on_goto_definition_handler( context: ServerContextSnapshot, diff --git a/crates/emmylua_ls/src/handlers/hover/build_hover.rs b/crates/emmylua_ls/src/handlers/hover/build_hover.rs index b99ab1c1f..f661c1ca8 100644 --- a/crates/emmylua_ls/src/handlers/hover/build_hover.rs +++ b/crates/emmylua_ls/src/handlers/hover/build_hover.rs @@ -5,15 +5,15 @@ use emmylua_code_analysis::{ DbIndex, LuaCompilation, LuaDeclId, LuaDocument, LuaMemberId, LuaMemberKey, LuaSemanticDeclId, LuaSignatureId, LuaType, LuaTypeDeclId, RenderLevel, SemanticInfo, SemanticModel, }; -use emmylua_parser::{LuaAssignStat, LuaAstNode, LuaExpr, LuaSyntaxToken}; +use emmylua_parser::{ + LuaAssignStat, LuaAstNode, LuaCallArgList, LuaExpr, LuaSyntaxKind, LuaSyntaxToken, + LuaTableExpr, LuaTableField, +}; use lsp_types::{Hover, HoverContents, MarkedString, MarkupContent}; use rowan::TextRange; -use crate::handlers::hover::{ - find_origin::replace_semantic_type, - function_humanize::{hover_function_type, is_function}, - hover_humanize::hover_humanize_type, -}; +use crate::handlers::hover::function::{build_function_hover, is_function}; +use crate::handlers::hover::hover_humanize::hover_humanize_type; use super::{ find_origin::{find_decl_origin_owners, find_member_origin_owners}, @@ -139,32 +139,19 @@ fn build_decl_hover( let mut semantic_decls = find_decl_origin_owners(builder.compilation, builder.semantic_model, decl_id) .get_types(builder.semantic_model); - replace_semantic_type(&mut semantic_decls, &typ); + // 处理类型签名 if is_function(&typ) { - if is_completion { - let decl_semantic_id = LuaSemanticDeclId::LuaDecl(decl_id); - semantic_decls.retain(|(decl, _)| decl == &decl_semantic_id); - } - // 如果找到了那么需要将它移动到末尾, 因为尾部最优先显示 - else if let Some(pos) = semantic_decls - .iter() - .position(|(_, origin_type)| origin_type == &typ) - { - let item = semantic_decls.remove(pos); - semantic_decls.push(item); - } else { - let semantic_decl = { - if let Some(semantic_decl) = semantic_decls.first() { - semantic_decl.0.clone() - } else { - LuaSemanticDeclId::LuaDecl(decl_id) - } - }; - semantic_decls.push((semantic_decl, typ.clone())); - } + adjust_semantic_decls( + builder, + &mut semantic_decls, + &LuaSemanticDeclId::LuaDecl(decl_id), + &typ, + ); - hover_function_type(builder, db, &semantic_decls); + // 处理函数类型 + build_function_hover(builder, db, &semantic_decls); + // hover_function_type(builder, db, &semantic_decls); if let Some((LuaSemanticDeclId::Member(member_id), _)) = semantic_decls .iter() @@ -231,7 +218,6 @@ fn build_member_hover( find_member_origin_owners(builder.compilation, builder.semantic_model, member_id, true) .get_types(builder.semantic_model); - replace_semantic_type(&mut semantic_decls, &typ); let member_name = match member.get_key() { LuaMemberKey::Name(name) => name.to_string(), LuaMemberKey::Integer(i) => format!("[{}]", i), @@ -239,29 +225,14 @@ fn build_member_hover( }; if is_function(&typ) { - if is_completion { - let member_semantic_id = LuaSemanticDeclId::Member(member_id); - semantic_decls.retain(|(decl, _)| decl == &member_semantic_id); - } - // 如果找到了那么需要将它移动到末尾, 因为尾部最优先显示 - else if let Some(pos) = semantic_decls - .iter() - .position(|(_, origin_type)| origin_type == &typ) - { - let item = semantic_decls.remove(pos); - semantic_decls.push(item); - } else { - let semantic_decl = { - if let Some(semantic_decl) = semantic_decls.first() { - semantic_decl.0.clone() - } else { - LuaSemanticDeclId::Member(member_id) - } - }; - semantic_decls.push((semantic_decl, typ.clone())); - } + adjust_semantic_decls( + builder, + &mut semantic_decls, + &LuaSemanticDeclId::Member(member_id), + &typ, + ); - hover_function_type(builder, db, &semantic_decls); + build_function_hover(builder, db, &semantic_decls); builder.set_location_path(Some(member)); @@ -414,3 +385,79 @@ pub fn get_hover_type(builder: &HoverBuilder, semantic_model: &SemanticModel) -> None } + +#[allow(unused)] +fn adjust_semantic_decls( + builder: &mut HoverBuilder, + semantic_decls: &mut Vec<(LuaSemanticDeclId, LuaType)>, + current_semantic_decl_id: &LuaSemanticDeclId, + current_type: &LuaType, +) -> Option<()> { + if let Some(pos) = semantic_decls + .iter() + .position(|(_, typ)| current_type == typ) + { + let item = semantic_decls.remove(pos); + semantic_decls.push(item); + return Some(()); + } + // semantic_decls 是追溯最初定义的结果, 不包含当前内容 + let current_len = semantic_decls.len(); + if current_len == 0 { + // 没有最初定义, 直接添加原始内容 + semantic_decls.push((current_semantic_decl_id.clone(), current_type.clone())); + return Some(()); + } + // 此时有最初定义, 证明当前内容的是派生的或者全部项实例化后联合的结果, 非常难以区分 + // 如果当前定义是 LuaDecl 且追溯到了最初定义, 那么我们不需要添加 + if let LuaSemanticDeclId::LuaDecl(_) = current_semantic_decl_id { + return Some(()); + } + + // 如果当前定义在最初定义组中存在, 那么我们也不需要添加. + // 具有一个难以解决的问题, 返回的`current_semantic_decl_id`为 member 时, 不一定是当前 token 指向的内容, 因此我们还需要再做一层判断, + // 如果是具有实际定义的, 我们仍然需要添加, 例如 signature. + if semantic_decls + .iter() + .any(|(decl, typ)| decl == current_semantic_decl_id && !typ.is_signature()) + { + return Some(()); + } + + if has_add_to_semantic_decls(builder, current_semantic_decl_id).unwrap_or(true) { + semantic_decls.push((current_semantic_decl_id.clone(), current_type.clone())); + }; + + Some(()) +} + +fn has_add_to_semantic_decls( + builder: &mut HoverBuilder, + semantic_decl_id: &LuaSemanticDeclId, +) -> Option { + if let LuaSemanticDeclId::Member(member_id) = semantic_decl_id { + let semantic_model = if member_id.file_id == builder.semantic_model.get_file_id() { + builder.semantic_model + } else { + &builder.compilation.get_semantic_model(member_id.file_id)? + }; + + let root = semantic_model.get_root().syntax(); + let current_node = member_id.get_syntax_id().to_node_from_root(root)?; + if member_id.get_syntax_id().get_kind() == LuaSyntaxKind::TableFieldAssign { + if LuaTableField::can_cast(current_node.kind().into()) { + let table_field = LuaTableField::cast(current_node.clone())?; + let parent = table_field.syntax().parent()?; + let table_expr = LuaTableExpr::cast(parent)?; + let table_type = semantic_model.infer_table_should_be(table_expr.clone())?; + if matches!(table_type, LuaType::Ref(_) | LuaType::Generic(_)) { + // 如果位于函数调用中, 则不添加 + let is_in_call = table_expr.ancestors::().next().is_some(); + return Some(!is_in_call); + } + } + }; + } + + Some(true) +} diff --git a/crates/emmylua_ls/src/handlers/hover/find_origin.rs b/crates/emmylua_ls/src/handlers/hover/find_origin.rs index d43714a61..3be94a54c 100644 --- a/crates/emmylua_ls/src/handlers/hover/find_origin.rs +++ b/crates/emmylua_ls/src/handlers/hover/find_origin.rs @@ -264,6 +264,7 @@ fn resolve_table_field_through_type_inference( .and_then(|m| m.property_owner_id) } +#[allow(unused)] pub fn replace_semantic_type( semantic_decls: &mut [(LuaSemanticDeclId, LuaType)], origin_type: &LuaType, diff --git a/crates/emmylua_ls/src/handlers/hover/function/mod.rs b/crates/emmylua_ls/src/handlers/hover/function/mod.rs new file mode 100644 index 000000000..4f29d01ea --- /dev/null +++ b/crates/emmylua_ls/src/handlers/hover/function/mod.rs @@ -0,0 +1,563 @@ +use std::{collections::HashSet, sync::Arc, vec}; + +use emmylua_code_analysis::{ + AsyncState, DbIndex, InferGuard, LuaDocReturnInfo, LuaFunctionType, LuaMember, LuaMemberKey, + LuaMemberOwner, LuaSemanticDeclId, LuaType, RenderLevel, TypeSubstitutor, VariadicType, + humanize_type, infer_call_expr_func, instantiate_doc_function, + try_extract_signature_id_from_field, +}; + +use crate::handlers::hover::{ + HoverBuilder, + hover_humanize::{ + DescriptionInfo, extract_description_from_property_owner, extract_owner_name_from_element, + extract_parent_type_from_element, hover_humanize_type, + }, + infer_prefix_global_name, +}; + +pub fn build_function_hover( + builder: &mut HoverBuilder, + db: &DbIndex, + semantic_decls: &[(LuaSemanticDeclId, LuaType)], +) -> Option<()> { + let (function_name, is_local) = { + let (semantic_decl, _) = semantic_decls.first()?; + match semantic_decl { + LuaSemanticDeclId::LuaDecl(id) => { + let decl = db.get_decl_index().get_decl(id)?; + (decl.get_name().to_string(), decl.is_local()) + } + LuaSemanticDeclId::Member(id) => { + let member = db.get_member_index().get_member(id)?; + (member.get_key().to_path(), false) + } + _ => { + return None; + } + } + }; + + // 如果是函数调用, 那么我们需要根据上下文实例化出实际类型 + if let Some(call_expr) = builder.get_call_expr() { + build_function_call_hover( + builder, + db, + semantic_decls, + &call_expr, + &function_name, + is_local, + ); + } else { + build_function_define_hover(builder, db, semantic_decls, &function_name, is_local); + } + + Some(()) +} + +fn build_function_call_hover( + builder: &mut HoverBuilder, + db: &DbIndex, + semantic_decls: &[(LuaSemanticDeclId, LuaType)], + call_expr: &emmylua_parser::LuaCallExpr, + function_name: &str, + is_local: bool, +) -> Option<()> { + let final_type = infer_call_expr_func( + db, + &mut builder.semantic_model.get_cache().borrow_mut(), + call_expr.clone(), + semantic_decls.last()?.1.clone(), + &InferGuard::new(), + None, + ) + .ok()?; + + // 根据推断出来的类型确定哪个 semantic_decl 是匹配的 + let mut match_semantic_decl = &semantic_decls.last()?.0; + for (semantic_decl, typ) in semantic_decls.iter() { + if let LuaType::DocFunction(f) = typ { + if f == &final_type { + match_semantic_decl = semantic_decl; + break; + } + } + } + + let function_member = match match_semantic_decl { + LuaSemanticDeclId::Member(id) => { + let member = db.get_member_index().get_member(&id)?; + Some(member) + } + _ => None, + }; + + let is_field = function_member_is_field(db, semantic_decls); + let contents = process_function_type( + builder, + db, + &LuaType::DocFunction(final_type), + function_member, + function_name, + is_local, + is_field, + )?; + let description = get_function_description(builder, db, &match_semantic_decl); + builder.set_type_description(contents.first()?.clone()); + builder.add_description_from_info(description); + + Some(()) +} + +#[derive(Debug, Clone)] +struct HoverFunctionInfo { + primary: String, + overloads: Option>, + description: Option, +} + +#[allow(unused)] +fn build_function_define_hover( + builder: &mut HoverBuilder, + db: &DbIndex, + semantic_decls: &[(LuaSemanticDeclId, LuaType)], + function_name: &str, + is_local: bool, +) -> Option<()> { + let is_field = function_member_is_field(db, semantic_decls); + let mut function_infos = Vec::new(); + for (semantic_decl_id, typ) in semantic_decls { + let mut typ = typ.clone(); + let function_member = match semantic_decl_id { + LuaSemanticDeclId::Member(id) => { + let member = db.get_member_index().get_member(&id)?; + Some(member) + } + _ => None, + }; + + if let Some(substitutor) = &builder.substitutor { + if let Some(lua_func) = hover_instantiate_function_type(db, &typ, substitutor) { + typ = LuaType::DocFunction(lua_func); + } + } + + let Some(contents) = process_function_type( + builder, + db, + &typ, + function_member, + function_name, + is_local, + is_field, + ) else { + continue; + }; + if contents.is_empty() { + continue; + } + let description = get_function_description(builder, db, &semantic_decl_id); + function_infos.push(HoverFunctionInfo { + primary: contents.first()?.clone(), + overloads: if contents.len() > 1 { + Some(contents[1..].to_vec()) + } else { + None + }, + description, + }); + } + + // 去重, 这是必须的 + function_infos.dedup_by_key(|info| info.primary.clone()); + + // 需要显示重载的情况 + match function_infos.len() { + 0 => { + return None; + } + 1 => { + builder.set_type_description(function_infos[0].primary.clone()); + builder.add_description_from_info(function_infos[0].description.clone()); + } + _ => { + let main_type = function_infos.pop()?; + builder.set_type_description(main_type.primary.clone()); + builder.add_description_from_info(main_type.description.clone()); + + for type_desc in function_infos { + builder.add_signature_overload(type_desc.primary.clone()); + if let Some(overloads) = &type_desc.overloads { + for overload in overloads { + builder.add_signature_overload(overload.clone()); + } + } + builder.add_description_from_info(type_desc.description.clone()); + } + } + } + Some(()) +} + +fn process_function_type( + builder: &mut HoverBuilder, + db: &DbIndex, + typ: &LuaType, + function_member: Option<&LuaMember>, + function_name: &str, + is_local: bool, + is_field: bool, +) -> Option> { + match typ { + LuaType::DocFunction(lua_func) => { + let content = hover_doc_function_type( + builder, + db, + lua_func, + function_member, + &function_name, + is_local, + is_field, + convert_function_return_to_docs(lua_func), + ); + Some(vec![content]) + } + LuaType::Signature(signature_id) => { + let signature = db.get_signature_index().get(&signature_id)?; + let mut new_overloads = signature.overloads.clone(); + let fake_doc_function = Arc::new(LuaFunctionType::new( + signature.async_state, + signature.is_colon_define, + signature.get_type_params(), + signature.get_return_type(), + )); + new_overloads.insert(0, fake_doc_function); + let mut contents = Vec::with_capacity(new_overloads.len()); + for (i, overload) in new_overloads.iter().enumerate() { + contents.push(hover_doc_function_type( + builder, + db, + overload, + function_member, + function_name, + is_local, + is_field, + if i == 0 { + signature.return_docs.clone() + } else { + convert_function_return_to_docs(overload) + }, + )); + } + Some(contents) + } + LuaType::Union(union) => { + let mut contents = Vec::new(); + for typ in union.into_vec() { + if let Some(content) = process_function_type( + builder, + db, + &typ, + function_member, + function_name, + is_local, + is_field, + ) { + contents.extend(content); + } + } + Some(contents) + } + _ => None, + } +} + +fn hover_doc_function_type( + builder: &mut HoverBuilder, + db: &DbIndex, + func: &LuaFunctionType, + owner_member: Option<&LuaMember>, + func_name: &str, + is_local: bool, + is_field: bool, /* 是否为类字段 */ + return_docs: Vec, /* 返回值以此为准 */ +) -> String { + let async_label = match func.get_async_state() { + AsyncState::Async => "async ", + AsyncState::Sync => "sync ", + _ => "", + }; + let mut is_method = func.is_colon_define(); + let mut type_label = "function "; + // 有可能来源于类. 例如: `local add = class.add`, `add()`应被视为类方法 + let full_name = if let Some(owner_member) = owner_member { + let mut name = String::new(); + let parent_owner = db + .get_member_index() + .get_current_owner(&owner_member.get_id()); + if let Some(parent_owner) = parent_owner { + match parent_owner { + LuaMemberOwner::Type(type_decl_id) => { + let global_name = + infer_prefix_global_name(builder.semantic_model, owner_member); + // 如果是全局定义, 则使用定义时的名称 + if let Some(global_name) = global_name { + name.push_str(global_name); + } else { + name.push_str(type_decl_id.get_simple_name()); + } + if is_field { + type_label = "(field) "; + } + is_method = func.is_method( + builder.semantic_model, + Some(&LuaType::Ref(type_decl_id.clone())), + ); + } + LuaMemberOwner::Element(element_id) => { + if let Some(LuaType::Ref(type_decl_id) | LuaType::Def(type_decl_id)) = + extract_parent_type_from_element(builder.semantic_model, element_id) + { + name.push_str(type_decl_id.get_simple_name()); + if is_field { + type_label = "(field) "; + } + is_method = func.is_method( + builder.semantic_model, + Some(&LuaType::Ref(type_decl_id.clone())), + ); + } else if let Some(owner_name) = + extract_owner_name_from_element(builder.semantic_model, element_id) + { + name.push_str(&owner_name); + } + } + _ => {} + } + } + + if is_method { + type_label = "(method) "; + name.push(':'); + } else { + name.push('.'); + } + if let LuaMemberKey::Name(n) = owner_member.get_key() { + name.push_str(n.as_str()); + } + name + } else { + if is_local { + type_label = "local function "; + } + func_name.to_string() + }; + let params = func + .get_params() + .iter() + .enumerate() + .map(|(index, param)| { + let name = param.0.clone(); + if index == 0 && is_method && !func.is_colon_define() { + "".to_string() + } else if let Some(ty) = ¶m.1 { + format!("{}: {}", name, humanize_type(db, ty, RenderLevel::Normal)) + } else { + name.to_string() + } + }) + .filter(|s| !s.is_empty()) + .collect::>() + .join(", "); + + let ret_detail = build_function_returns(builder, return_docs); + format_function_type(type_label, async_label, full_name, params, ret_detail) +} + +fn convert_function_return_to_docs(func: &LuaFunctionType) -> Vec { + match func.get_ret() { + LuaType::Variadic(variadic) => match variadic.as_ref() { + VariadicType::Base(base) => vec![LuaDocReturnInfo { + name: None, + type_ref: base.clone(), + description: None, + }], + VariadicType::Multi(types) => types + .iter() + .map(|ty| LuaDocReturnInfo { + name: None, + type_ref: ty.clone(), + description: None, + }) + .collect(), + }, + _ => vec![LuaDocReturnInfo { + name: None, + type_ref: func.get_ret().clone(), + description: None, + }], + } +} + +fn format_function_type( + type_label: &str, + async_label: &str, + full_name: String, + params: String, + rets: String, +) -> String { + let prefix = if type_label.starts_with("function") { + format!("{}{}", async_label, type_label) + } else { + format!("{}{}", type_label, async_label) + }; + format!("{}{}({}){}", prefix, full_name, params, rets) +} + +fn get_function_description( + builder: &mut HoverBuilder, + db: &DbIndex, + semantic_decl_id: &LuaSemanticDeclId, +) -> Option { + let mut description = + extract_description_from_property_owner(builder.semantic_model, semantic_decl_id); + match semantic_decl_id { + LuaSemanticDeclId::Member(id) => { + let member = db.get_member_index().get_member(id)?; + // 以 @field 定义的 function 描述信息绑定的 id 并不是 member, 需要特殊处理 + if description.is_none() + && let Some(signature_id) = try_extract_signature_id_from_field(db, member) + { + description = extract_description_from_property_owner( + builder.semantic_model, + &LuaSemanticDeclId::Signature(signature_id), + ); + } + Some(member) + } + _ => None, + }; + description +} + +fn build_function_returns( + builder: &mut HoverBuilder, + return_docs: Vec, +) -> String { + let mut result = String::new(); + // 如果不是补全且存在名称, 我们需要多行显示 + let has_multiline = !builder.is_completion + && return_docs + .iter() + .any(|return_info| return_info.name.is_some()); + + for (i, return_info) in return_docs.iter().enumerate() { + if i == 0 && return_info.type_ref.is_nil() { + continue; + } + let type_text = build_function_return_type(builder, return_info, i); + + if has_multiline { + // 存在返回值名称时使用多行模式 + let prefix = if i == 0 { + result.push('\n'); + "-> ".to_string() + } else { + format!("{}. ", i + 1) + }; + let name = return_info.name.clone().unwrap_or_default(); + + result.push_str(&format!( + " {}{}{}\n", + prefix, + if !name.is_empty() { + format!("{}: ", name) + } else { + "".to_string() + }, + type_text, + )); + } else { + // 不存在返回值名称时使用单行模式 + if i == 0 { + result.push_str(&format!(" -> {}", type_text)); + } else { + result.push_str(&format!(", {}", type_text)); + } + } + } + + result +} + +fn build_function_return_type( + builder: &mut HoverBuilder, + ret_info: &LuaDocReturnInfo, + i: usize, +) -> String { + let type_expansion_count = builder.get_type_expansion_count(); + // 在这个过程中可能会设置`type_expansion` + let type_text = hover_humanize_type(builder, &ret_info.type_ref, Some(RenderLevel::Simple)); + if builder.get_type_expansion_count() > type_expansion_count { + // 重新设置`type_expansion` + if let Some(pop_type_expansion) = + builder.pop_type_expansion(type_expansion_count, builder.get_type_expansion_count()) + { + let mut new_type_expansion = format!("return #{}", i + 1); + let mut seen = HashSet::new(); + for type_expansion in pop_type_expansion { + for line in type_expansion.lines().skip(1) { + if seen.insert(line.to_string()) { + new_type_expansion.push('\n'); + new_type_expansion.push_str(line); + } + } + } + builder.add_type_expansion(new_type_expansion); + } + }; + type_text +} + +// 函数是否为类字段, 任意一个为类字段我们都认为全部为类字段 +fn function_member_is_field(db: &DbIndex, semantic_decls: &[(LuaSemanticDeclId, LuaType)]) -> bool { + semantic_decls.iter().any(|(semantic_decl, _)| { + if let LuaSemanticDeclId::Member(id) = semantic_decl { + let member = db.get_member_index().get_member(id); + member.is_some() && member.unwrap().is_field() + } else { + false + } + }) +} + +fn hover_instantiate_function_type( + db: &DbIndex, + typ: &LuaType, + substitutor: &TypeSubstitutor, +) -> Option> { + if !typ.contain_tpl() { + return None; + } + match typ { + LuaType::DocFunction(f) => { + if let LuaType::DocFunction(f) = instantiate_doc_function(db, f, substitutor) { + Some(f) + } else { + None + } + } + _ => None, + } +} + +pub fn is_function(typ: &LuaType) -> bool { + typ.is_function() + || match &typ { + LuaType::Union(union) => union + .into_vec() + .iter() + .all(|t| matches!(t, LuaType::DocFunction(_) | LuaType::Signature(_))), + _ => false, + } +} diff --git a/crates/emmylua_ls/src/handlers/hover/function_humanize.rs b/crates/emmylua_ls/src/handlers/hover/function_humanize.rs deleted file mode 100644 index 7038f4872..000000000 --- a/crates/emmylua_ls/src/handlers/hover/function_humanize.rs +++ /dev/null @@ -1,788 +0,0 @@ -use std::collections::HashSet; - -use emmylua_code_analysis::{ - AsyncState, DbIndex, LuaDocReturnInfo, LuaFunctionType, LuaMember, LuaMemberKey, - LuaMemberOwner, LuaSemanticDeclId, LuaSignature, LuaSignatureId, LuaType, RenderLevel, - humanize_type, try_extract_signature_id_from_field, -}; - -use crate::handlers::{ - definition::extract_semantic_decl_from_signature, - hover::{ - HoverBuilder, - hover_humanize::{ - DescriptionInfo, extract_description_from_property_owner, - extract_owner_name_from_element, hover_humanize_type, - }, - infer_prefix_global_name, - }, -}; - -#[derive(Debug, Clone)] -struct HoverFunctionInfo { - type_description: String, - overloads: Option>, - description: Option, - is_call_function: bool, -} - -pub fn hover_function_type( - builder: &mut HoverBuilder, - db: &DbIndex, - semantic_decls: &[(LuaSemanticDeclId, LuaType)], -) -> Option<()> { - let (name, is_local) = { - let (semantic_decl, _) = semantic_decls.first()?; - match semantic_decl { - LuaSemanticDeclId::LuaDecl(id) => { - let decl = db.get_decl_index().get_decl(id)?; - (decl.get_name().to_string(), decl.is_local()) - } - LuaSemanticDeclId::Member(id) => { - let member = db.get_member_index().get_member(id)?; - (member.get_key().to_path(), false) - } - _ => { - return None; - } - } - }; - - let call_function = builder.get_call_function(); - // 已处理过的 semantic_decl_id, 用于解决`test_issue_499_3` - let mut handled_semantic_decl_ids = HashSet::new(); - let mut type_descs: Vec = Vec::with_capacity(semantic_decls.len()); - // 记录已处理过的类型, 用于在 Union 中跳过重复类型. - // 这是为了解决最后一个类型可能是前面所有类型的联合类型的情况 - let mut processed_types = HashSet::new(); - - for (semantic_decl_id, typ) in semantic_decls { - let is_new = handled_semantic_decl_ids.insert(semantic_decl_id); - let mut function_info = HoverFunctionInfo { - type_description: String::new(), - overloads: None, - description: if is_new { - extract_description_from_property_owner(builder.semantic_model, semantic_decl_id) - } else { - None - }, - is_call_function: false, - }; - - let function_member = match semantic_decl_id { - LuaSemanticDeclId::Member(id) => { - let member = db.get_member_index().get_member(id)?; - // 以 @field 定义的 function 描述信息绑定的 id 并不是 member, 需要特殊处理 - if is_new - && function_info.description.is_none() - && let Some(signature_id) = try_extract_signature_id_from_field(db, member) - { - function_info.description = extract_description_from_property_owner( - builder.semantic_model, - &LuaSemanticDeclId::Signature(signature_id), - ); - } - Some(member) - } - _ => None, - }; - - // 如果函数定义来自于其他文件, 我们需要添加原始的注释信息. 参考`test_other_file_function` - if let LuaType::Signature(signature_id) = typ - && let Some(semantic_id) = - extract_semantic_decl_from_signature(builder.compilation, signature_id) - && semantic_id != *semantic_decl_id - // signature 的原始定义的描述信息 - && let Some(origin_description) = - extract_description_from_property_owner(builder.semantic_model, &semantic_id) - { - match &mut function_info.description { - Some(current_description) => { - // 如果描述不为空, 则合并描述 - if let Some(description) = origin_description.description { - if current_description.description.is_none() { - current_description.description = Some(description); - } else { - current_description.description = Some(format!( - "{}\n{}", - current_description.description.take()?, - description - )); - } - } - } - None => { - function_info.description = Some(origin_description); - } - } - } - - // 如果当前类型是 Union, 传入已处理的类型集合 - let result = match typ { - LuaType::Union(_) => process_single_function_type_with_exclusions( - builder, - db, - typ, - function_member, - &name, - is_local, - call_function.as_ref(), - &processed_types, - ), - _ => { - // 记录非 Union 类型 - processed_types.insert(typ.clone()); - process_single_function_type( - builder, - db, - typ, - function_member, - &name, - is_local, - call_function.as_ref(), - ) - } - }; - - match result { - ProcessFunctionTypeResult::Single(mut info) => { - // 合并描述信息 - if function_info.description.is_some() && info.description.is_none() { - info.description = function_info.description; - } - function_info = info; - } - ProcessFunctionTypeResult::Multiple(infos) => { - // 对于 Union 类型, 将每个子类型的结果都添加到 type_descs 中 - let infos_len = infos.len(); - for (index, mut info) in infos.into_iter().enumerate() { - // 合并描述信息, 只有最后一个才设置描述 - if function_info.description.is_some() - && info.description.is_none() - && index == infos_len - 1 - { - info.description = function_info.description.clone(); - } - if info.is_call_function { - type_descs.clear(); - type_descs.push(info); - break; - } else { - type_descs.push(info); - } - } - continue; - } - ProcessFunctionTypeResult::Skip => { - continue; - } - } - - if function_info.is_call_function { - type_descs.clear(); - type_descs.push(function_info); - break; - } else { - type_descs.push(function_info); - } - } - - // 此时是函数调用且具有完全匹配的签名, 那么只需要显示对应的签名, 不需要显示重载 - if let Some(info) = type_descs.first() - && info.is_call_function - { - builder.signature_overload = None; - builder.set_type_description(info.type_description.clone()); - - builder.add_description_from_info(info.description.clone()); - return Some(()); - } - - // 去重 - type_descs.dedup_by_key(|info| info.type_description.clone()); - - // 需要显示重载的情况 - match type_descs.len() { - 0 => { - return None; - } - 1 => { - builder.set_type_description(type_descs[0].type_description.clone()); - builder.add_description_from_info(type_descs[0].description.clone()); - } - _ => { - // 将最后一个作为 type_description - let main_type = type_descs.pop()?; - builder.set_type_description(main_type.type_description.clone()); - builder.add_description_from_info(main_type.description.clone()); - - for type_desc in type_descs { - builder.add_signature_overload(type_desc.type_description); - if let Some(overloads) = type_desc.overloads { - for overload in overloads { - builder.add_signature_overload(overload); - } - } - builder.add_description_from_info(type_desc.description); - } - } - } - - Some(()) -} - -fn hover_doc_function_type( - builder: &HoverBuilder, - db: &DbIndex, - lua_func: &LuaFunctionType, - owner_member: Option<&LuaMember>, - func_name: &str, -) -> String { - let async_label = match lua_func.get_async_state() { - AsyncState::Async => "async ", - AsyncState::Sync => "sync ", - _ => "", - }; - let mut is_method = lua_func.is_colon_define(); - let mut type_label = "function "; - // 有可能来源于类. 例如: `local add = class.add`, `add()`应被视为类方法 - let full_name = if let Some(owner_member) = owner_member { - let global_name = infer_prefix_global_name(builder.semantic_model, owner_member); - let mut name = String::new(); - let parent_owner = db - .get_member_index() - .get_current_owner(&owner_member.get_id()); - if let Some(parent_owner) = parent_owner { - match parent_owner { - LuaMemberOwner::Type(type_decl_id) => { - // 如果是全局定义, 则使用定义时的名称 - if let Some(global_name) = global_name { - name.push_str(global_name); - } else { - name.push_str(type_decl_id.get_simple_name()); - } - if owner_member.is_field() { - type_label = "(field) "; - } - is_method = lua_func.is_method( - builder.semantic_model, - Some(&LuaType::Ref(type_decl_id.clone())), - ); - } - LuaMemberOwner::Element(element_id) => { - if let Some(owner_name) = - extract_owner_name_from_element(builder.semantic_model, element_id) - { - name.push_str(&owner_name); - } - } - _ => {} - } - } - - if is_method { - type_label = "(method) "; - name.push(':'); - } else { - name.push('.'); - } - if let LuaMemberKey::Name(n) = owner_member.get_key() { - name.push_str(n.as_str()); - } - name - } else { - func_name.to_string() - }; - let params = lua_func - .get_params() - .iter() - .enumerate() - .map(|(index, param)| { - let name = param.0.clone(); - if index == 0 && is_method && !lua_func.is_colon_define() { - "".to_string() - } else if let Some(ty) = ¶m.1 { - format!("{}: {}", name, humanize_type(db, ty, RenderLevel::Normal)) - } else { - name.to_string() - } - }) - .filter(|s| !s.is_empty()) - .collect::>() - .join(", "); - - let ret_detail = { - let ret_type = lua_func.get_ret(); - match ret_type { - LuaType::Nil => "".to_string(), - _ => { - format!(" -> {}", humanize_type(db, ret_type, RenderLevel::Simple)) - } - } - }; - format_function_type(type_label, async_label, full_name, params, ret_detail) -} - -struct HoverSignatureResult { - type_description: String, - overloads: Option>, - call_function: Option, -} - -fn hover_signature_type( - builder: &mut HoverBuilder, - db: &DbIndex, - signature_id: LuaSignatureId, - owner_member: Option<&LuaMember>, - func_name: &str, - is_local: bool, - call_function: Option<&LuaFunctionType>, -) -> Option { - let signature = db.get_signature_index().get(&signature_id)?; - - let mut is_method = signature.is_colon_define; - let mut self_real_type = LuaType::SelfInfer; - let mut type_label = "function "; - // 有可能来源于类. 例如: `local add = class.add`, `add()`应被视为类定义的内容 - let full_name = if let Some(owner_member) = owner_member { - let global_name = infer_prefix_global_name(builder.semantic_model, owner_member); - let mut name = String::new(); - let parent_owner = db - .get_member_index() - .get_current_owner(&owner_member.get_id()); - match parent_owner { - Some(LuaMemberOwner::Type(type_decl_id)) => { - self_real_type = LuaType::Ref(type_decl_id.clone()); - // 如果是全局定义, 则使用定义时的名称 - if let Some(global_name) = global_name { - name.push_str(global_name); - } else { - name.push_str(type_decl_id.get_simple_name()); - } - if owner_member.is_field() { - type_label = "(field) "; - } - // `field`定义的function也被视为`signature`, 因此这里需要额外处理 - is_method = signature.is_method(builder.semantic_model, Some(&self_real_type)); - if is_method { - type_label = "(method) "; - name.push(':'); - } else { - name.push('.'); - } - } - Some(LuaMemberOwner::Element(element_id)) => { - if let Some(owner_name) = - extract_owner_name_from_element(builder.semantic_model, element_id) - { - name.push_str(&owner_name); - name.push('.'); - } - } - _ => {} - } - if let LuaMemberKey::Name(n) = owner_member.get_key() { - name.push_str(n.as_str()); - } - name - } else { - if is_local { - type_label = "local function "; - } - func_name.to_string() - }; - - // 构建 signature - let signature_info: String = { - let async_label = db - .get_signature_index() - .get(&signature_id) - .map(|signature| match signature.async_state { - AsyncState::Async => "async ", - AsyncState::Sync => "sync ", - _ => "", - }) - .unwrap_or(""); - let params = signature - .get_type_params() - .iter() - .enumerate() - .map(|(index, param)| { - let name = param.0.clone(); - if index == 0 && !signature.is_colon_define && is_method { - "".to_string() - } else if let Some(ty) = ¶m.1 { - format!("{}: {}", name, humanize_type(db, ty, RenderLevel::Simple)) - } else { - name - } - }) - .filter(|s| !s.is_empty()) - .collect::>() - .join(", "); - let rets = build_signature_rets(builder, signature, builder.is_completion, None); - let result = format_function_type(type_label, async_label, full_name.clone(), params, rets); - // 由于 @field 定义的`docfunction`会被视为`signature`, 因此这里额外处理 - if let Some(call_function) = call_function - && call_function.get_params() == signature.get_type_params() - { - // 如果具有完全匹配的签名, 那么将其设置为当前签名, 且不显示重载 - return Some(HoverSignatureResult { - type_description: result, - overloads: None, - call_function: Some(call_function.clone()), - }); - } - result - }; - // 构建所有重载 - let overloads: Vec = { - let mut overloads = Vec::new(); - for overload in &signature.overloads { - let async_label = match overload.get_async_state() { - AsyncState::Async => "async ", - AsyncState::Sync => "sync ", - _ => "", - }; - let params = overload - .get_params() - .iter() - .enumerate() - .map(|(index, param)| { - let name = param.0.clone(); - if index == 0 - && param.1.is_some() - && overload.is_method(builder.semantic_model, Some(&self_real_type)) - { - "".to_string() - } else if let Some(ty) = ¶m.1 { - format!("{}: {}", name, humanize_type(db, ty, RenderLevel::Simple)) - } else { - name - } - }) - .filter(|s| !s.is_empty()) - .collect::>() - .join(", "); - let rets = - build_signature_rets(builder, signature, builder.is_completion, Some(overload)); - let result = - format_function_type(type_label, async_label, full_name.clone(), params, rets); - - if let Some(call_function) = call_function - && *call_function == **overload - { - // 如果具有完全匹配的签名, 那么将其设置为当前签名, 且不显示重载 - return Some(HoverSignatureResult { - type_description: result, - overloads: None, - call_function: Some(call_function.clone()), - }); - }; - overloads.push(result); - } - overloads - }; - - Some(HoverSignatureResult { - type_description: signature_info, - overloads: Some(overloads), - call_function: None, - }) -} - -fn build_signature_rets( - builder: &mut HoverBuilder, - signature: &LuaSignature, - is_completion: bool, - overload: Option<&LuaFunctionType>, -) -> String { - let db = builder.semantic_model.get_db(); - let mut result = String::new(); - // overload 的返回值固定为单行 - let overload_rets_string = if let Some(overload) = overload { - let ret_type = overload.get_ret(); - match ret_type { - LuaType::Nil => "".to_string(), - _ => { - format!(" -> {}", humanize_type(db, ret_type, RenderLevel::Simple)) - } - } - } else { - "".to_string() - }; - - if is_completion { - let rets = if !overload_rets_string.is_empty() { - overload_rets_string - } else { - let rets = &signature.return_docs; - if rets.is_empty() || signature.get_return_type().is_nil() { - "".to_string() - } else { - format!( - " -> {}", - rets.iter() - .enumerate() - .map(|(i, ret)| build_signature_ret_type(builder, ret, i)) - .collect::>() - .join(", ") - ) - } - }; - result.push_str(rets.as_str()); - return result; - } - - let rets = if !overload_rets_string.is_empty() { - overload_rets_string - } else { - let rets = &signature.return_docs; - if rets.is_empty() || signature.get_return_type().is_nil() { - "".to_string() - } else { - let mut rets_string_multiline = String::new(); - rets_string_multiline.push('\n'); - - for (i, ret) in rets.iter().enumerate() { - let type_text = build_signature_ret_type(builder, ret, i); - let prefix = if i == 0 { - "-> ".to_string() - } else { - format!("{}. ", i + 1) - }; - let name = ret.name.clone().unwrap_or_default(); - - rets_string_multiline.push_str(&format!( - " {}{}{}\n", - prefix, - if !name.is_empty() { - format!("{}: ", name) - } else { - "".to_string() - }, - type_text, - )); - } - rets_string_multiline - } - }; - result.push_str(rets.as_str()); - result -} - -fn build_signature_ret_type( - builder: &mut HoverBuilder, - ret_info: &LuaDocReturnInfo, - i: usize, -) -> String { - let type_expansion_count = builder.get_type_expansion_count(); - let type_text = hover_humanize_type(builder, &ret_info.type_ref, Some(RenderLevel::Simple)); - if builder.get_type_expansion_count() > type_expansion_count { - // 重新设置`type_expansion` - if let Some(pop_type_expansion) = - builder.pop_type_expansion(type_expansion_count, builder.get_type_expansion_count()) - { - let mut new_type_expansion = format!("return #{}", i + 1); - let mut seen = HashSet::new(); - for type_expansion in pop_type_expansion { - for line in type_expansion.lines().skip(1) { - if seen.insert(line.to_string()) { - new_type_expansion.push('\n'); - new_type_expansion.push_str(line); - } - } - } - builder.add_type_expansion(new_type_expansion); - } - }; - type_text -} - -fn format_function_type( - type_label: &str, - async_label: &str, - full_name: String, - params: String, - rets: String, -) -> String { - let prefix = if type_label.starts_with("function") { - format!("{}{}", async_label, type_label) - } else { - format!("{}{}", type_label, async_label) - }; - format!("{}{}({}){}", prefix, full_name, params, rets) -} - -#[derive(Debug, Clone)] -enum ProcessFunctionTypeResult { - Single(HoverFunctionInfo), - Multiple(Vec), - Skip, -} - -fn process_single_function_type( - builder: &mut HoverBuilder, - db: &DbIndex, - typ: &LuaType, - function_member: Option<&LuaMember>, - name: &str, - is_local: bool, - call_function: Option<&LuaFunctionType>, -) -> ProcessFunctionTypeResult { - match typ { - LuaType::Function => ProcessFunctionTypeResult::Single(HoverFunctionInfo { - type_description: format!("function {}()", name), - overloads: None, - description: None, - is_call_function: false, - }), - LuaType::DocFunction(lua_func) => { - let type_description = - hover_doc_function_type(builder, db, lua_func, function_member, name); - let is_call_function = if let Some(call_function) = call_function { - call_function.get_params() == lua_func.get_params() - } else { - false - }; - - ProcessFunctionTypeResult::Single(HoverFunctionInfo { - type_description, - overloads: None, - description: None, - is_call_function, - }) - } - LuaType::Signature(signature_id) => { - let signature_result = hover_signature_type( - builder, - db, - *signature_id, - function_member, - name, - is_local, - call_function, - ) - .unwrap_or_else(|| HoverSignatureResult { - type_description: format!("function {}", name), - overloads: None, - call_function: None, - }); - - let is_call_function = signature_result.call_function.is_some(); - - ProcessFunctionTypeResult::Single(HoverFunctionInfo { - type_description: signature_result.type_description, - overloads: signature_result.overloads, - description: None, - is_call_function, - }) - } - LuaType::Union(union) => { - let mut results = Vec::new(); - for union_type in union.into_vec() { - match process_single_function_type( - builder, - db, - &union_type, - function_member, - name, - is_local, - call_function, - ) { - ProcessFunctionTypeResult::Single(info) => { - results.push(info); - } - ProcessFunctionTypeResult::Multiple(infos) => { - results.extend(infos); - } - ProcessFunctionTypeResult::Skip => {} - } - } - - if results.is_empty() { - ProcessFunctionTypeResult::Skip - } else { - ProcessFunctionTypeResult::Multiple(results) - } - } - _ => ProcessFunctionTypeResult::Single(HoverFunctionInfo { - type_description: format!("function {}", name), - overloads: None, - description: None, - is_call_function: false, - }), - } -} - -#[allow(clippy::too_many_arguments)] -fn process_single_function_type_with_exclusions( - builder: &mut HoverBuilder, - db: &DbIndex, - typ: &LuaType, - function_member: Option<&LuaMember>, - name: &str, - is_local: bool, - call_function: Option<&LuaFunctionType>, - processed_types: &HashSet, -) -> ProcessFunctionTypeResult { - match typ { - LuaType::Union(union) => { - let mut results = Vec::new(); - for union_type in union.into_vec() { - // 跳过已经处理过的类型 - if processed_types.contains(&union_type) { - continue; - } - - match process_single_function_type_with_exclusions( - builder, - db, - &union_type, - function_member, - name, - is_local, - call_function, - processed_types, - ) { - ProcessFunctionTypeResult::Single(info) => { - results.push(info); - } - ProcessFunctionTypeResult::Multiple(infos) => { - results.extend(infos); - } - ProcessFunctionTypeResult::Skip => {} - } - } - - if results.is_empty() { - ProcessFunctionTypeResult::Skip - } else { - ProcessFunctionTypeResult::Multiple(results) - } - } - _ => { - // 对于非 Union 类型, 直接调用原函数 - process_single_function_type( - builder, - db, - typ, - function_member, - name, - is_local, - call_function, - ) - } - } -} - -pub fn is_function(typ: &LuaType) -> bool { - typ.is_function() - || match &typ { - LuaType::Union(union) => union - .into_vec() - .iter() - .all(|t| matches!(t, LuaType::DocFunction(_) | LuaType::Signature(_))), - _ => false, - } -} diff --git a/crates/emmylua_ls/src/handlers/hover/hover_builder.rs b/crates/emmylua_ls/src/handlers/hover/hover_builder.rs index 354acfe5d..d1eb4b912 100644 --- a/crates/emmylua_ls/src/handlers/hover/hover_builder.rs +++ b/crates/emmylua_ls/src/handlers/hover/hover_builder.rs @@ -1,8 +1,10 @@ use emmylua_code_analysis::{ - LuaCompilation, LuaFunctionType, LuaMember, LuaMemberOwner, LuaSemanticDeclId, LuaType, - RenderLevel, SemanticModel, + GenericTplId, LuaCompilation, LuaMember, LuaMemberOwner, LuaSemanticDeclId, LuaType, + RenderLevel, SemanticModel, TypeSubstitutor, +}; +use emmylua_parser::{ + LuaAstNode, LuaCallExpr, LuaExpr, LuaLocalName, LuaLocalStat, LuaSyntaxKind, LuaSyntaxToken, }; -use emmylua_parser::{LuaAstNode, LuaCallExpr, LuaSyntaxToken}; use lsp_types::{Hover, HoverContents, MarkedString, MarkupContent}; use crate::handlers::hover::hover_humanize::{ @@ -14,23 +16,26 @@ use super::build_hover::{add_signature_param_description, add_signature_ret_desc #[derive(Debug)] pub struct HoverBuilder<'a> { /// Type description, does not include overload - pub type_description: MarkedString, + pub primary: MarkedString, /// Full path of the class pub location_path: Option, /// Function overload signatures, with the first being the primary overload pub signature_overload: Option>, /// Annotation descriptions, including function parameters and return values pub annotation_description: Vec, - /// Type expansion, often used for alias types + /// 一些类型的完整追加显示, 通常是 @alias pub type_expansion: Option>, /// For `@see` and unknown tags tags tag_content: Option>, - pub is_completion: bool, trigger_token: Option, pub semantic_model: &'a SemanticModel<'a>, pub compilation: &'a LuaCompilation, pub detail_render_level: RenderLevel, + + pub is_completion: bool, + // 默认的泛型替换器 + pub substitutor: Option, } impl<'a> HoverBuilder<'a> { @@ -47,10 +52,16 @@ impl<'a> HoverBuilder<'a> { RenderLevel::Detailed }; + let substitutor = if let Some(token) = token.clone() { + infer_substitutor_base_type(semantic_model, token) + } else { + None + }; + Self { compilation, semantic_model, - type_description: MarkedString::String("".to_string()), + primary: MarkedString::String("".to_string()), location_path: None, signature_overload: None, annotation_description: Vec::new(), @@ -59,12 +70,12 @@ impl<'a> HoverBuilder<'a> { type_expansion: None, tag_content: None, detail_render_level, + substitutor, } } pub fn set_type_description(&mut self, type_description: String) { - self.type_description = - MarkedString::from_language_code("lua".to_string(), type_description); + self.primary = MarkedString::from_language_code("lua".to_string(), type_description); } pub fn set_location_path(&mut self, owner_member: Option<&LuaMember>) { @@ -177,43 +188,10 @@ impl<'a> HoverBuilder<'a> { } } - pub fn get_call_function(&mut self) -> Option { - if self.is_completion { - return None; - } - // 根据当前输入的参数, 匹配完全匹配的签名 - if let Some(token) = self.trigger_token.clone() - && let Some(call_expr) = token.parent()?.parent() - && LuaCallExpr::can_cast(call_expr.kind().into()) - { - let call_expr = LuaCallExpr::cast(call_expr)?; - let func = self - .semantic_model - .infer_call_expr_func(call_expr.clone(), None); - if let Some(func) = func { - // TODO: 对比参数类型确定是否完全匹配 - // 确定参数量是否与当前输入的参数数量一致, 因为`infer_call_expr_func`必然返回一个有效的类型, 即使不是完全匹配的 - let call_expr_args_count = call_expr.get_args_count(); - if let Some(mut call_expr_args_count) = call_expr_args_count { - let func_params_count = func.get_params().len(); - if !func.is_colon_define() && call_expr.is_colon_call() { - // 不是冒号定义的函数, 但是是冒号调用 - call_expr_args_count += 1; - } - if call_expr_args_count == func_params_count { - return Some((*func).clone()); - } - } - } - } - - None - } - pub fn build_hover_result(&self, range: Option) -> Option { let header = { let mut header = String::new(); - match &self.type_description { + match &self.primary { MarkedString::String(s) => { header.push_str(&format!("\n{}\n", s)); } @@ -303,4 +281,67 @@ impl<'a> HoverBuilder<'a> { pub fn get_trigger_token(&self) -> Option { self.trigger_token.clone() } + + pub fn get_call_expr(&self) -> Option { + if let Some(token) = self.trigger_token.clone() + && let Some(call_expr) = token.parent()?.parent() + && LuaCallExpr::can_cast(call_expr.kind().into()) + { + return LuaCallExpr::cast(call_expr); + } + None + } +} + +// 推断基础泛型替换器 +fn infer_substitutor_base_type( + semantic_model: &SemanticModel, + trigger_token: LuaSyntaxToken, +) -> Option { + let parent = trigger_token.parent()?; + match parent.kind().into() { + LuaSyntaxKind::LocalName => { + let target_local_name = LuaLocalName::cast(parent.clone())?; + let parent = parent.parent()?; + match parent.kind().into() { + LuaSyntaxKind::LocalStat => { + let local_stat = LuaLocalStat::cast(parent.clone())?; + let local_name_list = local_stat.get_local_name_list().collect::>(); + let value_expr_list = local_stat.get_value_exprs().collect::>(); + + for (index, name) in local_name_list.iter().enumerate() { + if target_local_name == *name { + let value_expr = value_expr_list.get(index)?; + return substitutor_form_expr(semantic_model, value_expr); + } + } + } + _ => return None, + } + } + _ => return None, + } + + None +} + +pub fn substitutor_form_expr( + semantic_model: &SemanticModel, + expr: &LuaExpr, +) -> Option { + if let LuaExpr::IndexExpr(index_expr) = expr { + let prefix_type = semantic_model + .infer_expr(index_expr.get_prefix_expr()?) + .ok()?; + let mut substitutor = TypeSubstitutor::new(); + if let LuaType::Generic(generic) = prefix_type { + for (i, param) in generic.get_params().iter().enumerate() { + substitutor.insert_type(GenericTplId::Type(i as u32), param.clone()); + } + return Some(substitutor); + } else { + return None; + } + } + None } diff --git a/crates/emmylua_ls/src/handlers/hover/hover_humanize.rs b/crates/emmylua_ls/src/handlers/hover/hover_humanize.rs index cffbee8c4..504a233ae 100644 --- a/crates/emmylua_ls/src/handlers/hover/hover_humanize.rs +++ b/crates/emmylua_ls/src/handlers/hover/hover_humanize.rs @@ -5,7 +5,9 @@ use emmylua_code_analysis::{ }; use emmylua_code_analysis::humanize_type; -use emmylua_parser::{LuaAstNode, LuaExpr, LuaIndexExpr, LuaStat, LuaSyntaxId, LuaSyntaxKind}; +use emmylua_parser::{ + LuaAstNode, LuaExpr, LuaIndexExpr, LuaStat, LuaSyntaxId, LuaSyntaxKind, LuaTableExpr, +}; use rowan::TextRange; use super::hover_builder::HoverBuilder; @@ -240,3 +242,23 @@ pub fn extract_owner_name_from_element( None } + +pub fn extract_parent_type_from_element( + semantic_model: &SemanticModel, + element_id: &InFiled, +) -> Option { + let root = semantic_model + .get_db() + .get_vfs() + .get_syntax_tree(&element_id.file_id)? + .get_red_root(); + + let node = LuaSyntaxId::to_node_at_range(&root, element_id.value)?; + let stat = LuaStat::cast(node.clone().parent()?)?; + if let LuaStat::LocalStat(_) = stat { + let table_expr = LuaTableExpr::cast(node)?; + let ty = semantic_model.infer_table_should_be(table_expr); + return ty; + } + None +} diff --git a/crates/emmylua_ls/src/handlers/hover/mod.rs b/crates/emmylua_ls/src/handlers/hover/mod.rs index 4aabcbb21..f0fb5883e 100644 --- a/crates/emmylua_ls/src/handlers/hover/mod.rs +++ b/crates/emmylua_ls/src/handlers/hover/mod.rs @@ -1,6 +1,6 @@ mod build_hover; mod find_origin; -mod function_humanize; +mod function; mod hover_builder; mod hover_humanize; mod keyword_hover; diff --git a/crates/emmylua_ls/src/handlers/test/hover_function_test.rs b/crates/emmylua_ls/src/handlers/test/hover_function_test.rs index 80d713855..9445ebae8 100644 --- a/crates/emmylua_ls/src/handlers/test/hover_function_test.rs +++ b/crates/emmylua_ls/src/handlers/test/hover_function_test.rs @@ -133,7 +133,6 @@ mod tests { let mut ws = ProviderVirtualWorkspace::new(); check!(ws.check_hover( r#" - ---@diagnostic disable: missing-return ---@class Trigger ---@class EventTypeA @@ -151,17 +150,9 @@ mod tests { ---@field event fun(self: self, event: "游戏-初始化"): Trigger ---@field event fun(self: self, event: "游戏-追帧完成"): Trigger ---@field event fun(self: self, event: "游戏-逻辑不同步"): Trigger - ---@field event fun(self: self, event: "游戏-地形预设加载完成"): Trigger - ---@field event fun(self: self, event: "游戏-结束"): Trigger - ---@field event fun(self: self, event: "游戏-暂停"): Trigger - ---@field event fun(self: self, event: "游戏-恢复"): Trigger - ---@field event fun(self: self, event: "游戏-昼夜变化"): Trigger - ---@field event fun(self: self, event: "区域-进入"): Trigger - ---@field event fun(self: self, event: "区域-离开"): Trigger - ---@field event fun(self: self, event: "游戏-http返回"): Trigger "#, VirtualHoverResult { - value: "```lua\n(method) GameA:event(event_type: EventTypeA, ...: any)\n -> Trigger\n\n```\n\n---\n\n注册引擎事件\n\n---\n\n```lua\n(method) GameA:event(event: \"游戏-初始化\") -> Trigger\n```\n\n```lua\n(method) GameA:event(event: \"游戏-追帧完成\") -> Trigger\n```\n\n```lua\n(method) GameA:event(event: \"游戏-逻辑不同步\") -> Trigger\n```\n\n```lua\n(method) GameA:event(event: \"游戏-地形预设加载完成\") -> Trigger\n```\n\n```lua\n(method) GameA:event(event: \"游戏-结束\") -> Trigger\n```\n\n```lua\n(method) GameA:event(event: \"游戏-暂停\") -> Trigger\n```\n\n```lua\n(method) GameA:event(event: \"游戏-恢复\") -> Trigger\n```\n\n```lua\n(method) GameA:event(event: \"游戏-昼夜变化\") -> Trigger\n```\n\n```lua\n(method) GameA:event(event: \"区域-进入\") -> Trigger\n```\n\n```lua\n(method) GameA:event(event: \"区域-离开\") -> Trigger\n```\n\n```lua\n(method) GameA:event(event: \"游戏-http返回\") -> Trigger\n```".to_string(), + value: "```lua\n(method) GameA:event(event_type: EventTypeA, ...: any) -> Trigger\n```\n\n---\n\n注册引擎事件\n\n---\n\n```lua\n(method) GameA:event(event: \"游戏-初始化\") -> Trigger\n```\n\n```lua\n(method) GameA:event(event: \"游戏-追帧完成\") -> Trigger\n```\n\n```lua\n(method) GameA:event(event: \"游戏-逻辑不同步\") -> Trigger\n```".to_string(), }, )); Ok(()) @@ -181,7 +172,7 @@ mod tests { end "#, VirtualHoverResult { - value: "```lua\nfunction ClosureTest.e(a: string, b: number)\n```\n\n---\n\n---\n\n```lua\n(field) ClosureTest.e(a: string, b: number)\n```".to_string(), + value: "```lua\n(field) ClosureTest.e(a: string, b: number)\n```".to_string(), }, )); Ok(()) @@ -335,7 +326,7 @@ mod tests { end "#, VirtualHoverResult { - value: "```lua\nfunction Reactive.reactive(target: T)\n -> T\n\n```".to_string(), + value: "```lua\nfunction Reactive.reactive(target: T) -> T\n```".to_string(), }, )); Ok(()) @@ -431,7 +422,7 @@ mod tests { end) "#, VirtualHoverResult { - value: "```lua\n(method) Observable:select(selector: fun(value: integer, index: integer?) -> R)\n```".to_string(), + value: "```lua\n(method) Observable:select(selector: fun(value: integer, index: integer?) -> any)\n```".to_string(), }, )); Ok(()) @@ -549,4 +540,28 @@ mod tests { )); Ok(()) } + + #[gtest] + fn test_call_1() -> Result<()> { + let mut ws = ProviderVirtualWorkspace::new(); + check!(ws.check_hover( + r#" + ---@class A + local A + ---@class B + local B + + ---@generic T + ---@param x T + function A.add(x) + end + + A.add(B) + "#, + VirtualHoverResult { + value: "```lua\nfunction A.add(x: B)\n```".to_string(), + }, + )); + Ok(()) + } }