Skip to content

Commit cd46b18

Browse files
committed
diagnostic: 增强参数与返回值的表检查, 当表字段不匹配时反向抑制参数与返回值检查
1 parent 0b5ae74 commit cd46b18

File tree

4 files changed

+143
-38
lines changed

4 files changed

+143
-38
lines changed

crates/emmylua_code_analysis/src/diagnostic/checker/assign_type_mismatch.rs

Lines changed: 44 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,9 @@ fn check_name_expr(
113113
check_table_expr(
114114
context,
115115
semantic_model,
116+
&expr,
116117
source_type.as_ref(),
117118
Some(&value_type),
118-
&expr,
119119
);
120120
}
121121

@@ -149,9 +149,9 @@ fn check_index_expr(
149149
check_table_expr(
150150
context,
151151
semantic_model,
152+
&expr,
152153
source_type.as_ref(),
153154
Some(&value_type),
154-
&expr,
155155
);
156156
}
157157
Some(())
@@ -192,35 +192,35 @@ fn check_local_stat(
192192
check_table_expr(
193193
context,
194194
semantic_model,
195+
&expr,
195196
Some(&var_type),
196197
Some(&value_type),
197-
&expr,
198198
);
199199
}
200200
}
201201
Some(())
202202
}
203203

204-
fn check_table_expr(
204+
pub fn check_table_expr(
205205
context: &mut DiagnosticContext,
206206
semantic_model: &SemanticModel,
207+
table_expr: &LuaExpr,
207208
table_type: Option<&LuaType>, // 记录的类型
208209
expr_type: Option<&LuaType>, // 实际表达式推导出的类型
209-
table_expr: &LuaExpr,
210-
) -> Option<()> {
210+
) -> Option<bool> {
211211
// 需要进行一些过滤
212212
if table_type == expr_type {
213-
return Some(());
213+
return Some(false);
214214
}
215215
let table_type = table_type?;
216216
match table_type {
217-
LuaType::Def(_) => return Some(()),
217+
LuaType::Def(_) => return Some(false),
218218
_ => {}
219219
}
220220
if let Some(table_expr) = LuaTableExpr::cast(table_expr.syntax().clone()) {
221-
check_table_expr_content(context, semantic_model, table_type, &table_expr);
221+
return check_table_expr_content(context, semantic_model, table_type, &table_expr);
222222
}
223-
Some(())
223+
Some(false)
224224
}
225225

226226
// 处理 value_expr 是 TableExpr 的情况, 但不会处理 `local a = { x = 1 }, local v = a`
@@ -229,16 +229,17 @@ fn check_table_expr_content(
229229
semantic_model: &SemanticModel,
230230
table_type: &LuaType,
231231
table_expr: &LuaTableExpr,
232-
) -> Option<()> {
232+
) -> Option<bool> {
233233
const MAX_CHECK_COUNT: usize = 250;
234234
let mut check_count = 0;
235+
let mut has_diagnostic = false;
235236

236237
let fields = table_expr.get_fields().collect::<Vec<_>>();
237238

238239
for (idx, field) in fields.iter().enumerate() {
239240
check_count += 1;
240241
if check_count > MAX_CHECK_COUNT {
241-
return Some(());
242+
return Some(has_diagnostic);
242243
}
243244
let Some(value_expr) = field.get_value_expr() else {
244245
continue;
@@ -255,12 +256,14 @@ fn check_table_expr_content(
255256
// 解开可变参数
256257
let expr_types =
257258
semantic_model.infer_expr_list_types(&vec![value_expr.clone()], None);
258-
check_table_last_variadic_type(
259+
if let Some(result) = check_table_last_variadic_type(
259260
context,
260261
semantic_model,
261262
table_type,
262263
&expr_types,
263-
);
264+
) {
265+
has_diagnostic = has_diagnostic || result;
266+
}
264267
continue;
265268
}
266269
_ => {}
@@ -283,7 +286,11 @@ fn check_table_expr_content(
283286
if source_type.is_table() || source_type.is_custom_type() {
284287
if let Some(table_expr) = LuaTableExpr::cast(value_expr.syntax().clone()) {
285288
// 检查子表
286-
check_table_expr_content(context, semantic_model, &source_type, &table_expr);
289+
if let Some(result) =
290+
check_table_expr_content(context, semantic_model, &source_type, &table_expr)
291+
{
292+
has_diagnostic = has_diagnostic || result;
293+
}
287294
continue;
288295
}
289296
}
@@ -293,25 +300,29 @@ fn check_table_expr_content(
293300
_ => false,
294301
};
295302

296-
check_assign_type_mismatch(
303+
if let Some(result) = check_assign_type_mismatch(
297304
context,
298305
semantic_model,
299306
field.get_range(),
300307
Some(&source_type),
301308
&expr_type,
302309
allow_nil,
303-
);
310+
) {
311+
has_diagnostic = has_diagnostic || result;
312+
}
304313
}
305314

306-
Some(())
315+
Some(has_diagnostic)
307316
}
308317

309318
fn check_table_last_variadic_type(
310319
context: &mut DiagnosticContext,
311320
semantic_model: &SemanticModel,
312321
table_type: &LuaType,
313322
expr_types: &[(LuaType, TextRange)],
314-
) -> Option<()> {
323+
) -> Option<bool> {
324+
let mut has_diagnostic = false;
325+
315326
for (idx, (expr_type, range)) in expr_types.iter().enumerate() {
316327
// 此时的值必然是从下标 1 开始递增的
317328
let member_key = LuaMemberKey::Integer(idx as i64 + 1);
@@ -328,16 +339,18 @@ fn check_table_last_variadic_type(
328339
expr_type.clone()
329340
};
330341

331-
check_assign_type_mismatch(
342+
if let Some(result) = check_assign_type_mismatch(
332343
context,
333344
semantic_model,
334345
*range,
335346
Some(&source_type),
336347
&expr_type,
337348
false,
338-
);
349+
) {
350+
has_diagnostic = has_diagnostic || result;
351+
}
339352
}
340-
Some(())
353+
Some(has_diagnostic)
341354
}
342355

343356
fn check_assign_type_mismatch(
@@ -347,28 +360,28 @@ fn check_assign_type_mismatch(
347360
source_type: Option<&LuaType>,
348361
value_type: &LuaType,
349362
allow_nil: bool,
350-
) -> Option<()> {
363+
) -> Option<bool> {
351364
let source_type = source_type.unwrap_or(&LuaType::Any);
352365
// 如果一致, 则不进行类型检查
353366
if source_type == value_type {
354-
return Some(());
367+
return Some(false);
355368
}
356369

357370
// 某些情况下我们应允许可空, 例如: boolean[]
358371
if allow_nil && value_type.is_nullable() {
359-
return Some(());
372+
return Some(false);
360373
}
361374

362375
match (&source_type, &value_type) {
363376
// 如果源类型是定义类型, 则仅在目标类型是定义类型或引用类型时进行类型检查
364377
(LuaType::Def(_), LuaType::Def(_) | LuaType::Ref(_)) => {}
365-
(LuaType::Def(_), _) => return Some(()),
378+
(LuaType::Def(_), _) => return Some(false),
366379
// 此时检查交给 table_field
367-
(LuaType::Ref(_) | LuaType::Tuple(_), LuaType::TableConst(_)) => return Some(()),
368-
(LuaType::Nil, _) => return Some(()),
380+
(LuaType::Ref(_) | LuaType::Tuple(_), LuaType::TableConst(_)) => return Some(false),
381+
(LuaType::Nil, _) => return Some(false),
369382
(LuaType::Ref(_), LuaType::Instance(instance)) => {
370383
if instance.get_base().is_table() {
371-
return Some(());
384+
return Some(false);
372385
}
373386
}
374387
_ => {}
@@ -384,8 +397,9 @@ fn check_assign_type_mismatch(
384397
&value_type,
385398
result,
386399
);
400+
return Some(true);
387401
}
388-
Some(())
402+
Some(false)
389403
}
390404

391405
fn add_type_check_diagnostic(

crates/emmylua_code_analysis/src/diagnostic/checker/param_type_check.rs

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,20 @@ use emmylua_parser::{LuaAst, LuaAstNode, LuaAstToken, LuaCallExpr, LuaExpr, LuaI
22
use rowan::TextRange;
33

44
use crate::{
5-
humanize_type, DiagnosticCode, LuaSemanticDeclId, LuaType, RenderLevel, SemanticDeclLevel,
6-
SemanticModel, TypeCheckFailReason, TypeCheckResult,
5+
diagnostic::checker::assign_type_mismatch::check_table_expr, humanize_type, DiagnosticCode,
6+
LuaSemanticDeclId, LuaType, RenderLevel, SemanticDeclLevel, SemanticModel, TypeCheckFailReason,
7+
TypeCheckResult,
78
};
89

910
use super::{Checker, DiagnosticContext};
1011

1112
pub struct ParamTypeCheckChecker;
1213

1314
impl Checker for ParamTypeCheckChecker {
14-
const CODES: &[DiagnosticCode] = &[DiagnosticCode::ParamTypeNotMatch];
15+
const CODES: &[DiagnosticCode] = &[
16+
DiagnosticCode::ParamTypeNotMatch,
17+
DiagnosticCode::AssignTypeMismatch,
18+
];
1519

1620
/// a simple implementation of param type check, later we will do better
1721
fn check(context: &mut DiagnosticContext, semantic_model: &SemanticModel) {
@@ -85,6 +89,35 @@ fn check_call_expr(
8589
}
8690
let result = semantic_model.type_check(&check_type, arg_type);
8791
if !result.is_ok() {
92+
// 这里执行了`AssignTypeMismatch`的检查
93+
if arg_type.is_table() {
94+
let arg_expr_idx = match (colon_call, colon_define) {
95+
(true, false) => {
96+
if idx == 0 {
97+
continue;
98+
} else {
99+
idx - 1
100+
}
101+
}
102+
_ => idx,
103+
};
104+
105+
if let Some(arg_expr) = arg_exprs.get(arg_expr_idx) {
106+
// 表字段已经报错了, 则不添加参数不匹配的诊断避免干扰
107+
if let Some(add_diagnostic) = check_table_expr(
108+
context,
109+
semantic_model,
110+
arg_expr,
111+
Some(&param_type),
112+
Some(arg_type),
113+
) {
114+
if add_diagnostic {
115+
continue;
116+
}
117+
}
118+
}
119+
}
120+
88121
try_add_diagnostic(
89122
context,
90123
semantic_model,

crates/emmylua_code_analysis/src/diagnostic/checker/return_type_mismatch.rs

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,20 @@ use emmylua_parser::{
44
use rowan::{NodeOrToken, TextRange};
55

66
use crate::{
7-
humanize_type, DiagnosticCode, LuaSemanticDeclId, LuaSignatureId, LuaType, RenderLevel,
8-
SemanticDeclLevel, SemanticModel, SignatureReturnStatus, TypeCheckFailReason, TypeCheckResult,
7+
diagnostic::checker::assign_type_mismatch::check_table_expr, humanize_type, DiagnosticCode,
8+
LuaSemanticDeclId, LuaSignatureId, LuaType, RenderLevel, SemanticDeclLevel, SemanticModel,
9+
SignatureReturnStatus, TypeCheckFailReason, TypeCheckResult,
910
};
1011

1112
use super::{get_return_stats, Checker, DiagnosticContext};
1213

1314
pub struct ReturnTypeMismatch;
1415

1516
impl Checker for ReturnTypeMismatch {
16-
const CODES: &[DiagnosticCode] = &[DiagnosticCode::ReturnTypeMismatch];
17+
const CODES: &[DiagnosticCode] = &[
18+
DiagnosticCode::ReturnTypeMismatch,
19+
DiagnosticCode::AssignTypeMismatch,
20+
];
1721

1822
fn check(context: &mut DiagnosticContext, semantic_model: &SemanticModel) {
1923
let root = semantic_model.get_root().clone();
@@ -54,9 +58,9 @@ fn check_return_stat(
5458
return_type: &LuaType,
5559
return_stat: &LuaReturnStat,
5660
) -> Option<()> {
61+
let return_exprs = return_stat.get_expr_list().collect::<Vec<_>>();
5762
let (return_expr_types, return_expr_ranges) = {
58-
let infos = semantic_model
59-
.infer_expr_list_types(&return_stat.get_expr_list().collect::<Vec<_>>(), None);
63+
let infos = semantic_model.infer_expr_list_types(&return_exprs, None);
6064
let mut return_expr_types = infos.iter().map(|(typ, _)| typ.clone()).collect::<Vec<_>>();
6165
// 解决 setmetatable 的返回值类型问题
6266
let setmetatable_index = has_setmetatable(semantic_model, return_stat);
@@ -87,6 +91,18 @@ fn check_return_stat(
8791

8892
let result = semantic_model.type_check(check_type, return_expr_type);
8993
if !result.is_ok() {
94+
if return_expr_type.is_table() {
95+
if let Some(return_expr) = return_exprs.get(index) {
96+
check_table_expr(
97+
context,
98+
semantic_model,
99+
return_expr,
100+
Some(check_type),
101+
Some(return_expr_type),
102+
);
103+
}
104+
}
105+
90106
add_type_check_diagnostic(
91107
context,
92108
semantic_model,
@@ -112,6 +128,22 @@ fn check_return_stat(
112128
let return_expr_range = return_expr_ranges[0];
113129
let result = semantic_model.type_check(check_type, &return_expr_type);
114130
if !result.is_ok() {
131+
if return_expr_type.is_table() {
132+
if let Some(return_expr) = return_exprs.get(0) {
133+
// 表字段已经报错了, 则不添加返回值不匹配的诊断避免干扰
134+
if let Some(add_diagnostic) = check_table_expr(
135+
context,
136+
semantic_model,
137+
return_expr,
138+
Some(return_type),
139+
Some(return_expr_type),
140+
) {
141+
if add_diagnostic {
142+
return Some(());
143+
}
144+
}
145+
}
146+
}
115147
add_type_check_diagnostic(
116148
context,
117149
semantic_model,

crates/emmylua_code_analysis/src/diagnostic/test/assign_type_mismatch_test.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -922,4 +922,30 @@ return t
922922
"#
923923
));
924924
}
925+
926+
#[test]
927+
fn test_param_tbale() {
928+
let mut ws = VirtualWorkspace::new();
929+
assert!(!ws.check_code_for(
930+
DiagnosticCode::AssignTypeMismatch,
931+
r#"
932+
---@class ability
933+
---@field t abilityType
934+
935+
---@enum (key) abilityType
936+
local abilityType = {
937+
passive = 1,
938+
}
939+
940+
---@param a ability
941+
function test(a)
942+
943+
end
944+
945+
test({
946+
t = ""
947+
})
948+
"#
949+
));
950+
}
925951
}

0 commit comments

Comments
 (0)