fix: narrow $x === false guard (int|false returns as int after a throw-guard)#463
Open
chadmandoo wants to merge 4 commits into
Open
fix: narrow $x === false guard (int|false returns as int after a throw-guard)#463chadmandoo wants to merge 4 commits into
$x === false guard (int|false returns as int after a throw-guard)#463chadmandoo wants to merge 4 commits into
Conversation
…throw-guard
`if ($value === false) { throw; } return $value;` (ward-http StreamGuards::requireInt
and the string/stream guards) declared `: int` but was inferred as returning
Union([Int, Bool]) — type_guard_narrowing recognized is_*()/instanceof but not the
`$x === false` strict comparison, so the divergent-guard complement (which the if/else
narrowing already keeps after a diverging body) never stripped the false-ish member.
Recognize `$var === false` / `false === $var` (BinOp::StrictEq) → narrow to Bool in the
then-branch and strip it in the else-branch (int|false → int). Byte-parity regression test.
AIC epic illegalstudio#483 / EC-8 (illegalstudio#491), the return-over-widening sub-part.
(cherry picked from commit 7c8ac75)
…ard) (cherry picked from commit 91eb570)
`$this->raw instanceof Message ? $this->raw : new Message($this->raw)` with `private Message|string $rawMessage` failed to compile — ternary inference applied zero narrowing, and guard narrowing keyed only variables (ExprKind::Variable), never property receivers. Add branch_guard_narrowing(): recognizes variable guards (delegating to type_guard_narrowing) plus `$var->prop instanceof Class` / `$this->prop instanceof Class`, keying the narrowed type under a synthetic \x01-sigil env key that infer_property_access_type consults before falling back to the declared field type. Both ternary inference paths (pure + assignment-effects) narrow their branch envs with it. A ternary is a single expression — no intervening writes — so the property narrowing is write-invalidation-safe here by construction. Byte-parity vs PHP 8.5 (hi:k) + codegen regression test. AIC EC-8 (illegalstudio#491): clears the FieldViolation Message|string pattern across the Forms Field family. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> (cherry picked from commit fcff01d)
…tement-level
Three narrowing gaps behind ~7 AIC Domain classes:
- `if (is_null($p)) { throw; }` did not narrow ?int → int (only
`=== null` did): add is_null to the predicate map.
- `if (!$this->prop instanceof C) { throw; } return $this->prop;`
(ward-forms StoreResult::ref) kept the property at ?C: statement-level
guards now key simple property receivers via the synthetic
\x01-sigil env key, riding the existing if-chain save/restore and
divergent-complement machinery.
- The ternary property narrowing from the previous commit now shares the
same single entry point.
Mechanics: fold type_guard_narrowing + branch_guard_narrowing into
guard_narrowing(); guard_receiver_and_type() extracts (receiver expr,
target) for all guard forms (is_* / is_null / instanceof / === false /
=== null) on variables AND simple property accesses.
Soundness: property narrowings are purged after any call (the callee may
mutate the object), after assignments writing through a property, and at
loop-body entry (a later iteration may observe an earlier write) —
purge_property_narrowings() in the effects walk. A call's own argument
checking still sees the narrowing (purge happens after the call's
inference).
Byte-parity vs PHP 8.5 (5 / x) + 2 codegen regression tests. Full suite
6177 passed / 1 env-only failure (IPv6-DNS network test). AIC EC-8 (illegalstudio#491).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit 3c58b4e)
Contributor
Author
|
Pushed two follow-up commits extending the same narrowing seam: (1) property-access flow-narrowing across ternary branches ( |
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.
What
if ($v === false) { throw; } return $v;on anint|falsevalue declared: intwas rejected — the return was inferred asUnion([Int, Bool]).Why
The if/else flow-narrowing already keeps the guard complement after a diverging body (
throw), buttype_guard_narrowingonly recognizedis_*()/instanceofguards — not the$x === falsestrict comparison — so the complement never stripped the false-ishBoolmember.Fix
Recognize
$var === false/false === $var(BinOp::StrictEq): narrow toBoolin the then-branch, strip it in the else-branch (int|false→int). Byte-parity regression test; builds standalone onmain.