Repository navigation
Conversation
[Medium impact] The changes affect how compiled PHP programs use PHPDoc generics. Fix the newly broken explicit method calls before merging.
|
…of on templates Since the monomorphization (illegalstudio#1221), plain-PHP @template patterns that php-src runs were rejected or miscompiled. Dynamically dispatched methods: - An interface method's PHPDoc @template is erased; the interface keeps its native signature and implementations still conform. - A templated method that implements or overrides another keeps its vtable slot, so override dispatch reaches the child. - A @template-covariant marker broken by the declaration is demoted to invariant with a warning. - Docblock type arguments on undeclared (builtin) interfaces are erased. - A plain class implementing a PHPDoc-generic interface by its bare name implements its default instantiation. - Methods implementing builtin interfaces count as dispatched; any other undeclared ancestor makes every method dispatched. Templates: - Each class/interface template leaves an empty marker interface that every instantiation inherits, so `instanceof Coll` holds for every instantiation and subclass; an instanceof guard on a template does not narrow to the member-less marker, keeps an object local's class and reads anything else as mixed. - A method template bounded by the native class refines it; a template no position adopts is dropped; a native `mixed` is not replaced by PHPDoc. - Erased `array<mixed, mixed>` / `mixed[]` become plain `array`. Reflection: the marker a class template leaves is hidden from interface_exists, get_declared_interfaces and class_implements, and is_subclass_of(x, 'Box') holds only for a class extending an instantiation. test_one_line_block_reads_its_opening_tag: the three one-line METHOD forms now compile untyped and print php-src's output, since their template is adopted by no position; the function and multi-line assertions are unchanged.
7ba2536 to
a71b166
Compare
Enums share the class namespace but were missing from the declared-name set, so a type argument on a declared enum (`Suit<int>`) was erased as if the name came from outside the program instead of being reported as on a class that declares no type parameters.
…e hint When a method template's only @PARAM annotation loses to a native hint (`@param T $v` on `mixed $v`), nothing at a call can bind the template, since method templates are inferred from arguments only. Keeping it and the `@return T` adopted from it made ordinary calls fail with 'does not determine type parameter <T>'. The template is now dropped and the return keeps its native type, as php-src sees the method.
| method.return_type = native_return; | ||
| } | ||
| method.type_params.retain(|param| !lost.contains(¶m.name)); |
There was a problem hiding this comment.
Explicit method calls stop compiling
Removing a template here breaks calls that compiled before this update. With separate @template T, @param T $v, and @return T tags on public function id(mixed $v) { return $v; }, $repo->id<int>(7) previously bound T explicitly.
This change removes T and restores the native return, so only ordinary id remains. The explicit call still looks for id<int> and fails with Undefined method. Preserve explicit calls while allowing ordinary calls to use the erased signature.
Knowledge Base Used: Generic language support
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/docblock/members.rs
Line: 118-120
Comment:
**Explicit method calls stop compiling**
Removing a template here breaks calls that compiled before this update. With separate `@template T`, `@param T $v`, and `@return T` tags on `public function id(mixed $v) { return $v; }`, `$repo->id<int>(7)` previously bound `T` explicitly.
This change removes `T` and restores the native return, so only ordinary `id` remains. The explicit call still looks for `id<int>` and fails with `Undefined method`. Preserve explicit calls while allowing ordinary calls to use the erased signature.
**Knowledge Base Used:** [Generic language support](https://app.greptile.com/illegal-studio/-/custom-context/knowledge-base/illegalstudio/elephc/-/docs/generic-language-support.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
Summary
Since the generics monomorphization (#1221), several plain-PHP
@templatepatterns thatphp-src runs (Laravel's and PHPStan-annotated code is full of them) are rejected or
miscompiled on main. This PR re-validates the follow-ups against main's current generics
(monomorphization, #1819, #1822) and fixes them:
Dynamically dispatched methods:
@templateis erased: the interface keeps its nativesignature, so
TInstanceis no longer anUnknown typeand implementations stillconform;
method, keeps its vtable slot, so conformance and override dispatch work (main dispatched
to the parent:
Base 5instead ofChild 5);@template-covariantmarker that the declaration itself breaks (a writable property,a parameter) is demoted to invariant with a warning instead of rejecting a file php-src
runs;
(
@implements IteratorAggregate<string, int>,ArrayAccess<K, V>) are erased;implements it (main:
Unknown interface: Arrayable).Templates and
instanceof:instantiation inherits, so
$x instanceof Collholds for every instantiation andsubclass, as in php-src (main:
falsefor all of them); aninstanceofguard on atemplate does not narrow to the member-less marker;
T of ModelonModel) refines it; atemplate that no position adopts is dropped (the method compiles untyped, as php-src
runs it); a native
mixedis no longer replaced by PHPDoc;array<mixed, mixed>/mixed[]become plainarray, so a dispatched method'sphysical signature stays compatible;
JsonSerializable, ...) count as dispatched; any other undeclared ancestor makes every
method dispatched.
Reflection: the marker a class template leaves behind is hidden from
interface_exists,get_declared_interfacesandclass_implements, andis_subclass_of($x, 'Box')holds only for a class extending an instantiation, as inphp-src. Interface-template markers stay visible. An
instanceofguard on a template keepsan object local's class and reads anything else as mixed.
The erasure rule is documented in
docs/beyond-php/generics.md. There is no environmentswitch to turn PHPDoc generics off.
The changes land as one commit because they share the docblock and generics passes; happy to
split it (dispatched-method erasure / template markers and
instanceof/ reflection) if youprefer.
Closes #1899 (follow-up of #1221).
Changed upstream test
test_one_line_block_reads_its_opening_tag(from c769f6c) expecteddoes not determine type parameter <T>for the function, static method, trait method andenum method forms of a one-line
/** @template T of int @param T $v @return T */. With the"a method template no position adopts is dropped" rule, the three METHOD forms now compile
untyped and print
seven, which is what php-src prints. The function form and themulti-line bound violation keep the upstream assertions.
Tests
18 new tests in
tests/codegen/generics.rsandtests/codegen/generics_docblock_methods.rs.Expected outputs come from php 8.5, except one native-syntax test (
class Box<T>) that phpcannot parse.
Unknown type: TInstance,Class ArrayRepo must implement interface method Repo::get,Unknown interface: Arrayable, a covariance error,'IteratorAggregate' is written with type arguments but declares no type parameters,Cannot implement interface method IntColl::first without declaring a compatible return type, ...). Two compile and print the wrong result: override dispatch (Base 5) andinstanceofon a class template (allfalse).test_docblock_bounded_template_refines_the_native_bound(marked in its doc comment).
Based on main df57a98 (verified against a build of that SHA).
Local runs:
cargo build;error_tests,parser_tests,lexer_tests;codegen_tests -- generics docblock(290); lib tests fordocblock,generics,source::and
narrowing; codegeninstanceof class_implements interface_exists is_subclass_of declared_interfaces.Refs #1886
AI assistance
This change was written with substantial help from Claude Code (Anthropic). I reviewed
it, checked the expected outputs against php 8.5, and remain responsible for it.
🤖 Generated with Claude Code