Skip to content

fix(types): register failed-assignment targets during checker error recovery - #610

Merged
nahime0 merged 4 commits into
illegalstudio:mainfrom
mirchaemanuel:fix/597-checker-assign-recovery
Jul 25, 2026
Merged

fix(types): register failed-assignment targets during checker error recovery#610
nahime0 merged 4 commits into
illegalstudio:mainfrom
mirchaemanuel:fix/597-checker-assign-recovery

Conversation

@mirchaemanuel

Copy link
Copy Markdown
Contributor

Summary

When the right-hand side of an assignment fails to type-check, the checker never
registered the assignment target in the type environment. Every later use of that
variable then produced a misleading follow-on Undefined variable: $x — burying the
one real diagnostic under noise for a variable the program clearly defines. On larger
programs a single bad expression cascaded into a wall of Undefined variable errors.

Fixes #597.

Reproduction

<?php
$x = 5;
$s = [...$x];
echo count($s);

Before: error: Spread operator requires an array (correct) plus
error: Undefined variable: $s (noise). After: only the spread error.

Root cause

In check_assign / check_typed_assign (src/types/checker/stmt_check/assignments/locals.rs)
the RHS inference error was propagated with ? before the target was inserted into the
type environment, so the symbol never existed for later statements. The per-statement
checker driver continues after an error, so each later read of the target raised a
follow-on Undefined variable.

Fix

Error recovery now poisons the failed target as Mixed (only when unbound, preserving a
valid earlier binding) before propagating the real RHS error, for both $x = ... and typed
T $x = ... forms (inference failure and declared-type mismatch). Mixed is treated as
"unknown, do not diagnose", so downstream uses (count($x), indexing, reassignment) are
accepted silently and one bad assignment no longer cascades into one error per use.
Genuinely undefined variables are still reported.

Method bodies seed their base environment from the top-level environment, so a poisoned
top-level target is recorded and dropped from that seed; otherwise, merged with a same-named
method local it would stay Mixed and spawn a spurious return-type error. This is scoped to
already-failing programs (a poison only occurs on an assignment error), so compiling programs
are unaffected — with no poisoned targets the method seed is identical to before.

Tests

Six regression tests in tests/error_tests/recovery.rs: the exact repro emits only the
spread error; many later uses still emit exactly one error; a genuinely undefined variable is
still reported; a typed-assignment failure does not cascade; a valid spread assignment still
type-checks; and a poisoned top-level target does not leak into a same-named method local.

Full error_tests suite green (1114 passed, 0 failed), including the image-builtin test that
caught the method-seed interaction during development. Focused codegen slice
class method assign green (1241 passed). cargo build clean.

https://claude.ai/code/session_01HRvbcjPHa3kAojdCjNZL5L

…ecovery

When an assignment's RHS fails to type-check, the checker never registered
the assignment target in the type environment, so every later use of that
variable produced a misleading follow-on `Undefined variable: $x` — burying
the one real diagnostic under noise for a variable the program clearly defines.

Recover by poisoning the target as `Mixed` (unknown) when its initializer
fails, for both `$x = ...` and typed `T $x = ...` forms (inference failure and
declared-type mismatch). `Mixed` is treated as "unknown, do not diagnose", so
downstream uses (`count($x)`, indexing, reassignment) are accepted silently and
one bad assignment no longer cascades into one error per later use. Genuinely
undefined variables are still reported.

Poisoned top-level targets are recorded so the method-body pass can drop them
from the environment it inherits from top level: a poisoned `Mixed` merged with
a same-named method local would otherwise stay `Mixed` and spawn a spurious
return-type error.

Claude-Session: https://claude.ai/code/session_01HRvbcjPHa3kAojdCjNZL5L
@mirchaemanuel

Copy link
Copy Markdown
Contributor Author

Two adjacent pre-existing findings, both reverified on current main + this branch, filed separately: #611 (method bodies seed their env from top-level locals — a genuine mixed top-level local still poisons a same-named method local on error-free programs; this PR only shields the recovery-poisoned case) and #612 (the same failed-RHS cascade in the remaining binding forms — list unpack and reference assignment — which can reuse this PR's recovery machinery).

nahime0 commented Jul 24, 2026

Copy link
Copy Markdown
Member

Updated this PR in place on top of current main (0fd01c162) and completed the recovery work in ff10a3111.

What changed:

  • centralized failed-binding recovery in one helper that binds only previously unbound targets as Mixed, preserving valid earlier bindings;
  • extended recovery beyond simple/typed assignments to typed mismatches, late callable-metadata errors, list unpacking, reference assignments, and static locals;
  • removed failed_assignment_targets;
  • fixed the underlying method-scope leak: method bodies now start from a PHP-local environment (superglobals plus parameters/$this), while explicit global declarations read the finalized top-level environment;
  • added focused recovery and method-scope regressions plus an Unreleased changelog entry.

This also closes the two residual gaps called out above and tracked in #611 and #612.

Local validation:

  • cargo build
  • cargo test --test error_tests — 1123 passed
  • cargo test --test codegen_tests test_method_local_is_isolated_from_same_named_top_level_local — passed
  • git diff --check

Published head: ff10a3111b91333bbf03a2d4358d43a1c3cdda89 (0 commits behind current main). The PR is mergeable; CI is running on this exact SHA. Initial build/archive jobs on macOS AArch64, Linux AArch64, and Linux x86_64 plus docs/stub sync are green; downstream shards are still queued/in progress.

@nahime0
nahime0 merged commit 0f9e96d into illegalstudio:main Jul 25, 2026
113 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:types Touches type checking, inference, or compatibility. size:s Small pull request. type:fix Corrects broken or incompatible behavior.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Checker error recovery: a failed assignment RHS leaves the target unregistered, cascading into misleading 'Undefined variable' errors

2 participants