Skip to content

fix(types): preserve late-static method returns#578

Merged
nahime0 merged 2 commits into
mainfrom
fix/518-late-static-method-returns
Jul 20, 2026
Merged

fix(types): preserve late-static method returns#578
nahime0 merged 2 commits into
mainfrom
fix/518-late-static-method-returns

Conversation

@nahime0

@nahime0 nahime0 commented Jul 20, 2026

Copy link
Copy Markdown
Member

Supersedes #518 and carries forward its PSR-style fluent-call reproducer and contributor credit.

Summary

  • Preserve native method return static as a late-bound type instead of erasing it to the declaring class or interface.
  • Bind late-static returns to the call-site receiver in both checker inference and EIR lowering.
  • Apply the same semantics to inherited instance methods, static factories, class hierarchies, interfaces, nullable returns, and compound unions.
  • Enforce PHP-compatible override and interface conformance: a required static return cannot be replaced by the current concrete class, while covariant narrowing such as static|false to static remains valid.
  • Preserve reflection-visible static return metadata, including PHP-compatible union ordering.
  • Document the native : static requirement and add a fluent class example.

Root cause

#518 addressed a real failure in PSR-style chains such as withHeader()->withMethod(), but refined any interface method whose lowercase name started with with. That naming heuristic also matched unrelated methods such as withdraw(), produced false positives, excluded non-with* fluent methods, and only covered interface receivers.

The underlying problem was that relative-type substitution rewrote both self and static to the declaring type before schema construction. The checker and EIR therefore had no way to distinguish a lexical self return from a late-bound static return at the call site.

This replacement keeps the exact return syntax containing static in class/interface metadata while retaining a nominal declaring-type signature for ABI and compatibility work. Only the symbolic static members are rebound when a concrete receiver is known.

#473 remains complementary: it added sound covariant self-return validation, while this PR preserves and consumes native late-static return contracts end to end.

Scope

PHPDoc is not parsed for typing. APIs that need late-static refinement must use the native PHP return declaration : static; @return static alone has no typing effect.

The unrelated indexed-array/associative-array compatibility change from #518 is not part of this diff; the current main behavior is retained unchanged.

Validation

  • cargo check --tests
  • cargo test --test codegen_tests codegen::oop::relative_types:: — 15 passed
  • cargo test --test codegen_tests codegen::oop::interfaces:: — 18 passed
  • Focused negative regressions for withdraw(), compound unions, invalid class overrides, invalid interface implementations, and invalid interface redeclarations
  • ELEPHC_PHP_CHECK=1 cross-checks for PSR-style interfaces, nullable static, compound unions, Reflection metadata, and covariant union narrowing
  • cargo fmt --all -- --check
  • git diff --check
  • ./scripts/check_asm_comments.py src/codegen/lower_inst/objects/reflection.rs

@github-actions github-actions Bot added area:eir Touches EIR definitions, lowering, validation, or passes. area:parser Touches parsing or AST construction. area:types Touches type checking, inference, or compatibility. scope:multi-area Touches more compiler areas than the automatic area-label cap. size:m Medium-sized pull request. type:fix Corrects broken or incompatible behavior. labels Jul 20, 2026
@nahime0
nahime0 marked this pull request as ready for review July 20, 2026 09:33
chadmandoo and others added 2 commits July 20, 2026 14:36
…faces

A PSR-FIG immutable "wither" (`withHeader(): MessageInterface`) declares an
ancestor interface as its return type but carries `@return static`. Called
through a descendant-interface receiver, the result must keep the receiver's
type so a subsequent descendant-only call type-checks — matching PHP's runtime
`clone $this`.

Both inference passes resolve a `with*` method whose declared return is a
strict ancestor of the receiver interface back to the receiver: the checker
(`infer_method_call_on_interface_type`) and the EIR lowering
(`method_call_result_type`). Both are required — the checker alone leaves the
EIR backend re-resolving to the ancestor and failing native codegen.

Also accepts `Array(Mixed)` where an `AssocArray` parameter is declared (an
untyped list literal passed to an `array<int, mixed>` param).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@nahime0
nahime0 force-pushed the fix/518-late-static-method-returns branch from 799162c to d152f7f Compare July 20, 2026 12:38
@nahime0
nahime0 merged commit 19cf852 into main Jul 20, 2026
113 checks passed
@nahime0
nahime0 deleted the fix/518-late-static-method-returns branch July 20, 2026 14:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:eir Touches EIR definitions, lowering, validation, or passes. area:parser Touches parsing or AST construction. area:types Touches type checking, inference, or compatibility. scope:multi-area Touches more compiler areas than the automatic area-label cap. size:m Medium-sized pull request. type:fix Corrects broken or incompatible behavior.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants