Skip to content

Commit a25a73c

Browse files
committed
添加---@Attribute class_ctor
用于取代配置项`runtime.class_default_call`
1 parent 24cf0a4 commit a25a73c

File tree

16 files changed

+339
-14
lines changed

16 files changed

+339
-14
lines changed

crates/emmylua_code_analysis/resources/std/builtin.lua

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,3 +154,12 @@
154154
---
155155
--- Receives a string parameter for the alias name.
156156
---@attribute index_alias(name: string)
157+
158+
--- This attribute must be applied to function parameters, and the function parameter's type must be a string template generic,
159+
--- used to specify the default constructor of a class.
160+
---
161+
--- Parameters:
162+
--- - `name` - The name of the constructor
163+
--- - `strip_self` - Whether the `self` parameter can be omitted when calling the constructor, defaults to `true`
164+
--- - `return_self` - Whether the constructor is forced to return `self`, defaults to `true`
165+
---@attribute class_ctor(name: string, strip_self: boolean?, return_self: boolean?)
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
use emmylua_parser::LuaCallExpr;
2+
3+
use crate::{
4+
InferFailReason, LuaType,
5+
compilation::analyzer::{lua::LuaAnalyzer, unresolve::UnResolveClassCtor},
6+
};
7+
8+
pub fn analyze_call(analyzer: &mut LuaAnalyzer, call_expr: LuaCallExpr) -> Option<()> {
9+
let prefix_expr = call_expr.clone().get_prefix_expr()?;
10+
match analyzer.infer_expr(&prefix_expr) {
11+
Ok(expr_type) => {
12+
let LuaType::Signature(signature_id) = expr_type else {
13+
return Some(());
14+
};
15+
let signature = analyzer.db.get_signature_index().get(&signature_id)?;
16+
for (idx, param_info) in signature.param_docs.iter() {
17+
if let Some(ref attrs) = param_info.attributes {
18+
for attr in attrs.iter() {
19+
if attr.id.get_name() == "class_ctor" {
20+
let unresolve = UnResolveClassCtor {
21+
file_id: analyzer.file_id,
22+
call_expr: call_expr.clone(),
23+
signature_id: signature_id,
24+
param_idx: *idx,
25+
};
26+
analyzer
27+
.context
28+
.add_unresolve(unresolve.into(), InferFailReason::None);
29+
return Some(());
30+
}
31+
}
32+
}
33+
}
34+
}
35+
Err(_) => {}
36+
}
37+
Some(())
38+
}

