PHP RFC: Bound-Erased Generic Types#21969
Conversation
DanielEScherzer
left a comment
There was a problem hiding this comment.
not even going to pretend like I understand this, or even read it all, but some initial notes, I hope you don't mind
I very much hope that this RFC gets to a point where it is voted on and hopefully passes, generics are great!
You may want to consider adding some kind of overview for how the implementation logic works (e.g. a new file in docs/source) to aid reviewers in understanding how all of the parts go together
You may want to also split this up, it currently adds support for
Classes, interfaces, traits, functions, methods, closures, and arrow functions
and an initial version might just support classes, interfaces, and methods. Just a thought, please don't take this as criticism - I really hope that something like this is added to PHP
|
Thanks for the review! On the internal docs: i'd lean toward holding off for now. If the RFC doesn't pass, we'd just throw the prose away; if it does, the API/internals will likely shift between now and acceptance, and I'd rather not rewrite the doc multiple times along the way. Once the RFC lands (hoping it does), I'm happy to write a proper INTERNALS section in one go against the final shape. or do you think there's value in having something now? (for review?) |
I was thinking that this would be for reviewers to understand the rest of the patch - you know it best, since you wrote it, but others could use a roadmap/overview. I don't even known where to start for giving an example of "something like ..." for this, so I'll give one for #16952 (which I know best because I wrote it)
Just to have an understanding of how all of the pieces fit together |
|
Makes sense. Will add it tomorrow morning 👍🏼 |
ed79328 to
4bfba49
Compare
689c568 to
c26a1a5
Compare
|
I checked the failing CircleCI ARM job. Current failure is only:
It prints the reflected type correctly: and then segfaults when the merged trait method is called: I built the branch locally on macOS arm64 with debug + ZTS and the same test passes for me, both plain and with opcache enabled. So this looks more like Linux ARM / JIT / merged arg_info lifetime than a test expectation issue. The useful split is probably to rerun that one test on the ARM worker with JIT disabled. If it only crashes with JIT, the bad path is likely the merged trait method clone being executable but not looking quite like a normal trait clone to the runtime/JIT. |
|
@prateekbhujel reproduced it locally in aarch64 Ubuntu 24.04 Docker container. pushing a fix now :) |
|
It's a big RFC so sorry if this is working-as-intended, but I seem to have found a bug. Reproducer code
<?php declare(strict_types=1);
interface Sequence<+T>
{
}
<?php declare(strict_types=1);
final class ArraySequence<+T>
implements Sequence<T>
{
}
<?php declare(strict_types=1);
set_include_path(__DIR__ . '/src' . PATH_SEPARATOR . get_include_path());
spl_autoload_register();
// class_exists(Sequence::class);
class_exists(ArraySequence::class);
echo "unreachable\n";Run it: php -d display_errors=1 fails.phpResults in: If you un-comment the line: // class_exists(Sequence::class);Then it works. It's a bug because |
|
@morrisonlevi looking into it! |
c18f989 to
45189ee
Compare
|
@morrisonlevi fixed! |
There was a problem hiding this comment.
Found another issue. if we use namespace/ syntax to fully qualify something, it loses generics in reflection.
reflection.php
<?php declare(strict_types=1);
namespace X;
interface Iface<+A>
{
}
trait Mixin<+A>
{
}
class FqnImplements
implements \X\Iface<int>
{
}
class NamespaceImplements
implements namespace\Iface<int>
{
}
class FqnUsesTrait
{
use \X\Mixin<int>;
}
class NamespaceUsesTrait
{
use namespace\Mixin<int>;
}
/**
* @param list<\ReflectionNamedType> $args
*/
function render_args(array $args): string
{
if ($args === []) {
return '[]';
}
return '[' . \implode(', ', \array_map(
static fn(\ReflectionNamedType $arg): string => $arg->getName(),
$args,
)) . ']';
}
function show(string $label, array $args): void
{
\printf("%s: count=%d args=%s\n", $label, \count($args), render_args($args));
}
$fqn_implements_args = (new \ReflectionClass(FqnImplements::class))
->getGenericArgumentsForParentInterface(Iface::class);
$namespace_implements_args = (new \ReflectionClass(NamespaceImplements::class))
->getGenericArgumentsForParentInterface(Iface::class);
$fqn_trait_args = (new \ReflectionClass(FqnUsesTrait::class))
->getGenericArgumentsForUsedTrait(Mixin::class);
$namespace_trait_args = (new \ReflectionClass(NamespaceUsesTrait::class))
->getGenericArgumentsForUsedTrait(Mixin::class);
show('implements FQN', $fqn_implements_args);
show('implements namespace-relative', $namespace_implements_args);
show('trait use FQN', $fqn_trait_args);
show('trait use namespace-relative', $namespace_trait_args);
if (\count($namespace_implements_args) !== 1 || \count($namespace_trait_args) !== 1) {
\fwrite(\STDERR, "BUG: namespace-relative inheritance clauses compile but lose generic arguments in reflection.\n");
exit(1);
}45189ee to
6faad45
Compare
|
@morrisonlevi fixed! :D I have added both to test suite, please report any other edge cases you might encounter. |
a38781a to
8d12f23
Compare
|
Small follow-up on the optimizer note above: I pushed the patch to my fork so it is not just a pasted diff. Commit: prateekbhujel@ccb50d3 It is based on the current RFC head ( |
|
@prateekbhujel feel free to open a PR against |
|
Opened it against the working branch here: carthage-software#2 |
8d12f23 to
6c895fd
Compare
|
Opened one more small PR against the working branch for a Reflection edge case I hit while testing transitive interfaces: It fixes |
d4ba7a6 to
ed70992
Compare
|
Opened a small PR for the current CI failure here: carthage-software#4 The failing test was creating an inherited-method arg_info clone even though the method-level |
19f9294 to
86b9e98
Compare
86b9e98 to
c3c8f39
Compare
|
Opened another Reflection follow-up against the working branch: carthage-software#5 It fixes nested transitive interface args, e.g. |
|
Opened the concrete Reflection piece here: carthage-software#6 It keeps the current singular method alone, but adds an all-bindings view for tools that need to see cases like Foo, Foo from Reflection without reparsing source. It is stacked on #5 for now, so the diff should shrink once that one lands. |
c3c8f39 to
2c2bd6f
Compare
2c2bd6f to
14b1eca
Compare
|
Is there anything new? |
|
How is the current state? |
14b1eca to
9da6a90
Compare
Signed-off-by: azjezz <azjezz@protonmail.com>
9da6a90 to
127703d
Compare
RFC: https://wiki.php.net/rfc/bound_erased_generic_types
Note: This PR has been squashed, full history can be found in here