Skip to content

steins-infer: PHP's compile-time return fatals are return.compile-fatal (#952, #1051 S3) - #1059

Merged
zonuexe merged 4 commits into
masterfrom
claude/ta-s3-compile-fatal-returns
Oct 11, 2026
Merged

zonuexe merged 4 commits into
masterfrom
claude/ta-s3-compile-fatal-returns

Conversation

@zonuexe

@zonuexe zonuexe commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Slice S3 of the type/arity reproduction run (#1051). Fixes #952 k1/k2: PHP's three compile-time return fatals are now reported under one new Proof/Default id, return.compile-fatal (decision D5).

What changed

  • IR (steins-syntax): a scope lists its return statements with the form PHP's compiler tells apart: bare, valued, or arrow body. The list is read off the syntax tree, so dead code and opaque constructs are covered. Nested function-likes are not descended into, and the list is empty when the scope has no written return type.
  • Pass (crates/steins-infer/src/compile_fatal_returns.rs): judges each listed return against the scope's written return type. It needs no env, no folder and no dead-region filter. The full rule table is in the module's doc comment.
  • Id: return.compile-fatal is Proof/Default. The message quotes PHP's own sentence.
  • Docs: an ADR-0078 amendment (PENDING), the trace-IR spec, the profile counts in the user manual, and a changelog fragment.

Rule table (every row run on PHP 8.5.11)

scope type statement verdict
: void (function, method, closure or arrow fn) return <expr>;, return null; included fires: "A void function must not return a value"
: never any return fires: "A never-returning function must not return"
fn(): never => 5 arrow body silent: PHP compiles it
any other written type (17 tried, including null, false, true, static, self, mixed and unions) bare return; fires: "A function with return type must return a value"
: int return null; silent for this id; stays type.return-mismatch
constructor or destructor return 5; silent
nested closure any judged by its own type, since the rule is per scope
generator, property hook, ?void, int|void — declined (different fatal, or out of scope)
trait, enum or anonymous-class method — silent until S8 gives them scopes

Tests

  • compile_fatal_returns.rs has 30 tests, at least one per row.
  • union_and_return::return_without_value_is_silent used a bare return; under : int. That is now this id's finding, so the test now asserts only return.compile-fatal.
  • The syntax, infer, cli and xtask suites pass. Workspace clippy and rustdoc pass with -D warnings, and so does xtask changelog.

Measurement (ten public packages)

  • Strict with vendor diagnostics, and default: byte-identical, with 0 return.compile-fatal hits (vendor included).
  • effect-diff: 0 events.

Not measured

  • The private half of the local fp-gate. The orchestrator runs it before merge.

Review outcome

Adversarial review: one blocker, now fixed.

  • Blocker (false positive): a return inside a property hook, in a class nested in a typed scope, was charged to the enclosing scope. Fixed in 917f50d5. The function-like boundary is now one helper (opens_function_like: function, method, closure, arrow fn, property hook), shared by scan_returns and node_is_generator. Regression tests cover hooks in named and anonymous classes inside void and typed scopes, and a yield in a hook.
  • Re-check on the fixed head (73 witnesses, each run through php -l): no witness fires where PHP compiles. The only PHP fatals Steins stays silent on are the declared declines:
    • trait, enum and anonymous-class methods, which S8 picks up;
    • ?void and int|void;
    • generators;
    • one unrelated abstract-method fatal.
  • Should-fix applied: the profile counts in 04-findings.md, and the constructor/destructor row of the module table (__construct(): void { return 5; } fires, as PHP does).

@zonuexe
zonuexe force-pushed the claude/ta-s3-compile-fatal-returns branch from b256c37 to 30e20e6 Compare October 11, 2026 08:16
…are or valued form PHP's compiler tells apart, read off the syntax tree (#952, #1051)
…from a never one and a bare return under any other written return type are return.compile-fatal, dead code included (#952, #1051)
… counts and a changelog fragment say compile-time return fatals are return.compile-fatal (#952, #1051)
…e hook, so neither charges the enclosing function-like, through one shared boundary helper (#952, #1051)
@zonuexe
zonuexe force-pushed the claude/ta-s3-compile-fatal-returns branch from 30e20e6 to dfe3811 Compare October 11, 2026 09:24
@zonuexe
zonuexe marked this pull request as ready for review October 11, 2026 09:30
@zonuexe
zonuexe merged commit 6d4ec16 into master Oct 11, 2026
7 checks passed
@zonuexe
zonuexe deleted the claude/ta-s3-compile-fatal-returns branch October 11, 2026 09:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Compile-time return fatals have no id, and type.property-mismatch misses class values, static and trait properties, and destructure targets

1 participant