Skip to content

Commit 4eead02

Browse files
committed
feature: impl implementation
optimize searcher member implementation
1 parent 961502d commit 4eead02

File tree

7 files changed

+411
-7
lines changed

7 files changed

+411
-7
lines changed
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
use std::collections::HashMap;
2+
3+
use emmylua_code_analysis::{
4+
LuaCompilation, LuaDeclId, LuaMemberId, LuaSemanticDeclId, LuaType, LuaTypeDeclId,
5+
SemanticDeclLevel, SemanticModel,
6+
};
7+
use emmylua_parser::{
8+
LuaAstNode, LuaDocTagField, LuaIndexExpr, LuaStat, LuaSyntaxNode, LuaSyntaxToken,
9+
};
10+
use lsp_types::Location;
11+
12+
pub fn search_implementations(
13+
semantic_model: &SemanticModel,
14+
compilation: &LuaCompilation,
15+
token: LuaSyntaxToken,
16+
) -> Option<Vec<Location>> {
17+
let mut result = Vec::new();
18+
if let Some(semantic_decl) =
19+
semantic_model.find_decl(token.clone().into(), SemanticDeclLevel::NoTrace)
20+
{
21+
match semantic_decl {
22+
LuaSemanticDeclId::TypeDecl(type_decl_id) => {
23+
search_type_implementations(semantic_model, compilation, type_decl_id, &mut result);
24+
}
25+
LuaSemanticDeclId::Member(member_id) => {
26+
search_member_implementations(semantic_model, compilation, member_id, &mut result);
27+
}
28+
LuaSemanticDeclId::LuaDecl(decl_id) => {
29+
search_decl_implementations(semantic_model, compilation, decl_id, &mut result);
30+
}
31+
_ => {}
32+
}
33+
}
34+
35+
Some(result)
36+
}
37+
38+
pub fn search_member_implementations(
39+
semantic_model: &SemanticModel,
40+
compilation: &LuaCompilation,
41+
member_id: LuaMemberId,
42+
result: &mut Vec<Location>,
43+
) -> Option<()> {
44+
let member = semantic_model
45+
.get_db()
46+
.get_member_index()
47+
.get_member(&member_id)?;
48+
let key = member.get_key();
49+
let index_references = semantic_model
50+
.get_db()
51+
.get_reference_index()
52+
.get_index_references(&key)?;
53+
54+
let mut semantic_cache = HashMap::new();
55+
56+
let property_owner = LuaSemanticDeclId::Member(member_id);
57+
for in_filed_syntax_id in index_references {
58+
let semantic_model =
59+
if let Some(semantic_model) = semantic_cache.get_mut(&in_filed_syntax_id.file_id) {
60+
semantic_model
61+
} else {
62+
let semantic_model = compilation.get_semantic_model(in_filed_syntax_id.file_id)?;
63+
semantic_cache.insert(in_filed_syntax_id.file_id, semantic_model);
64+
semantic_cache.get_mut(&in_filed_syntax_id.file_id)?
65+
};
66+
let root = semantic_model.get_root();
67+
let node = in_filed_syntax_id.value.to_node_from_root(root.syntax())?;
68+
69+
if check_member_reference(&semantic_model, node.clone()).is_none() {
70+
continue;
71+
}
72+
73+
if !semantic_model.is_reference_to(
74+
node,
75+
property_owner.clone(),
76+
SemanticDeclLevel::default(),
77+
) {
78+
continue;
79+
}
80+
81+
let document = semantic_model.get_document();
82+
let range = in_filed_syntax_id.value.get_range();
83+
let location = document.to_lsp_location(range)?;
84+
result.push(location);
85+
}
86+
Some(())
87+
}
88+
89+
/// 检查成员引用是否符合实现
90+
fn check_member_reference(semantic_model: &SemanticModel, node: LuaSyntaxNode) -> Option<()> {
91+
match &node {
92+
expr_node if LuaIndexExpr::can_cast(expr_node.kind().into()) => {
93+
let expr = LuaIndexExpr::cast(expr_node.clone())?;
94+
let prefix_type = semantic_model
95+
.infer_expr(expr.get_prefix_expr()?.into())
96+
.ok()?;
97+
match prefix_type {
98+
LuaType::Ref(_) => {
99+
return None;
100+
}
101+
_ => {}
102+
};
103+
// 往上寻找 stat 节点
104+
let stat = expr.ancestors::<LuaStat>().next()?;
105+
match stat {
106+
LuaStat::FuncStat(_) => {
107+
return Some(());
108+
}
109+
LuaStat::AssignStat(assign_stat) => {
110+
// 判断是否在左侧
111+
let (vars, _) = assign_stat.get_var_and_expr_list();
112+
for var in vars {
113+
if var
114+
.syntax()
115+
.text_range()
116+
.contains(node.text_range().start())
117+
{
118+
return Some(());
119+
}
120+
}
121+
return None;
122+
}
123+
_ => {
124+
return None;
125+
}
126+
}
127+
}
128+
tag_field_node if LuaDocTagField::can_cast(tag_field_node.kind().into()) => {
129+
return Some(());
130+
}
131+
_ => {}
132+
}
133+
134+
Some(())
135+
}
136+
pub fn search_type_implementations(
137+
semantic_model: &SemanticModel,
138+
compilation: &LuaCompilation,
139+
type_decl_id: LuaTypeDeclId,
140+
result: &mut Vec<Location>,
141+
) -> Option<()> {
142+
let db = semantic_model.get_db();
143+
let type_index = db.get_type_index();
144+
let type_decl = type_index.get_type_decl(&type_decl_id)?;
145+
let locations = type_decl.get_locations();
146+
let mut semantic_cache = HashMap::new();
147+
for location in locations {
148+
let semantic_model = if let Some(semantic_model) = semantic_cache.get_mut(&location.file_id)
149+
{
150+
semantic_model
151+
} else {
152+
let semantic_model = compilation.get_semantic_model(location.file_id)?;
153+
semantic_cache.insert(location.file_id, semantic_model);
154+
semantic_cache.get_mut(&location.file_id)?
155+
};
156+
let document = semantic_model.get_document();
157+
let range = location.range;
158+
let location = document.to_lsp_location(range)?;
159+
result.push(location);
160+
}
161+
162+
Some(())
163+
}
164+
165+
pub fn search_decl_implementations(
166+
semantic_model: &SemanticModel,
167+
compilation: &LuaCompilation,
168+
decl_id: LuaDeclId,
169+
result: &mut Vec<Location>,
170+
) -> Option<()> {
171+
let decl = semantic_model
172+
.get_db()
173+
.get_decl_index()
174+
.get_decl(&decl_id)?;
175+
176+
if decl.is_local() {
177+
let document = semantic_model.get_document();
178+
let range = decl.get_range();
179+
let location = document.to_lsp_location(range)?;
180+
result.push(location);
181+
return Some(());
182+
} else {
183+
let name = decl.get_name();
184+
let global_decl_ids = semantic_model
185+
.get_db()
186+
.get_global_index()
187+
.get_global_decl_ids(name)?;
188+
189+
let mut semantic_cache = HashMap::new();
190+
191+
for global_decl_id in global_decl_ids {
192+
let semantic_model =
193+
if let Some(semantic_model) = semantic_cache.get_mut(&global_decl_id.file_id) {
194+
semantic_model
195+
} else {
196+
let semantic_model = compilation.get_semantic_model(global_decl_id.file_id)?;
197+
semantic_cache.insert(global_decl_id.file_id, semantic_model);
198+
semantic_cache.get_mut(&global_decl_id.file_id)?
199+
};
200+
let Some(decl) = semantic_model
201+
.get_db()
202+
.get_decl_index()
203+
.get_decl(&global_decl_id)
204+
else {
205+
continue;
206+
};
207+
208+
let document = semantic_model.get_document();
209+
let range = decl.get_range();
210+
let location = document.to_lsp_location(range)?;
211+
result.push(location);
212+
}
213+
}
214+
215+
Some(())
216+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
mod implementation_searcher;
2+
mod test;
3+
4+
use crate::context::ServerContextSnapshot;
5+
use emmylua_code_analysis::{EmmyLuaAnalysis, FileId};
6+
use emmylua_parser::LuaAstNode;
7+
use implementation_searcher::search_implementations;
8+
use lsp_types::{
9+
request::GotoImplementationParams, ClientCapabilities, GotoDefinitionResponse,
10+
ImplementationProviderCapability, Position, ServerCapabilities,
11+
};
12+
use rowan::TokenAtOffset;
13+
use tokio_util::sync::CancellationToken;
14+
15+
use super::RegisterCapabilities;
16+
17+
pub async fn on_implementation_handler(
18+
context: ServerContextSnapshot,
19+
params: GotoImplementationParams,
20+
_: CancellationToken,
21+
) -> Option<GotoDefinitionResponse> {
22+
let uri = params.text_document_position_params.text_document.uri;
23+
let analysis = context.analysis.read().await;
24+
let file_id = analysis.get_file_id(&uri)?;
25+
let position = params.text_document_position_params.position;
26+
27+
implementation(&analysis, file_id, position)
28+
}
29+
30+
pub fn implementation(
31+
analysis: &EmmyLuaAnalysis,
32+
file_id: FileId,
33+
position: Position,
34+
) -> Option<GotoDefinitionResponse> {
35+
let mut semantic_model = analysis.compilation.get_semantic_model(file_id)?;
36+
37+
let root = semantic_model.get_root();
38+
let position_offset = {
39+
let document = semantic_model.get_document();
40+
document.get_offset(position.line as usize, position.character as usize)?
41+
};
42+
43+
if position_offset > root.syntax().text_range().end() {
44+
return None;
45+
}
46+
47+
let token = match root.syntax().token_at_offset(position_offset) {
48+
TokenAtOffset::None => return None,
49+
TokenAtOffset::Single(token) => token,
50+
TokenAtOffset::Between(token, _) => token,
51+
};
52+
53+
let implementations =
54+
search_implementations(&mut semantic_model, &analysis.compilation, token)?;
55+
56+
if implementations.is_empty() {
57+
return None;
58+
}
59+
60+
Some(GotoDefinitionResponse::Array(implementations))
61+
}
62+
63+
pub struct ImplementationCapabilities;
64+
65+
impl RegisterCapabilities for ImplementationCapabilities {
66+
fn register_capabilities(server_capabilities: &mut ServerCapabilities, _: &ClientCapabilities) {
67+
server_capabilities.implementation_provider =
68+
Some(ImplementationProviderCapability::Simple(true));
69+
}
70+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
#[cfg(test)]
2+
mod tests {
3+
4+
use crate::handlers::test_lib::ProviderVirtualWorkspace;
5+
6+
#[test]
7+
fn test_1() {
8+
let mut ws = ProviderVirtualWorkspace::new();
9+
ws.def_file(
10+
"2.lua",
11+
r#"
12+
delete = require("virtual_0").delete
13+
delete()
14+
"#,
15+
);
16+
ws.def_file(
17+
"3.lua",
18+
r#"
19+
delete = require("virtual_0").delete
20+
delete()
21+
"#,
22+
);
23+
24+
ws.check_implementation(
25+
r#"
26+
local M = {}
27+
function M.de<??>lete(a)
28+
end
29+
return M
30+
"#,
31+
);
32+
}
33+
34+
#[test]
35+
fn test_2() {
36+
let mut ws = ProviderVirtualWorkspace::new();
37+
ws.def_file(
38+
"1.lua",
39+
r#"
40+
---@class (partial) Test
41+
test = {}
42+
43+
test.a = 1
44+
"#,
45+
);
46+
ws.def_file(
47+
"2.lua",
48+
r#"
49+
---@class (partial) Test
50+
test = {}
51+
test.a = 1
52+
"#,
53+
);
54+
ws.def_file(
55+
"3.lua",
56+
r#"
57+
local a = test.a
58+
"#,
59+
);
60+
ws.check_implementation(
61+
r#"
62+
t<??>est
63+
"#,
64+
);
65+
}
66+
67+
#[test]
68+
fn test_3() {
69+
let mut ws = ProviderVirtualWorkspace::new();
70+
ws.def_file(
71+
"1.lua",
72+
r#"
73+
---@class YYY
74+
---@field a number
75+
yyy = {}
76+
77+
if false then
78+
yyy.a = 1
79+
if yyy.a then
80+
end
81+
end
82+
83+
"#,
84+
);
85+
ws.check_implementation(
86+
r#"
87+
yyy.<??>a = 2
88+
"#,
89+
);
90+
}
91+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
mod implementation_test;

0 commit comments

Comments
 (0)