Repository navigation
Conversation
There was a problem hiding this comment.
Code Review
Overview
This PR refactors how inline --[[@as T]] assertions are applied. Previously, @as casts were collected as flow effects and applied to any declaration/member whose flow path passed through the comment, causing over-broad casting (e.g., casting fields of a table literal, or arguments of a call). The new approach binds the assertion to its specific target expression via inline_as_target and applies it only when the queried expression matches.
Issues & Observations
1. inline_as_target fallback may mis-target expressions (flow.rs)
owner => LuaExpr::cast(owner.syntax().clone()),The catch-all arm casts the owner AST node directly to LuaExpr. If the owner is a statement or non-expression node (e.g., LuaIfStat, LuaBlock), LuaExpr::cast returns None, silently dropping the assertion. This is likely acceptable, but the behavior is implicit. Consider documenting which owner kinds are expected, or explicitly returning None for non-expression owners to make intent clear.
2. LuaCallExprStat target ignores trailing comments on multi-call statements
LuaAst::LuaCallExprStat(stat) => stat.get_call_expr().map(LuaExpr::CallExpr),A LuaCallExprStat holds a single call expression, so this is fine. But note the comment says "its target is the last RHS value" — for call statements there is no RHS list, so the comment is slightly misleading. Minor.
3. has_tag_cast() early-return changes semantics for include_tag_casts = true
if !tree.has_tag_cast() {
return base;
}This short-circuits both TagCast and AsCast handling. Verify has_tag_cast() returns true whenever either effect kind exists. If has_tag_cast only tracks FlowEffect::TagCast (the ---@cast path op) and not AsCast, then apply_inline_as_casts would incorrectly bail out when only an @as is present. This is the highest-risk item — please confirm the flag's definition covers both effect variants.
4. Removed collect_decl_as_cast / collect_member_as_cast — verify no regression for non-expression decl paths
The old code applied @as casts during flow tracing for declarations and members. The new code only applies them via apply_inline_as_casts at expression query sites (type_of_expr_at, type_of_decl closure branch, trace_member value). Confirm that all previously-covered query paths now route through one of these entry points. In particular, trace_decl_uncached now has FlowNodeKind::AsCast(_) => {} — if a declaration's type is queried without going through type_of_decl's expression branch, the cast may be lost.
5. apply_inline_as_casts visibility is pub(super) but used across modules
It's called from types.rs (sibling module under semantic_model). pub(super) from flow.rs exposes it to semantic_model, which is correct. Just flagging for clarity — no action needed.
6. Test helper expression_type asserts exactly one match
assert_eq!(expressions.len(), 1, "expected exactly one expression: {text}");This is brittle for texts like "input" that may appear multiple times (e.g., inline_as_return_and_closure_assertions_still_apply defines input once, but inline_as_nested_assertions_keep_their_own_targets uses input once — OK). However, in inline_as_does_not_cast_unrelated_later_reads, "other" appears as both a parameter and a local read. Verify the filter expr.get_text() == text doesn't match the parameter declaration node. If it does, these tests will fail intermittently. Consider filtering by position or excluding declaration sites.
7. Behavior change: @as no longer propagates through flow to later reads
This is the intended fix (see inline_as_does_not_cast_unrelated_later_reads), but it's a semantic change that could break existing user code relying on the old (arguably buggy) behavior. Worth a changelog/release note.
Recommendations
- Verify
has_tag_cast()semantics (item 3) — add a test or comment confirming it coversAsCasteffects. - Audit all
type_of_decl/type_of_memberentry points (item 4) to ensure no path bypasses the new cast application. - Harden
expression_typetest helper (item 6) to avoid false matches on declaration nodes. - Clarify the
LuaCallExprStatcomment (item 2) since there's no RHS list for call statements. - Consider adding a regression test for a
@ason a statement whose owner is not one of the explicitly handled kinds, to lock in the fallback behavior.
Overall the refactor is well-motivated and the test coverage is thorough. The main risk is the has_tag_cast() short-circuit and ensuring no query path lost cast application after removing the flow-trace-based collection.
An inline assertion on a compound expression can replace the types of its children and unrelated later reads:
data.trainshould retain typeTrain, while the constructor/result should beFreeTrain. Flow-sensitive queries instead apply the enclosing assertion todata.train, which can produce a false assignment mismatch. The flow walker also collected every@asit passed as a cast on whichever declaration/member was being queried, contaminating unrelated later reads.Resolve the assertion's attached expression and apply it only to that expression's syntax identity. A comment attached to a statement targets its last RHS value; a table-field comment targets the field value. Stop treating
@asas an ambient declaration/member cast. Initializer and member-assignment inference explicitly retain assertions on their RHS, and name/parenthesized-expression queries apply the assertion locally. Named@castbehavior is unchanged; this fix is independent of PR #1285.Twelve regressions cover constructor fields, call arguments, parenthesized operands, unrelated later reads, valid/invalid field assignments, member assignments, multiple RHS values, nested assertions, and return/closure expressions. They verify that the intended result keeps its cast type while its source and neighboring expressions keep their own types. Additional cases query declaration/member flow APIs directly, verify the cast flag for both
@castand@as, and ensure an assertion attached to a control-flow statement is ignored.Validation: all 2,411 workspace tests with all features, workspace/all-target Clippy with warnings denied, and rustfmt pass. Eight of the nine regression tests fail on unchanged upstream main. Files without any cast nodes take the existing flow-tree flag fast path; no new general flow walk is introduced.
Review follow-up:
FlowTree::has_tag_cast()indexes bothTagCastandAsCastnodes; the new explicit test confirms that contract. The test helper traversesLuaExprnodes, so parameter/local declaration nodes cannot match; it rejects duplicate expression text instead of silently choosing a read. The query audit covers initializer and closure declarations, RHS member assignments and return expressions. Existing explicit declaration annotations keep their precedence.Behavior note:
@asdescribes the attached expression, including a value assigned to a local/member. It no longer changes unrelated variables just because their flow passes the comment. Use a targeted@cast name Typewhen changing a variable's flow type is intended. The review follow-up changes comments/tests only; all twelve focused tests, Clippy and formatting checks pass. A direct LSP reproduction confirms the false assignment warnings disappear.