Skip to content

Commit 6941750

Browse files
committed
add nil safe check
1 parent d3fd56d commit 6941750

File tree

2 files changed

+113
-0
lines changed

2 files changed

+113
-0
lines changed

crates/code_analysis/src/diagnostic/checker/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ mod deprecated;
55
mod discard_returns;
66
mod local_const_reassign;
77
mod missing_parameter;
8+
mod need_check_nil;
89
mod param_type_check;
910
mod syntax_error;
1011
mod undefined_global;
@@ -48,6 +49,7 @@ pub fn check_file(
4849
check!(discard_returns);
4950
check!(await_in_sync);
5051
check!(param_type_check);
52+
check!(need_check_nil);
5153

5254
Some(())
5355
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
use emmylua_parser::{
2+
BinaryOperator, LuaAstNode, LuaBinaryExpr, LuaCallExpr, LuaExpr, LuaIndexExpr,
3+
};
4+
5+
use crate::{DiagnosticCode, SemanticModel};
6+
7+
use super::DiagnosticContext;
8+
9+
pub const CODES: &[DiagnosticCode] = &[DiagnosticCode::NeedCheckNil];
10+
11+
pub fn check(context: &mut DiagnosticContext, semantic_model: &mut SemanticModel) -> Option<()> {
12+
let root = semantic_model.get_root().clone();
13+
for expr in root.descendants::<LuaExpr>() {
14+
match expr {
15+
LuaExpr::CallExpr(call_expr) => {
16+
check_call_expr(context, semantic_model, call_expr);
17+
}
18+
LuaExpr::BinaryExpr(binary_expr) => {
19+
check_binary_expr(context, semantic_model, binary_expr);
20+
}
21+
LuaExpr::IndexExpr(index_expr) => {
22+
check_index_expr(context, semantic_model, index_expr);
23+
}
24+
_ => {}
25+
}
26+
}
27+
28+
Some(())
29+
}
30+
31+
fn check_call_expr(
32+
context: &mut DiagnosticContext,
33+
semantic_model: &mut SemanticModel,
34+
call_expr: LuaCallExpr,
35+
) -> Option<()> {
36+
let prefix = call_expr.get_prefix_expr()?;
37+
let func = semantic_model.infer_expr(prefix.clone())?;
38+
if func.is_optional() {
39+
context.add_diagnostic(
40+
DiagnosticCode::NeedCheckNil,
41+
prefix.get_range(),
42+
t!(
43+
"function %{name} may be nil",
44+
name = prefix.syntax().text()
45+
)
46+
.to_string(),
47+
None,
48+
);
49+
}
50+
51+
Some(())
52+
}
53+
54+
fn check_index_expr(
55+
context: &mut DiagnosticContext,
56+
semantic_model: &mut SemanticModel,
57+
index_expr: LuaIndexExpr,
58+
) -> Option<()> {
59+
let prefix = index_expr.get_prefix_expr()?;
60+
let prefix_type = semantic_model.infer_expr(prefix.clone())?;
61+
if prefix_type.is_optional() {
62+
context.add_diagnostic(
63+
DiagnosticCode::NeedCheckNil,
64+
prefix.get_range(),
65+
t!("%{name} may be nil", name = prefix.syntax().text()).to_string(),
66+
None,
67+
);
68+
}
69+
70+
Some(())
71+
}
72+
73+
fn check_binary_expr(
74+
context: &mut DiagnosticContext,
75+
semantic_model: &mut SemanticModel,
76+
binary_expr: LuaBinaryExpr,
77+
) -> Option<()> {
78+
let op = binary_expr.get_op_token()?.get_op();
79+
if matches!(
80+
op,
81+
BinaryOperator::OpAdd
82+
| BinaryOperator::OpSub
83+
| BinaryOperator::OpMul
84+
| BinaryOperator::OpDiv
85+
| BinaryOperator::OpMod
86+
) {
87+
let (left, right) = binary_expr.get_exprs()?;
88+
let left_type = semantic_model.infer_expr(left.clone())?;
89+
90+
if left_type.is_optional() {
91+
context.add_diagnostic(
92+
DiagnosticCode::NeedCheckNil,
93+
left.get_range(),
94+
t!("%{name} value may be nil", name = left.syntax().text()).to_string(),
95+
None,
96+
);
97+
}
98+
99+
let right_type = semantic_model.infer_expr(right.clone())?;
100+
if right_type.is_optional() {
101+
context.add_diagnostic(
102+
DiagnosticCode::NeedCheckNil,
103+
right.get_range(),
104+
t!("%{name} value may be nil", name = right.syntax().text()).to_string(),
105+
None,
106+
);
107+
}
108+
}
109+
110+
Some(())
111+
}

0 commit comments

Comments
 (0)