crates/emmylua_code_analysis/src/compilation/analyzer/lua/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
mod call;
12
mod closure;
23
mod for_range_stat;
34
mod func_body;
@@ -22,7 +23,7 @@ use stats::{
2223

2324
use crate::{
2425
Emmyrc, FileId, InferFailReason,
25-
compilation::analyzer::AnalysisPipeline,
26+
compilation::analyzer::{AnalysisPipeline, lua::call::analyze_call},
2627
db_index::{DbIndex, LuaType},
2728
profile::Profile,
2829
semantic::infer_expr,
@@ -81,6 +82,8 @@ fn analyze_node(analyzer: &mut LuaAnalyzer, node: LuaAst) {
8182
LuaAst::LuaCallExpr(call_expr) => {
8283
if call_expr.is_setmetatable() {
8384
analyze_setmetatable(analyzer, call_expr);
85+
} else {
86+
analyze_call(analyzer, call_expr);
8487
}
8588
}
8689
_ => {}

crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/check_reason.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ pub fn check_reach_reason(
1818
match reason {
1919
InferFailReason::None
2020
| InferFailReason::FieldNotFound
21+
| InferFailReason::UnResolveOperatorCall
2122
| InferFailReason::RecursiveInfer => Some(true),
2223
InferFailReason::UnResolveDeclType(decl_id) => {
2324
let decl = db.get_decl_index().get_decl(decl_id)?;
@@ -60,6 +61,7 @@ pub fn resolve_as_any(db: &mut DbIndex, reason: &InferFailReason, loop_count: us
6061
match reason {
6162
InferFailReason::None
6263
| InferFailReason::FieldNotFound
64+
| InferFailReason::UnResolveOperatorCall
6365
| InferFailReason::RecursiveInfer => {
6466
return Some(());
6567
}

crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/mod.rs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use std::collections::HashMap;
77

88
use crate::{
99
FileId, InferFailReason, LuaMemberFeature, LuaSemanticDeclId,
10-
compilation::analyzer::AnalysisPipeline,
10+
compilation::analyzer::{AnalysisPipeline, unresolve::resolve::try_resolve_class_ctor},
1111
db_index::{DbIndex, LuaDeclId, LuaMemberId, LuaSignatureId},
1212
profile::Profile,
1313
};
@@ -201,6 +201,9 @@ fn try_resolve(
201201
UnResolve::TableField(un_resolve_table_field) => {
202202
try_resolve_table_field(db, cache, un_resolve_table_field)
203203
}
204+
UnResolve::ClassCtor(un_resolve_class_ctor) => {
205+
try_resolve_class_ctor(db, cache, un_resolve_class_ctor)
206+
}
204207
};
205208

206209
match resolve_result {
@@ -213,6 +216,12 @@ fn try_resolve(
213216
retain_unresolve.push((unresolve, InferFailReason::FieldNotFound));
214217
}
215218
}
219+
Err(InferFailReason::UnResolveOperatorCall) => {
220+
if !cache.get_config().analysis_phase.is_force() {
221+
retain_unresolve
222+
.push((unresolve, InferFailReason::UnResolveOperatorCall));
223+
}
224+
}
216225
Err(reason) => {
217226
if reason != *check_reason {
218227
changed = true;
@@ -254,6 +263,7 @@ pub enum UnResolve {
254263
ClosureParentParams(Box<UnResolveParentClosureParams>),
255264
ModuleRef(Box<UnResolveModuleRef>),
256265
TableField(Box<UnResolveTableField>),
266+
ClassCtor(Box<UnResolveClassCtor>),
257267
}
258268

259269
#[allow(dead_code)]
@@ -276,6 +286,7 @@ impl UnResolve {
276286
}
277287
UnResolve::TableField(un_resolve_table_field) => Some(un_resolve_table_field.file_id),
278288
UnResolve::ModuleRef(_) => None,
289+
UnResolve::ClassCtor(un_resolve_class_ctor) => Some(un_resolve_class_ctor.file_id),
279290
}
280291
}
281292
}
@@ -422,3 +433,17 @@ impl From<UnResolveTableField> for UnResolve {
422433
UnResolve::TableField(Box::new(un_resolve_table_field))
423434
}
424435
}
436+
437+
#[derive(Debug)]
438+
pub struct UnResolveClassCtor {
439+
pub file_id: FileId,
440+
pub call_expr: LuaCallExpr,
441+
pub signature_id: LuaSignatureId,
442+
pub param_idx: usize,
443+
}
444+
445+
impl From<UnResolveClassCtor> for UnResolve {
446+
fn from(un_resolve_class_ctor: UnResolveClassCtor) -> Self {
447+
UnResolve::ClassCtor(Box::new(un_resolve_class_ctor))
448+
}
449+
}

crates/emmylua_code_analysis/src/compilation/analyzer/unresolve/resolve.rs

Lines changed: 151 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
use std::ops::Deref;
22

3-
use emmylua_parser::{LuaAstNode, LuaAstToken, LuaExpr, LuaLocalStat, LuaTableExpr};
3+
use emmylua_parser::{
4+
LuaAstNode, LuaAstToken, LuaCallExpr, LuaExpr, LuaIndexExpr, LuaLocalStat, LuaTableExpr,
5+
};
46

57
use crate::{
6-
InFiled, InferFailReason, LuaDeclId, LuaMember, LuaMemberId, LuaMemberKey, LuaSemanticDeclId,
7-
LuaTypeCache, SignatureReturnStatus, TypeOps,
8+
InFiled, InferFailReason, LuaDeclId, LuaMember, LuaMemberId, LuaMemberInfo, LuaMemberKey,
9+
LuaOperator, LuaOperatorMetaMethod, LuaOperatorOwner, LuaSemanticDeclId, LuaTypeCache,
10+
LuaTypeDeclId, OperatorFunction, SignatureReturnStatus, TypeOps,
811
compilation::analyzer::{
912
common::{add_member, bind_type},
1013
lua::{analyze_return_point, infer_for_range_iter_expr_func},
14+
unresolve::UnResolveClassCtor,
1115
},
1216
db_index::{DbIndex, LuaMemberOwner, LuaType},
17+
find_members_with_key,
1318
semantic::{LuaInferCache, infer_expr},
1419
};
1520

@@ -246,3 +251,146 @@ pub fn try_resolve_module_ref(
246251

247252
Ok(())
248253
}
254+
255+
#[allow(unused)]
256+
pub fn try_resolve_class_ctor(
257+
db: &mut DbIndex,
258+
cache: &mut LuaInferCache,
259+
unresolve_class_ctor: &mut UnResolveClassCtor,
260+
) -> ResolveResult {
261+
let signature = db
262+
.get_signature_index()
263+
.get(&unresolve_class_ctor.signature_id)
264+
.ok_or(InferFailReason::None)?;
265+
let param_info = signature
266+
.get_param_info_by_id(unresolve_class_ctor.param_idx)
267+
.ok_or(InferFailReason::None)?;
268+
let class_ctor_use = param_info
269+
.attributes
270+
.iter()
271+
.flatten()
272+
.find(|attr| attr.id.get_name() == "class_ctor")
273+
.ok_or(InferFailReason::None)?;
274+
let LuaType::DocStringConst(target_signature_name) =
275+
class_ctor_use.args.get(0).ok_or(InferFailReason::None)?
276+
else {
277+
return Err(InferFailReason::None);
278+
};
279+
let target_type_decl_id = get_class_ctor_target_type(
280+
db,
281+
cache,
282+
&param_info.type_ref,
283+
unresolve_class_ctor.call_expr.clone(),
284+
unresolve_class_ctor.param_idx,
285+
)
286+
.ok_or(InferFailReason::None)?;
287+
let target_type = LuaType::Ref(target_type_decl_id);
288+
let member_key = LuaMemberKey::Name(target_signature_name.deref().clone());
289+
let members =
290+
find_members_with_key(db, &target_type, member_key, false).ok_or(InferFailReason::None)?;
291+
let ctor_signature_member = members.first().ok_or(InferFailReason::None)?;
292+
let strip_self = {
293+
if let Some(LuaType::DocBooleanConst(strip_self)) = class_ctor_use.args.get(1) {
294+
*strip_self
295+
} else {
296+
true
297+
}
298+
};
299+
let return_self = {
300+
if let Some(LuaType::DocBooleanConst(return_self)) = class_ctor_use.args.get(2) {
301+
*return_self
302+
} else {
303+
true
304+
}
305+
};
306+
set_signature_to_default_call(db, cache, ctor_signature_member, strip_self, return_self)
307+
.ok_or(InferFailReason::None)?;
308+
309+
Ok(())
310+
}
311+
312+
fn set_signature_to_default_call(
313+
db: &mut DbIndex,
314+
cache: &mut LuaInferCache,
315+
member_info: &LuaMemberInfo,
316+
strip_self: bool,
317+
return_self: bool,
318+
) -> Option<()> {
319+
let LuaType::Signature(signature_id) = member_info.typ else {
320+
return None;
321+
};
322+
let Some(LuaSemanticDeclId::Member(member_id)) = member_info.property_owner_id else {
323+
return None;
324+
};
325+
// 我们仍然需要再做一次判断确定是否来源于`Def`类型
326+
let root = db
327+
.get_vfs()
328+
.get_syntax_tree(&member_id.file_id)?
329+
.get_red_root();
330+
let index_expr = LuaIndexExpr::cast(member_id.get_syntax_id().to_node_from_root(&root)?)?;
331+
let prefix_expr = index_expr.get_prefix_expr()?;
332+
let prefix_type = infer_expr(db, cache, prefix_expr.clone()).ok()?;
333+
let LuaType::Def(decl_id) = prefix_type else {
334+
return None;
335+
};
336+
// 如果已经存在显式的`__call`定义, 则不添加
337+
let call = db.get_operator_index().get_operators(
338+
&LuaOperatorOwner::Type(decl_id.clone()),
339+
LuaOperatorMetaMethod::Call,
340+
);
341+
if call.is_some() {
342+
return None;
343+
}
344+
345+
let operator = LuaOperator::new(
346+
decl_id.into(),
347+
LuaOperatorMetaMethod::Call,
348+
member_id.file_id,
349+
// 必须指向名称, 使用 index_expr 的完整范围不会跳转到函数上
350+
index_expr.get_name_token()?.syntax().text_range(),
351+
OperatorFunction::DefaultClassCtor {
352+
id: signature_id,
353+
strip_self,
354+
return_self,
355+
},
356+
);
357+
db.get_operator_index_mut().add_operator(operator);
358+
Some(())
359+
}
360+
361+
fn get_class_ctor_target_type(
362+
db: &DbIndex,
363+
cache: &mut LuaInferCache,
364+
param_type: &LuaType,
365+
call_expr: LuaCallExpr,
366+
call_index: usize,
367+
) -> Option<LuaTypeDeclId> {
368+
match param_type {
369+
LuaType::StrTplRef(str_tpl) => {
370+
let name = {
371+
let arg_expr = call_expr
372+
.get_args_list()?
373+
.get_args()
374+
.nth(call_index)?
375+
.clone();
376+
let name = infer_expr(db, cache, arg_expr).ok()?;
377+
match name {
378+
LuaType::StringConst(s) => s.to_string(),
379+
_ => return None,
380+
}
381+
};
382+
383+
let prefix = str_tpl.get_prefix();
384+
let suffix = str_tpl.get_suffix();
385+
let type_decl_id: LuaTypeDeclId =
386+
LuaTypeDeclId::new(format!("{}{}{}", prefix, name, suffix).as_str());
387+
let type_decl = db.get_type_index().get_type_decl(&type_decl_id)?;
388+
if type_decl.is_class() {
389+
return Some(type_decl_id);
390+
}
391+
}
392+
_ => {}
393+
}
394+
395+
None
396+
}

crates/emmylua_code_analysis/src/compilation/test/attribute_test.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,50 @@ mod test {
6464
"#,
6565
);
6666
}
67+
68+
#[test]
69+
fn test_class_ctor() {
70+
let mut ws = VirtualWorkspace::new();
71+
72+
ws.def_files(vec![
73+
(
74+
"init.lua",
75+
r#"
76+
A = meta("A")
77+
"#,
78+
),
79+
(
80+
"meta.lua",
81+
r#"
82+
---@attribute class_ctor(name: string, strip_self: boolean?, return_self: boolean?)
83+
84+
---@generic T
85+
---@param [class_ctor("__init")] name `T`
86+
---@return T
87+
function meta(name)
88+
end
89+
"#,
90+
),
91+
]);
92+
93+
// ws.def(
94+
// r#"
95+
// ---@attribute class_ctor(name: string, strip_self: boolean?, return_self: boolean?)
96+
97+
// ---@generic T
98+
// ---@param [class_ctor("__init")] name `T`
99+
// ---@return T
100+
// function meta(name)
101+
// end
102+
// "#,
103+
// );
104+
// ws.def(
105+
// r#"
106+
// A = meta("A")
107+
108+
// function A:__init(a)
109+
// end
110+
// "#,
111+
// );
112+
}
67113
}

0 commit comments

Comments
 (0)