Skip to content

Commit 3da38e7

Browse files
committed
add meta __call hint
1 parent df5ac21 commit 3da38e7

File tree

8 files changed

+151
-4
lines changed

8 files changed

+151
-4
lines changed

crates/emmylua_code_analysis/resources/schema.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
"enable": true,
7777
"indexHint": true,
7878
"localHint": true,
79+
"metaCallHint": true,
7980
"overrideHint": true,
8081
"paramHint": true
8182
},
@@ -741,6 +742,11 @@
741742
"default": true,
742743
"type": "boolean"
743744
},
745+
"metaCallHint": {
746+
"description": "Whether to enable meta __call operator hints.",
747+
"default": true,
748+
"type": "boolean"
749+
},
744750
"overrideHint": {
745751
"description": "Whether to enable override hints.",
746752
"default": true,

crates/emmylua_code_analysis/src/config/configs/inlayhint.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ pub struct EmmyrcInlayHint {
2020
/// Whether to enable override hints.
2121
#[serde(default = "default_true")]
2222
pub override_hint: bool,
23+
/// Whether to enable meta __call operator hints.
24+
#[serde(default = "default_true")]
25+
pub meta_call_hint: bool,
2326
}
2427

2528
impl Default for EmmyrcInlayHint {
@@ -30,6 +33,7 @@ impl Default for EmmyrcInlayHint {
3033
index_hint: default_true(),
3134
local_hint: default_true(),
3235
override_hint: default_true(),
36+
meta_call_hint: default_true(),
3337
}
3438
}
3539
}

crates/emmylua_code_analysis/src/db_index/operators/lua_operator.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,10 @@ impl LuaOperator {
164164
position: self.range.start(),
165165
}
166166
}
167+
168+
pub fn get_range(&self) -> TextRange {
169+
self.range
170+
}
167171
}
168172

169173
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]

crates/emmylua_code_analysis/src/semantic/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use emmylua_parser::{
2222
use infer::{infer_bind_value_type, infer_multi_value_adjusted_expression_types};
2323
pub use infer::{infer_table_field_value_should_be, infer_table_should_be};
2424
use lsp_types::Uri;
25+
pub use member::find_index_operations;
2526
pub use member::get_member_map;
2627
pub use member::LuaMemberInfo;
2728
use member::{find_member_origin_owner, find_members};

crates/emmylua_ls/src/handlers/definition/goto_function.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ fn get_signature_functions(signature: &LuaSignature) -> Vec<Arc<LuaFunctionType>
9696
}
9797

