Skip to content

Commit eee5b44

Browse files
committed
add meta __call hint
1 parent df5ac21 commit eee5b44

File tree

8 files changed

+191
-4
lines changed

8 files changed

+191
-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: 157 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use std::collections::HashMap;
2+
use std::sync::Arc;
23

34
use emmylua_code_analysis::{
4-
FileId, InferGuard, LuaFunctionType, LuaMemberId, LuaMemberKey, LuaSemanticDeclId, LuaType,
5-
SemanticModel,
5+
FileId, InferGuard, LuaFunctionType, LuaMemberId, LuaMemberKey, LuaOperatorId,
6+
LuaOperatorMetaMethod, LuaSemanticDeclId, LuaType, SemanticModel,
67
};
78
use emmylua_parser::{
89
LuaAst, LuaAstNode, LuaCallExpr, LuaExpr, LuaFuncStat, LuaIndexExpr, LuaLocalFuncStat,
@@ -14,6 +15,7 @@ use rowan::NodeOrToken;
1415

1516
use rowan::TokenAtOffset;
1617

18+
use crate::handlers::definition::compare_function_types;
1719
use crate::handlers::inlay_hint::build_function_hint::{build_closure_hint, build_label_parts};
1820

1921
pub fn build_inlay_hints(semantic_model: &SemanticModel) -> Option<Vec<InlayHint>> {
@@ -26,7 +28,8 @@ pub fn build_inlay_hints(semantic_model: &SemanticModel) -> Option<Vec<InlayHint
2628
}
2729
LuaAst::LuaCallExpr(call_expr) => {
2830
build_call_expr_param_hint(semantic_model, &mut result, call_expr.clone());
29-
build_call_expr_await_hint(semantic_model, &mut result, call_expr);
31+
build_call_expr_await_hint(semantic_model, &mut result, call_expr.clone());
32+
build_call_expr_meta_call_hint(semantic_model, &mut result, call_expr);
3033
}
3134
LuaAst::LuaLocalName(local_name) => {
3235
build_local_name_hint(semantic_model, &mut result, local_name);
@@ -458,3 +461,154 @@ fn get_override_lsp_location(
458461
let lsp_range = document.to_lsp_location(range)?;
459462
Some(lsp_range)
460463
}
464+
465+
fn build_call_expr_meta_call_hint(
466+
semantic_model: &SemanticModel,
467+
result: &mut Vec<InlayHint>,
468+
call_expr: LuaCallExpr,
469+
) -> Option<()> {
470+
if !semantic_model.get_emmyrc().hint.meta_call_hint {
471+
return Some(());
472+
}
473+
474+
let prefix_expr = call_expr.get_prefix_expr()?;
475+
let semantic_info =
476+
semantic_model.get_semantic_info(NodeOrToken::Node(prefix_expr.syntax().clone()))?;
477+
478+
match &semantic_info.typ {
479+
LuaType::Ref(id) | LuaType::Def(id) => {
480+
let decl = semantic_model.get_db().get_type_index().get_type_decl(id)?;
481+
if !decl.is_class() {
482+
return Some(());
483+
}
484+
485+
let call_operator_ids = semantic_model
486+
.get_db()
487+
.get_operator_index()
488+
.get_operators(&id.clone().into(), LuaOperatorMetaMethod::Call)?;
489+
490+
set_meta_call_part(
491+
semantic_model,
492+
result,
493+
call_operator_ids,
494+
call_expr,
495+
semantic_info.typ,
496+
)?;
497+
}
498+
_ => {}
499+
}
500+
Some(())
501+
}
502+
503+
fn set_meta_call_part(
504+
semantic_model: &SemanticModel,
505+
result: &mut Vec<InlayHint>,
506+
operator_ids: &Vec<LuaOperatorId>,
507+
call_expr: LuaCallExpr,
508+
target_type: LuaType,
509+
) -> Option<()> {
510+
let (operator_id, call_func) =
511+
find_match_meta_call_operator_id(semantic_model, operator_ids, call_expr.clone())?;
512+
513+
let operator = semantic_model
514+
.get_db()
515+
.get_operator_index()
516+
.get_operator(&operator_id)?;
517+
518+
let location = {
519+
let range = operator.get_range();
520+
let document = semantic_model.get_document_by_file_id(operator.get_file_id())?;
521+
let lsp_range = document.to_lsp_range(range)?;
522+
Location::new(document.get_uri(), lsp_range)
523+
};
524+
525+
let document = semantic_model.get_document();
526+
let parent = call_expr.syntax().parent()?;
527+
528+
// 如果是 `Class(...)` 且调用返回值是 Class 类型, 则显示 `new` 提示
529+
let hint_new = {
530+
LuaStat::can_cast(parent.kind().into())
531+
&& !matches!(call_expr.get_prefix_expr()?, LuaExpr::CallExpr(_))
532+
&& semantic_model
533+
.type_check(call_func.get_ret(), &target_type)
534+
.is_ok()
535+
};
536+
537+
let (value, hint_range, padding_right) = if hint_new {
538+
("new".to_string(), call_expr.get_range(), Some(true))
539+
} else {
540+
(
541+
"⚡".to_string(),
542+
call_expr.get_prefix_expr()?.get_range(),
543+
None,
544+
)
545+
};
546+
547+
let hint_position = {
548+
let lsp_range = document.to_lsp_range(hint_range)?;
549+
if hint_new {
550+
lsp_range.start
551+
} else {
552+
lsp_range.end
553+
}
554+
};
555+
556+
let part = InlayHintLabelPart {
557+
value,
558+
location: Some(location),
559+
..Default::default()
560+
};
561+
562+
let hint = InlayHint {
563+
kind: Some(InlayHintKind::TYPE),
564+
label: InlayHintLabel::LabelParts(vec![part]),
565+
position: hint_position,
566+
text_edits: None,
567+
tooltip: None,
568+
padding_left: None,
569+
padding_right,
570+
data: None,
571+
};
572+
573+
result.push(hint);
574+
Some(())
575+
}
576+
577+
fn find_match_meta_call_operator_id(
578+
semantic_model: &SemanticModel,
579+
operator_ids: &Vec<LuaOperatorId>,
580+
call_expr: LuaCallExpr,
581+
) -> Option<(LuaOperatorId, Arc<LuaFunctionType>)> {
582+
let call_func = semantic_model.infer_call_expr_func(call_expr.clone(), None)?;
583+
if operator_ids.len() == 1 {
584+
return Some((operator_ids.first().cloned()?, call_func));
585+
}
586+
for operator_id in operator_ids {
587+
let operator = semantic_model
588+
.get_db()
589+
.get_operator_index()
590+
.get_operator(operator_id)?;
591+
let operator_func = {
592+
let operator_type = operator.get_operator_func(semantic_model.get_db());
593+
match operator_type {
594+
LuaType::DocFunction(func) => func,
595+
LuaType::Signature(signature_id) => {
596+
let signature = semantic_model
597+
.get_db()
598+
.get_signature_index()
599+
.get(&signature_id)?;
600+
signature.to_doc_func_type()
601+
}
602+
_ => return None,
603+
}
604+
};
605+
let is_match =
606+
compare_function_types(semantic_model, &call_func, &operator_func, &call_expr)
607+
.unwrap_or(false);
608+
609+
if is_match {
610+
return Some((operator_id.clone(), operator_func));
611+
}
612+
}
613+
operator_ids.first().cloned().map(|id| (id, call_func))
614+
}

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)