fix(types): preserve late-static method returns#578
Merged
Conversation
nahime0
marked this pull request as ready for review
July 20, 2026 09:33
…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
force-pushed
the
fix/518-late-static-method-returns
branch
from
July 20, 2026 12:38
799162c to
d152f7f
Compare
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.
Supersedes #518 and carries forward its PSR-style fluent-call reproducer and contributor credit.
Summary
staticas a late-bound type instead of erasing it to the declaring class or interface.staticreturn cannot be replaced by the current concrete class, while covariant narrowing such asstatic|falsetostaticremains valid.staticreturn metadata, including PHP-compatible union ordering.: staticrequirement 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 withwith. That naming heuristic also matched unrelated methods such aswithdraw(), produced false positives, excluded non-with*fluent methods, and only covered interface receivers.The underlying problem was that relative-type substitution rewrote both
selfandstaticto the declaring type before schema construction. The checker and EIR therefore had no way to distinguish a lexicalselfreturn from a late-boundstaticreturn at the call site.This replacement keeps the exact return syntax containing
staticin class/interface metadata while retaining a nominal declaring-type signature for ABI and compatibility work. Only the symbolicstaticmembers 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 staticalone has no typing effect.The unrelated indexed-array/associative-array compatibility change from #518 is not part of this diff; the current
mainbehavior is retained unchanged.Validation
cargo check --testscargo test --test codegen_tests codegen::oop::relative_types::— 15 passedcargo test --test codegen_tests codegen::oop::interfaces::— 18 passedwithdraw(), compound unions, invalid class overrides, invalid interface implementations, and invalid interface redeclarationsELEPHC_PHP_CHECK=1cross-checks for PSR-style interfaces, nullablestatic, compound unions, Reflection metadata, and covariant union narrowingcargo fmt --all -- --checkgit diff --check./scripts/check_asm_comments.py src/codegen/lower_inst/objects/reflection.rs