Skip to content

Commit f291848

Browse files
committed
fix(flow): handle lesser array length guards
Narrow array lengths through complementary less-than branches. Preserve condition flow through explicit empty else blocks and clamp exclusive bounds at i64::MAX to avoid overflowing during inference. Fixes #1207 Assisted-by: Codex
1 parent 4bea3ae commit f291848

4 files changed

Lines changed: 84 additions & 19 deletions

File tree

crates/emmylua_code_analysis/src/compilation/analyzer/flow/bind_analyze/engine.rs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,6 @@ enum Continuation {
161161
post_if: FlowId,
162162
current: FlowId,
163163
else_block: Option<LuaBlock>,
164-
has_else_clause: bool,
165164
},
166165
/// if statement: finalize after the else block completes
167166
IfFinal { post_if: FlowId, else_label: FlowId },
@@ -764,7 +763,6 @@ impl<'a, 'b> BindEngine<'a, 'b> {
764763
let then_label = self.binder.create_branch_label();
765764
let clauses = if_stat.get_else_if_clause_list().collect::<Vec<_>>();
766765
let else_clause = if_stat.get_else_clause();
767-
let has_else_clause = else_clause.is_some();
768766
let else_block = else_clause.and_then(|clause| clause.get_block());
769767

770768
self.stack.push(Continuation::IfBranchDone {
@@ -774,7 +772,6 @@ impl<'a, 'b> BindEngine<'a, 'b> {
774772
post_if: post_if_label,
775773
current,
776774
else_block,
777-
has_else_clause,
778775
});
779776
if let Some(then_block) = if_stat.get_block() {
780777
self.stack
@@ -1114,7 +1111,6 @@ impl<'a, 'b> BindEngine<'a, 'b> {
11141111
post_if,
11151112
current,
11161113
else_block,
1117-
has_else_clause,
11181114
} => {
11191115
self.binder.add_antecedent(post_if, value);
11201116
if idx >= clauses.len() {
@@ -1128,9 +1124,7 @@ impl<'a, 'b> BindEngine<'a, 'b> {
11281124
Step::Task(Task::Block(block, else_label))
11291125
}
11301126
None => {
1131-
if !has_else_clause {
1132-
self.binder.add_antecedent(post_if, else_label);
1133-
}
1127+
self.binder.add_antecedent(post_if, else_label);
11341128
Step::Done(finalize_if(self.binder, post_if, else_label))
11351129
}
11361130
}
@@ -1146,7 +1140,6 @@ impl<'a, 'b> BindEngine<'a, 'b> {
11461140
post_if,
11471141
current,
11481142
else_block,
1149-
has_else_clause,
11501143
});
11511144
if let Some(block) = clause.get_block() {
11521145
self.stack.push(Continuation::ThenBlock { block });

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

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3155,6 +3155,57 @@ _2 = a[1]
31553155
));
31563156
}
31573157

3158+
#[test]
3159+
fn test_issue_1207_lesser_array_length_guards() {
3160+
let mut ws = VirtualWorkspace::new();
3161+
assert!(ws.has_no_diagnostic(
3162+
DiagnosticCode::AssignTypeMismatch,
3163+
r#"
3164+
local a --- @type string[]
3165+
if #a <= 1 then error() end
3166+
3167+
--- @type string
3168+
_ = a[2]
3169+
3170+
local b --- @type string[]
3171+
if #b < 2 then error() end
3172+
3173+
--- @type string
3174+
_ = b[2]
3175+
"#
3176+
));
3177+
}
3178+
3179+
#[test]
3180+
fn test_issue_1207_array_length_bound_does_not_overflow() {
3181+
let mut ws = VirtualWorkspace::new();
3182+
assert!(ws.has_no_diagnostic(
3183+
DiagnosticCode::AssignTypeMismatch,
3184+
r#"
3185+
local a --- @type string[]
3186+
if #a <= 9223372036854775807 then error() end
3187+
3188+
--- @type string
3189+
_ = a[1]
3190+
"#
3191+
));
3192+
}
3193+
3194+
#[test]
3195+
fn test_issue_1207_empty_else_preserves_array_length_guard() {
3196+
let mut ws = VirtualWorkspace::new();
3197+
assert!(ws.has_no_diagnostic(
3198+
DiagnosticCode::AssignTypeMismatch,
3199+
r#"
3200+
local a --- @type string[]
3201+
if not (#a > 1) then error() else end
3202+
3203+
--- @type string
3204+
_ = a[2]
3205+
"#
3206+
));
3207+
}
3208+
31583209
#[test]
31593210
fn test_return_cast_with_fallback() {
31603211
let mut ws = VirtualWorkspace::new();

crates/emmylua_code_analysis/src/semantic/infer/narrow/condition_flow/binary_flow.rs

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,25 +63,46 @@ pub fn get_type_at_binary_expr(
6363
right_expr,
6464
condition_flow.invert(),
6565
),
66-
BinaryOperator::OpGt => try_get_at_gt_or_ge_expr(
66+
BinaryOperator::OpGt => try_get_at_array_len_expr(
6767
db,
6868
cache,
6969
var_ref_id,
7070
flow_node,
7171
left_expr,
7272
right_expr,
7373
condition_flow,
74-
true,
74+
1,
7575
),
76-
BinaryOperator::OpGe => try_get_at_gt_or_ge_expr(
76+
BinaryOperator::OpGe => try_get_at_array_len_expr(
7777
db,
7878
cache,
7979
var_ref_id,
8080
flow_node,
8181
left_expr,
8282
right_expr,
8383
condition_flow,
84-
false,
84+
0,
85+
),
86+
// The false branches are equivalent to `>=` and `>` respectively.
87+
BinaryOperator::OpLt => try_get_at_array_len_expr(
88+
db,
89+
cache,
90+
var_ref_id,
91+
flow_node,
92+
left_expr,
93+
right_expr,
94+
condition_flow.invert(),
95+
0,
96+
),
97+
BinaryOperator::OpLe => try_get_at_array_len_expr(
98+
db,
99+
cache,
100+
var_ref_id,
101+
flow_node,
102+
left_expr,
103+
right_expr,
104+
condition_flow.invert(),
105+
1,
85106
),
86107
BinaryOperator::OpNilCoalescing => {
87108
try_get_at_nil_coalescing(db, cache, var_ref_id, left_expr, condition_flow)
@@ -177,15 +198,15 @@ fn try_get_at_eq_or_neq_expr(
177198
}
178199

179200
#[allow(clippy::too_many_arguments)]
180-
fn try_get_at_gt_or_ge_expr(
201+
fn try_get_at_array_len_expr(
181202
db: &DbIndex,
182203
cache: &mut LuaInferCache,
183204
var_ref_id: &VarRefId,
184205
flow_node: &FlowNode,
185206
left_expr: LuaExpr,
186207
right_expr: LuaExpr,
187208
condition_flow: InferConditionFlow,
188-
gt: bool,
209+
max_adjustment: i64,
189210
) -> Result<ConditionFlowAction, InferFailReason> {
190211
match left_expr {
191212
LuaExpr::UnaryExpr(unary_expr) => {
@@ -217,7 +238,7 @@ fn try_get_at_gt_or_ge_expr(
217238
expr: right_expr,
218239
resume: ExprTypeContinuation::ArrayLen {
219240
subquery_condition_flow: condition_flow,
220-
max_adjustment: if gt { 1 } else { 0 },
241+
max_adjustment,
221242
},
222243
})
223244
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -293,10 +293,10 @@ impl PendingConditionNarrow {
293293
LuaType::Array(array_type),
294294
LuaType::IntegerConst(i) | LuaType::DocIntegerConst(i),
295295
) if matches!(condition_flow, InferConditionFlow::TrueCondition) => {
296-
let new_array_type = LuaArrayType::new(
297-
array_type.get_base().clone(),
298-
LuaArrayLen::Max(*i + *max_adjustment),
299-
);
296+
// Array bounds are i64, so clamp an exclusive bound above i64::MAX.
297+
let max_len = i.saturating_add(*max_adjustment);
298+
let new_array_type =
299+
LuaArrayType::new(array_type.get_base().clone(), LuaArrayLen::Max(max_len));
300300
LuaType::Array(new_array_type.into())
301301
}
302302
_ => antecedent_type,

0 commit comments

Comments
 (0)