diff --git a/crates/emmylua_code_analysis/src/compilation/analyzer/flow/bind_analyze/engine.rs b/crates/emmylua_code_analysis/src/compilation/analyzer/flow/bind_analyze/engine.rs index b9b224aad..12b4abaf9 100644 --- a/crates/emmylua_code_analysis/src/compilation/analyzer/flow/bind_analyze/engine.rs +++ b/crates/emmylua_code_analysis/src/compilation/analyzer/flow/bind_analyze/engine.rs @@ -161,7 +161,6 @@ enum Continuation { post_if: FlowId, current: FlowId, else_block: Option, - has_else_clause: bool, }, /// if statement: finalize after the else block completes IfFinal { post_if: FlowId, else_label: FlowId }, @@ -764,7 +763,6 @@ impl<'a, 'b> BindEngine<'a, 'b> { let then_label = self.binder.create_branch_label(); let clauses = if_stat.get_else_if_clause_list().collect::>(); let else_clause = if_stat.get_else_clause(); - let has_else_clause = else_clause.is_some(); let else_block = else_clause.and_then(|clause| clause.get_block()); self.stack.push(Continuation::IfBranchDone { @@ -774,7 +772,6 @@ impl<'a, 'b> BindEngine<'a, 'b> { post_if: post_if_label, current, else_block, - has_else_clause, }); if let Some(then_block) = if_stat.get_block() { self.stack @@ -1114,7 +1111,6 @@ impl<'a, 'b> BindEngine<'a, 'b> { post_if, current, else_block, - has_else_clause, } => { self.binder.add_antecedent(post_if, value); if idx >= clauses.len() { @@ -1128,9 +1124,7 @@ impl<'a, 'b> BindEngine<'a, 'b> { Step::Task(Task::Block(block, else_label)) } None => { - if !has_else_clause { - self.binder.add_antecedent(post_if, else_label); - } + self.binder.add_antecedent(post_if, else_label); Step::Done(finalize_if(self.binder, post_if, else_label)) } } @@ -1146,7 +1140,6 @@ impl<'a, 'b> BindEngine<'a, 'b> { post_if, current, else_block, - has_else_clause, }); if let Some(block) = clause.get_block() { self.stack.push(Continuation::ThenBlock { block }); diff --git a/crates/emmylua_code_analysis/src/compilation/test/flow.rs b/crates/emmylua_code_analysis/src/compilation/test/flow.rs index 24874a1a8..e6725c5a0 100644 --- a/crates/emmylua_code_analysis/src/compilation/test/flow.rs +++ b/crates/emmylua_code_analysis/src/compilation/test/flow.rs @@ -3155,6 +3155,57 @@ _2 = a[1] )); } + #[test] + fn test_issue_1207_lesser_array_length_guards() { + let mut ws = VirtualWorkspace::new(); + assert!(ws.has_no_diagnostic( + DiagnosticCode::AssignTypeMismatch, + r#" +local a --- @type string[] +if #a <= 1 then error() end + +--- @type string +_ = a[2] + +local b --- @type string[] +if #b < 2 then error() end + +--- @type string +_ = b[2] + "# + )); + } + + #[test] + fn test_issue_1207_array_length_bound_does_not_overflow() { + let mut ws = VirtualWorkspace::new(); + assert!(ws.has_no_diagnostic( + DiagnosticCode::AssignTypeMismatch, + r#" +local a --- @type string[] +if #a <= 9223372036854775807 then error() end + +--- @type string +_ = a[1] + "# + )); + } + + #[test] + fn test_issue_1207_empty_else_preserves_array_length_guard() { + let mut ws = VirtualWorkspace::new(); + assert!(ws.has_no_diagnostic( + DiagnosticCode::AssignTypeMismatch, + r#" +local a --- @type string[] +if not (#a > 1) then error() else end + +--- @type string +_ = a[2] + "# + )); + } + #[test] fn test_return_cast_with_fallback() { let mut ws = VirtualWorkspace::new(); diff --git a/crates/emmylua_code_analysis/src/semantic/infer/narrow/condition_flow/binary_flow.rs b/crates/emmylua_code_analysis/src/semantic/infer/narrow/condition_flow/binary_flow.rs index 53c4cdc30..8b809f643 100644 --- a/crates/emmylua_code_analysis/src/semantic/infer/narrow/condition_flow/binary_flow.rs +++ b/crates/emmylua_code_analysis/src/semantic/infer/narrow/condition_flow/binary_flow.rs @@ -63,7 +63,7 @@ pub fn get_type_at_binary_expr( right_expr, condition_flow.invert(), ), - BinaryOperator::OpGt => try_get_at_gt_or_ge_expr( + BinaryOperator::OpGt => try_get_at_array_len_expr( db, cache, var_ref_id, @@ -71,9 +71,9 @@ pub fn get_type_at_binary_expr( left_expr, right_expr, condition_flow, - true, + 1, ), - BinaryOperator::OpGe => try_get_at_gt_or_ge_expr( + BinaryOperator::OpGe => try_get_at_array_len_expr( db, cache, var_ref_id, @@ -81,7 +81,28 @@ pub fn get_type_at_binary_expr( left_expr, right_expr, condition_flow, - false, + 0, + ), + // The false branches are equivalent to `>=` and `>` respectively. + BinaryOperator::OpLt => try_get_at_array_len_expr( + db, + cache, + var_ref_id, + flow_node, + left_expr, + right_expr, + condition_flow.invert(), + 0, + ), + BinaryOperator::OpLe => try_get_at_array_len_expr( + db, + cache, + var_ref_id, + flow_node, + left_expr, + right_expr, + condition_flow.invert(), + 1, ), BinaryOperator::OpNilCoalescing => { try_get_at_nil_coalescing(db, cache, var_ref_id, left_expr, condition_flow) @@ -177,7 +198,7 @@ fn try_get_at_eq_or_neq_expr( } #[allow(clippy::too_many_arguments)] -fn try_get_at_gt_or_ge_expr( +fn try_get_at_array_len_expr( db: &DbIndex, cache: &mut LuaInferCache, var_ref_id: &VarRefId, @@ -185,7 +206,7 @@ fn try_get_at_gt_or_ge_expr( left_expr: LuaExpr, right_expr: LuaExpr, condition_flow: InferConditionFlow, - gt: bool, + max_adjustment: i64, ) -> Result { match left_expr { LuaExpr::UnaryExpr(unary_expr) => { @@ -217,7 +238,7 @@ fn try_get_at_gt_or_ge_expr( expr: right_expr, resume: ExprTypeContinuation::ArrayLen { subquery_condition_flow: condition_flow, - max_adjustment: if gt { 1 } else { 0 }, + max_adjustment, }, }) } diff --git a/crates/emmylua_code_analysis/src/semantic/infer/narrow/condition_flow/mod.rs b/crates/emmylua_code_analysis/src/semantic/infer/narrow/condition_flow/mod.rs index f1b4529ce..ef3f0f9a3 100644 --- a/crates/emmylua_code_analysis/src/semantic/infer/narrow/condition_flow/mod.rs +++ b/crates/emmylua_code_analysis/src/semantic/infer/narrow/condition_flow/mod.rs @@ -293,10 +293,10 @@ impl PendingConditionNarrow { LuaType::Array(array_type), LuaType::IntegerConst(i) | LuaType::DocIntegerConst(i), ) if matches!(condition_flow, InferConditionFlow::TrueCondition) => { - let new_array_type = LuaArrayType::new( - array_type.get_base().clone(), - LuaArrayLen::Max(*i + *max_adjustment), - ); + // Array bounds are i64, so clamp an exclusive bound above i64::MAX. + let max_len = i.saturating_add(*max_adjustment); + let new_array_type = + LuaArrayType::new(array_type.get_base().clone(), LuaArrayLen::Max(max_len)); LuaType::Array(new_array_type.into()) } _ => antecedent_type,