9898
/// 比较函数类型是否匹配, 会处理泛型情况
99-
fn compare_function_types(
99+
pub fn compare_function_types(
100100
semantic_model: &SemanticModel,
101101
call_function: &LuaFunctionType,
102102
func: &Arc<LuaFunctionType>,

crates/emmylua_ls/src/handlers/definition/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use emmylua_parser::{
1010
pub use goto_def_definition::goto_def_definition;
1111
use goto_def_definition::goto_str_tpl_ref_definition;
1212
pub use goto_doc_see::goto_doc_see;
13+
pub use goto_function::compare_function_types;
1314
pub use goto_module_file::goto_module_file;
1415
use lsp_types::{
1516
ClientCapabilities, GotoDefinitionParams, GotoDefinitionResponse, OneOf, Position,

crates/emmylua_ls/src/handlers/inlay_hint/build_inlay_hint.rs

Lines changed: 117 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use std::collections::HashMap;
22

33
use emmylua_code_analysis::{
4-
FileId, InferGuard, LuaFunctionType, LuaMemberId, LuaMemberKey, LuaSemanticDeclId, LuaType,
5-
SemanticModel,
4+
FileId, InferGuard, LuaFunctionType, LuaMemberId, LuaMemberKey, LuaOperatorId,
5+
LuaOperatorMetaMethod, LuaSemanticDeclId, LuaType, SemanticModel,
66
};
77
use emmylua_parser::{
88
LuaAst, LuaAstNode, LuaCallExpr, LuaExpr, LuaFuncStat, LuaIndexExpr, LuaLocalFuncStat,
@@ -14,6 +14,7 @@ use rowan::NodeOrToken;
1414

1515
use rowan::TokenAtOffset;
1616

17+
use crate::handlers::definition::compare_function_types;
1718
use crate::handlers::inlay_hint::build_function_hint::{build_closure_hint, build_label_parts};
1819

1920
pub fn build_inlay_hints(semantic_model: &SemanticModel) -> Option<Vec<InlayHint>> {
@@ -26,7 +27,8 @@ pub fn build_inlay_hints(semantic_model: &SemanticModel) -> Option<Vec<InlayHint
2627
}
2728
LuaAst::LuaCallExpr(call_expr) => {
2829
build_call_expr_param_hint(semantic_model, &mut result, call_expr.clone());
29-
build_call_expr_await_hint(semantic_model, &mut result, call_expr);
30+
build_call_expr_await_hint(semantic_model, &mut result, call_expr.clone());
31+
build_call_expr_meta_call_hint(semantic_model, &mut result, call_expr);
3032
}
3133
LuaAst::LuaLocalName(local_name) => {
3234
build_local_name_hint(semantic_model, &mut result, local_name);
@@ -458,3 +460,115 @@ fn get_override_lsp_location(
458460
let lsp_range = document.to_lsp_location(range)?;
459461
Some(lsp_range)
460462
}
463+
464+
fn build_call_expr_meta_call_hint(
465+
semantic_model: &SemanticModel,
466+
result: &mut Vec<InlayHint>,
467+
call_expr: LuaCallExpr,
468+
) -> Option<()> {
469+
if !semantic_model.get_emmyrc().hint.meta_call_hint {
470+
return Some(());
471+
}
472+
473+
let prefix_expr = call_expr.get_prefix_expr()?;
474+
let semantic_info =
475+
semantic_model.get_semantic_info(NodeOrToken::Node(prefix_expr.syntax().clone()))?;
476+
477+
match &semantic_info.typ {
478+
LuaType::Ref(id) | LuaType::Def(id) => {
479+
let decl = semantic_model.get_db().get_type_index().get_type_decl(id)?;
480+
if !decl.is_class() {
481+
return Some(());
482+
}
483+
484+
let call_operator_ids = semantic_model
485+
.get_db()
486+
.get_operator_index()
487+
.get_operators(&id.clone().into(), LuaOperatorMetaMethod::Call)?;
488+
let call_operator_parts =
489+
get_meta_call_part(semantic_model, call_operator_ids, call_expr)?;
490+
if call_operator_parts.is_empty() {
491+
return Some(());
492+
}
493+
494+
let hint_position = {
495+
let range = prefix_expr.get_range();
496+
let document = semantic_model.get_document();
497+
let lsp_range = document.to_lsp_range(range)?;
498+
lsp_range.end
499+
};
500+
501+
let hint = InlayHint {
502+
kind: Some(InlayHintKind::TYPE),
503+
label: InlayHintLabel::LabelParts(call_operator_parts),
504+
position: hint_position,
505+
text_edits: None,
506+
tooltip: None,
507+
padding_left: None,
508+
padding_right: Some(true),
509+
data: None,
510+
};
511+
result.push(hint);
512+
}
513+
_ => {}
514+
}
515+
Some(())
516+
}
517+
518+
fn get_meta_call_part(
519+
semantic_model: &SemanticModel,
520+
operator_ids: &Vec<LuaOperatorId>,
521+
call_expr: LuaCallExpr,
522+
) -> Option<Vec<InlayHintLabelPart>> {
523+
let call_func = semantic_model.infer_call_expr_func(call_expr.clone(), None)?;
524+
525+
let mut parts = Vec::new();
526+
for (idx, operator_id) in operator_ids.iter().enumerate() {
527+
let operator = semantic_model
528+
.get_db()
529+
.get_operator_index()
530+
.get_operator(operator_id)?;
531+
let operator_func = {
532+
let operator_type = operator.get_operator_func(semantic_model.get_db());
533+
match operator_type {
534+
LuaType::DocFunction(func) => func,
535+
LuaType::Signature(signature_id) => {
536+
let signature = semantic_model
537+
.get_db()
538+
.get_signature_index()
539+
.get(&signature_id)?;
540+
signature.to_doc_func_type()
541+
}
542+
_ => return None,
543+
}
544+
};
545+
let is_match =
546+
compare_function_types(semantic_model, &call_func, &operator_func, &call_expr)
547+
.unwrap_or(false);
548+
549+
// 不匹配且不是第一个, 跳过
550+
if !is_match && idx != 0 {
551+
continue;
552+
}
553+
// 清空
554+
parts.clear();
555+
556+
let location = {
557+
let range = operator.get_range();
558+
let document = semantic_model.get_document_by_file_id(operator.get_file_id())?;
559+
let lsp_range = document.to_lsp_range(range)?;
560+
Location::new(document.get_uri(), lsp_range)
561+
};
562+
563+
parts.push(InlayHintLabelPart {
564+
value: ": call".to_string(),
565+
location: Some(location),
566+
..Default::default()
567+
});
568+
569+
if is_match {
570+
return Some(parts);
571+
}
572+
}
573+
Some(parts)
574+
}

crates/emmylua_ls/src/handlers/test/inlay_hint_test.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,21 @@ mod tests {
9191
.unwrap();
9292
assert!(result.is_empty());
9393
}
94+
95+
#[test]
96+
fn test_meta_call_hint() {
97+
let mut ws = ProviderVirtualWorkspace::new();
98+
let result = ws
99+
.check_inlay_hint(
100+
r#"
101+
---@class Hint1
102+
---@overload fun(a: string): Hint1
103+
local Hint1
104+
105+
local a = Hint1("a")
106+
"#,
107+
)
108+
.unwrap();
109+
assert!(result.len() == 4);
110+
}
94111
}

0 commit comments

Comments
 (0)