Repository navigation
A for body sees the variable its condition narrows - #514
Merged
Merged
Conversation
After `s.kind === "sq"` narrows `s: Sq | Ci` to `Sq`, `s = { kind: "ci",
r: 1 }` was generated with the narrowed `Sq` as its receiver type: the
literal became a `{ kind: "sq", r }` and nothing was stored, with no
error. An `if` kept "sq", and a `while` that wrote the variable to end
the loop never ended (or crashed the compiler).
mlirGenSaveLogic now takes the receiver type of a narrowed variable (a
SafeCastOp) from the value it narrows, so the literal is typed as the
variable is declared. The store already saw through the SafeCastOp.
A variable on the right, or a literal of the narrowed member, was
already right.
00narrowed_assign_other_member.ts: the other member assigned in an if
and in a while (which must end), the same member, and the other
member's field read after the if. On main the first assertion fails.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mlirGen(WhileStatement) narrows the tested variable for its body
(checkSafeCast); mlirGen(ForStatement) did not, so
`for (; typeof v === "string"; v = 1) { v.length }` failed with "Can't
resolve property 'length' of type number", where the same `while`
compiled. A type guard (`isStr(v)`) failed the same way.
The body is now generated in a scope of its own that narrows as a while
body does. The scope ends before the incrementor, which assigns the
variable as it is declared (`node = node.next`). The narrowing reads
the condition before its boolean cast, as mlirGen(IfStatement) does, so
a type guard's predicate still narrows. A condition known to be false
narrows nothing, as in an if; the body is still generated, so a `var`
in it is declared.
Writing the narrowed variable in the body relies on the previous
commit: `for (; s.kind === "sq"; ) { s = { kind: "ci", r: 1 } }` works
on main because the for did not narrow, and would otherwise have never
ended.
00for_condition_narrowing.ts: typeof with the incrementor or the body
writing the variable, a type guard, instanceof, a discriminant with the
body writing the other member, an optional list walk (with continue,
and nested), a condition known to be false, and a var in an unbraced
body. On main it fails to compile.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A
whilebody sees the variable its condition narrows; aforbody did not. Sofor (; typeof v === "string"; v = 1) { v.length }failed with "Can't resolve property 'length' of type number", and so did a type-guard condition (isStr(v)). The samewhileloops compiled. This is the narrowing gap noted in #510.Two commits. The first is a fix the second needs.
1. An object literal assigned to a narrowed variable is typed as declared
After
s.kind === "sq"narrowss: Sq | CitoSq,s = { kind: "ci", r: 1 }was generated with the narrowedSqas its receiver type. The literal became a{ kind: "sq", r }and nothing was stored, with no error:if,skept"sq".while, a write meant to end the loop never ended it, or crashed the compiler.mlirGenSaveLogicnow takes a narrowed variable's (SafeCastOp) receiver type from the value it narrows. The store already saw through theSafeCastOp.This had to come first:
for (; s.kind === "sq"; ) { s = { kind: "ci", r: 1 } }works on main because thefordoes not narrow. Narrowing alone would have turned it into an infinite loop.Test:
00narrowed_assign_other_member.ts(if, while, same member, and the other member's field read afterwards). On main the first assertion fails.2. A for body sees the variable its condition narrows
The body is generated in a scope of its own that narrows as a
whilebody does (checkSafeCast). That scope ends before the incrementor, which assigns the variable as declared (node = node.next). Two differences fromwhile:ifdoes, so a type guard's predicate still narrows.varin it is still declared.Test:
00for_condition_narrowing.tscovers:typeof, with the incrementor and with the body writing the variableinstanceofcontinueand nestedvarin an unbraced bodyOn main it fails to compile.
Both tests are registered for compile, JIT and the rc/none corpus. They pass under gc, rc and none, with and without
--opt.00for_optional_class_condition.ts(#510) still passes.ctest -C Release: 3876/3876 passed (gtest unittests not built in this tree).Found on the way, filed separately
-mm=rc, reassigning a parameter inside a loop whose condition isx instanceof Acrashes at run time. It also crashes before this PR. The test'sinstanceofcase loops over a local copy because of it.let s: Sq; s = { kind: "ci", r: 1 }) compiles with no error and assigns nothing. Commit 1 fixes the narrowed case, where the assignment is valid. It does not fix this silent path.-mm=ownrejects the newfortest because it assigns to parameters, which own does not support yet. The test is not registered under own.🤖 Generated with Claude Code