Improving support for anonymous classes#2613
Conversation
lib/WorseReflection/Core/Inference/Resolver/ObjectCreationExpressionResolver.php
Show resolved
Hide resolved
| } | ||
|
|
||
| if ($child instanceof ObjectCreationExpression) { | ||
| // TODO: come up with a good way to generate a class name |
There was a problem hiding this comment.
We need a better way to generate class names as the code is currently duplicated everywhere.
There was a problem hiding this comment.
PHPStan does it like this: https://github.com/dantleech/phpstan-src/blob/f7b89d47cfa1fa636fb571b4315841a9af0e976f/src/Broker/AnonymousClassNameHelper.php#L22-L25
but I don't think this is the correct place to do it.
We can create the reflection class in the resolver?
There was a problem hiding this comment.
Yeah, technically using the line number is not 100% garanteed to be unique but who has two anon classes on the same line. :D
But what you mean with "creating the reflection class in the resolver"? I wanted to try to avoid making a ReflectionAnonymousClass class as anon classes should be the same except for their weird name.
There was a problem hiding this comment.
Yes you are right. A different approach for this would probably make more sense but that would require a lot of refactoring as the fromNode function is used quite a lot.
There was a problem hiding this comment.
I don't think it needs refactoring and would also fix the issue Thomas raised below, but would need to show an example.
lib/WorseReflection/Core/Inference/Resolver/ObjectCreationExpressionResolver.php
Outdated
Show resolved
Hide resolved
lib/WorseReflection/Core/Reflection/Collection/ClassLikeReflectionMemberCollection.php
Outdated
Show resolved
Hide resolved
lib/WorseReflection/Core/Inference/Resolver/ObjectCreationExpressionResolver.php
Outdated
Show resolved
Hide resolved
f8c99ee to
8e24b48
Compare
4235fc4 to
f32ad1b
Compare
|
From #2612 perspective this seems to be one step ahead but: |
|
What were you trying to do? Generate a class? |
|
"Implement contracts" like in #2612 |
|
Now it should work (at least the test for it is green) and it looks like the rest broke. |
681f9c8 to
7a53a6b
Compare
| return $classNode->classMembers; | ||
| if ($classNode instanceof ClassDeclaration || $classNode instanceof ObjectCreationExpression) { | ||
| $classNode = $classNode->classMembers; | ||
| Assert::isInstanceOf( $classNode, ClassMembersNode::class); |
There was a problem hiding this comment.
is it expected that this is NOT a ClassMembersNode? pm am ObjectCreationExpression it can be NULL, and for ClassDeclaration tolerant parser often lies about nullability. this will cause a runtime exception, which is probably not what we want.
if it should never happen then this should be an exception and I'd prefer not to use Assertion libraries.
There was a problem hiding this comment.
Yeah, the problem in this case is that it could also be a "normal" Object creation with a class name which would then crash here. But in this case I'm not sure what the best way to go about this is.
| if ($nodeOrToken instanceof Token) { | ||
| return (string)$nodeOrToken->getText($node->getFileContents()); | ||
| } | ||
| if ($nodeOrToken instanceof ObjectCreationExpression) { |
There was a problem hiding this comment.
this is method is explicitly about getting the name frm a Node or Token, it's not about annonymous classes. you can add a separate method or probably there is something better.
lib/WorseReflection/Bridge/TolerantParser/Reflection/ReflectionClass.php
Outdated
Show resolved
Hide resolved
| public function memberListPosition(): ByteOffsetRange | ||
| { | ||
| $classMembers = $this->node->classMembers; | ||
| assert($classMembers instanceof ClassMembersNode, 'ObjectCreationExpression does not contain anonymous class'); |
There was a problem hiding this comment.
presumably thid can happen and this will crash? can we return defaults instead based on the node position?
| $classDeclaration = $this->node->getFirstAncestor(ClassLike::class, ObjectCreationExpression::class); | ||
| if ($classDeclaration instanceof ObjectCreationExpression) { | ||
| return $this->class ?? $this->serviceLocator->reflector() | ||
| ->reflectClassLike(NodeUtil::nameFromTokenOrNode($classDeclaration, $classDeclaration)); |
There was a problem hiding this comment.
the reflector has no way to know how to locate this, so this is working based on caching? does it work without the cache?
There was a problem hiding this comment.
I don't know how the reflector does it. But I assume it cashes the classes that it has seen yes. Where would the logic be for finding it properly? Do we need to index anonymous classes for that?
lib/WorseReflection/Bridge/TolerantParser/Reflection/TraitImport/TraitImports.php
Outdated
Show resolved
Hide resolved
| $node->getEndPosition(), | ||
| [ | ||
| 'symbol_type' => Symbol::CLASS_, | ||
| 'type' => TypeFactory::reflectedClass($resolver->reflector(), NodeUtil::nameFromTokenOrNode($node, $node)) |
There was a problem hiding this comment.
again, i'm not sure how this works, and assume it works based on the anonymous class previously being parsed? we should avoid depending on the cache.
see Roave/BetterReflection#300
probably the reflector should support accepting the Node instead of a "name".
lib/WorseReflection/Core/Reflection/Collection/ClassLikeReflectionMemberCollection.php
Outdated
Show resolved
Hide resolved
| continue; | ||
| } | ||
|
|
||
| if ($child instanceof ObjectCreationExpression && !($child->classTypeDesignator instanceof Node)) { |
There was a problem hiding this comment.
this may cause unexpected issues. "reflect classes in" reflects locatable symbols and also it should not need to deeply traverse the AST.
we could consider removing this and instead adding support to $reflector->reflect{Node,AnnonymousClass,Object}($node) for example.
There was a problem hiding this comment.
The problem is this approach won't find all classes anyways. Because we actually have to traverse into class definitions as well. Otherwise we would miss anonymous classes inside other classes. But I don't have an idea how to do improve performance here.
There was a problem hiding this comment.
yes, they need probably to be interpreted inline as types as with the above comment. doing this will mean that "implement contracts" will not automatically work, but at the same time it's only working because of the cache effect in this PR
There was a problem hiding this comment.
I just saw that reflectClassesIn should probably better be named reflectClassLikeIn 'cause it also returns interfaces enums etc. which are completely irrelevant for the "Implement contracts". But that's not a problem of this PR.
However I get what you're saying, I'm not sure if its based on cache in this situation because the source code builder reflects the same structure again and should also build the same list of ClassLike nodes. On the other hand I'm quite familiar with the concept of inline types and how I would go about and implement this. Do we already have an example for this?
b7acb99 to
da577f9
Compare
|
At the moment I have no occasion to work with anonymous classes, but have merged the recent version of this PR into my experimental branch and can report possible side effects. |
|
just to be clear, it's unlikely to be merged with it's current implementation. but hopefully I can find some time to look more into it. |
|
Regardless of merging into master, this change stopped working after merge de10e22 here (incompatible declarartion of |
|
Should work again now. |
|
Works again, thank you |
95cf6a5 to
e2fec03
Compare
e2fec03 to
3a2108e
Compare
|
I had to rebase this because I messed up the merging of this branch. |


Things that have been done
Limitations: