Skip to content

Commit e1fc069

Browse files
committed
complete basic range check
Close #524
1 parent 534d126 commit e1fc069

File tree

4 files changed

+111
-7
lines changed

4 files changed

+111
-7
lines changed

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1001,4 +1001,38 @@ end
10011001
"#,
10021002
));
10031003
}
1004+
1005+
#[test]
1006+
fn test_issue_524() {
1007+
let mut ws = VirtualWorkspace::new();
1008+
ws.def(
1009+
r#"
1010+
---@type string[]
1011+
local d = {}
1012+
1013+
if #d == 2 then
1014+
a = d[1]
1015+
b = d[2]
1016+
c = d[3]
1017+
end
1018+
1019+
for i = 1, #d do
1020+
e = d[i]
1021+
end
1022+
"#,
1023+
);
1024+
1025+
let a = ws.expr_ty("a");
1026+
let a_expected = ws.ty("string");
1027+
assert_eq!(a, a_expected);
1028+
let b = ws.expr_ty("b");
1029+
let b_expected = ws.ty("string");
1030+
assert_eq!(b, b_expected);
1031+
let c = ws.expr_ty("c");
1032+
let c_expected = ws.ty("string?");
1033+
assert_eq!(c, c_expected);
1034+
let e = ws.expr_ty("e");
1035+
let e_expected = ws.ty("string");
1036+
assert_eq!(e, e_expected);
1037+
}
10041038
}

crates/emmylua_code_analysis/src/db_index/type/type_ops/union_type.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,11 @@ pub fn union_type(source: LuaType, target: LuaType) -> LuaType {
7474
let mut left = left.into_set();
7575
let right = right.into_set();
7676
left.extend(right);
77-
LuaType::Union(LuaUnionType::from_set(left).into())
77+
match left.len() {
78+
0 => LuaType::Never,
79+
1 => left.into_iter().next().unwrap(),
80+
_ => LuaType::Union(LuaUnionType::from_set(left).into()),
81+
}
7882
}
7983

8084
// same type

crates/emmylua_code_analysis/src/semantic/infer/infer_index.rs

Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
use std::collections::HashSet;
22

3-
use emmylua_parser::{LuaExpr, LuaIndexExpr, LuaIndexKey, LuaIndexMemberExpr, PathTrait};
3+
use emmylua_parser::{
4+
LuaAstNode, LuaExpr, LuaForStat, LuaIndexExpr, LuaIndexKey, LuaIndexMemberExpr, PathTrait,
5+
UnaryOperator,
6+
};
47
use internment::ArcIntern;
58
use rowan::TextRange;
69
use smol_str::SmolStr;
@@ -13,7 +16,11 @@ use crate::{
1316
enum_variable_is_param,
1417
semantic::{
1518
generic::{instantiate_type_generic, TypeSubstitutor},
16-
infer::{infer_name::get_name_expr_var_ref_id, narrow::infer_expr_narrow_type, VarRefId},
19+
infer::{
20+
infer_name::get_name_expr_var_ref_id,
21+
narrow::{get_var_expr_var_ref_id, infer_expr_narrow_type},
22+
VarRefId,
23+
},
1724
member::get_buildin_type_map_type_id,
1825
type_check::{self, check_type_compact},
1926
InferGuard,
@@ -182,9 +189,20 @@ fn infer_array_member(
182189
db: &DbIndex,
183190
cache: &mut LuaInferCache,
184191
array_type: &LuaArrayType,
185-
index_expr: LuaIndexMemberExpr,
192+
index_member_expr: LuaIndexMemberExpr,
186193
) -> Result<LuaType, InferFailReason> {
187-
let key = index_expr.get_index_key().ok_or(InferFailReason::None)?;
194+
let key = index_member_expr
195+
.get_index_key()
196+
.ok_or(InferFailReason::None)?;
197+
let index_prefix_expr = match index_member_expr {
198+
LuaIndexMemberExpr::TableField(_) => {
199+
return Ok(array_type.get_base().clone());
200+
}
201+
_ => index_member_expr
202+
.get_prefix_expr()
203+
.ok_or(InferFailReason::None)?,
204+
};
205+
188206
match key {
189207
LuaIndexKey::Integer(i) => {
190208
if !db.get_emmyrc().strict.array_index {
@@ -222,7 +240,13 @@ fn infer_array_member(
222240
return Ok(base_type.clone());
223241
}
224242
}
225-
_ => {}
243+
_ => {
244+
if check_iter_var_range(db, cache, &expr, index_prefix_expr)
245+
.unwrap_or(false)
246+
{
247+
return Ok(base_type.clone());
248+
}
249+
}
226250
}
227251

228252
let result_type = match &base_type {
@@ -239,6 +263,48 @@ fn infer_array_member(
239263
}
240264
}
241265

266+
fn check_iter_var_range(
267+
db: &DbIndex,
268+
cache: &mut LuaInferCache,
269+
may_iter_var: &LuaExpr,
270+
prefix_expr: LuaExpr,
271+
) -> Option<bool> {
272+
let LuaExpr::NameExpr(name_expr) = may_iter_var else {
273+
return None;
274+
};
275+
276+
let decl_id = db
277+
.get_reference_index()
278+
.get_var_reference_decl(&cache.get_file_id(), name_expr.get_range())?;
279+
280+
let decl = db.get_decl_index().get_decl(&decl_id)?;
281+
let decl_syntax_id = decl.get_syntax_id();
282+
if !decl_syntax_id.is_token() {
283+
return None;
284+
}
285+
286+
let root = prefix_expr.get_root();
287+
let token = decl_syntax_id.to_token_from_root(&root)?;
288+
let parent_node = token.parent()?;
289+
let for_stat = LuaForStat::cast(parent_node)?;
290+
// get second expr
291+
let test_len_expr = for_stat.get_iter_expr().skip(1).next()?;
292+
let LuaExpr::UnaryExpr(unary_expr) = test_len_expr else {
293+
return None;
294+
};
295+
296+
let op = unary_expr.get_op_token()?;
297+
if op.get_op() != UnaryOperator::OpLen {
298+
return None;
299+
}
300+
301+
let len_expr = unary_expr.get_expr()?;
302+
let len_expr_var_ref_id = get_var_expr_var_ref_id(db, cache, len_expr)?;
303+
let prefix_expr_var_ref_id = get_var_expr_var_ref_id(db, cache, prefix_expr)?;
304+
305+
Some(len_expr_var_ref_id == prefix_expr_var_ref_id)
306+
}
307+
242308
fn infer_table_member(
243309
db: &DbIndex,
244310
cache: &mut LuaInferCache,

crates/emmylua_code_analysis/src/semantic/infer/narrow/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use crate::{
1616
use emmylua_parser::{LuaAstNode, LuaChunk, LuaExpr};
1717
pub use get_type_at_cast_flow::get_type_at_call_expr_inline_cast;
1818
pub use narrow_type::{narrow_down_type, narrow_false_or_nil, remove_false_or_nil};
19-
pub use var_ref_id::VarRefId;
19+
pub use var_ref_id::{get_var_expr_var_ref_id, VarRefId};
2020

2121
pub fn infer_expr_narrow_type(
2222
db: &DbIndex,

0 commit comments

Comments
 (0)