Allow mixed type dynamic constants#3583
Conversation
| 'MixedTypeConstants\NoMixedTypeConstantClass::MIXED_TYPE_CONSTANT_IN_CLASS', | ||
| ], | ||
| [ | ||
| 'false', |
There was a problem hiding this comment.
This doesn't seem right to me, but this test is copied and modified from testDynamicConstants where it also has false. That changed from bool to false in 285b49f#diff-52d2cce81a3daff27b7d6d8ba619b045420ab9a9acffc57c1c379af4eba0b127R8384.
I tried tracking down how I might be able to fix it. I found that the constant gets defined here:
phpstan-src/src/Type/Php/DefineConstantTypeSpecifyingExtension.php
Lines 45 to 60 in d73acef
The issue is that $scope->getType($node->getArgs()[1]->value) is just going based off the value, so it never reaches where I think it would determine that the constant is dynamic:
phpstan-src/src/Analyser/MutatingScope.php
Line 1943 in d73acef
|
It'd make sense to me to allow this syntax to force a specific constant type: parameters:
dynamicConstantNames:
- DATABASE_ENGINE
- Foo::BAR_CONSTANT
ES_ENGINE: string|null
Foo::BAZ_CONSTANT: mixed |
That makes sense. I can get it as far as detecting that it's in |
I think you can take inspiration from phpstan-src/src/Reflection/SignatureMap/SignatureMapParser.php Lines 49 to 56 in 830b6d5 |
|
Thanks, @staabm! That got me on the right track. I got this working. I even fixed #3583 (comment). I'm not 100% sure if the new interfaces I added are desired, but it gets the job done. Please let me know if you want me to make any changes. I could use some help with the 1 failing test though: |
src/Analyser/ConstantResolver.php
Outdated
| if ($constantType->isConstantValue()->yes()) { | ||
| if (array_key_exists($constantName, $this->dynamicConstantNames)) { | ||
| $phpdocTypes = $this->dynamicConstantNames[$constantName]; | ||
| return $this->reflectionProviderProvider->getReflectionProvider()->getSignatureMapProvider()->getTypeFromString($phpdocTypes, null); |
There was a problem hiding this comment.
You should use TypeStringResolver for this, not SignatureMapProvider.
There was a problem hiding this comment.
How would I get an instance of TypeStringResolver from here?
src/Analyser/ConstantResolver.php
Outdated
|
|
||
| if ($constantType->isConstantValue()->yes()) { | ||
| $phpdocTypes = $this->dynamicConstantNames[$lookupConstantName]; | ||
| return $this->getReflectionProvider()->getSignatureMapProvider()->getTypeFromString($phpdocTypes, $className); |
|
|
||
| public function resolveConstantName(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAnswerer): ?string; | ||
|
|
||
| public function getSignatureMapProvider(): SignatureMapProvider; |
There was a problem hiding this comment.
Don't add these methods please.
a654da5 to
4c71fb5
Compare
|
Thanks, @ondrejmirtes. I made updates. I'm not sure if passing around the container is the right thing to do, but I got it working. |
| private array $dynamicConstantNames, | ||
| private ?PhpVersion $composerMinPhpVersion, | ||
| private ?PhpVersion $composerMaxPhpVersion, | ||
| private ?Container $container, |
There was a problem hiding this comment.
make this new param the TypeStringResolver itself.
It will be injected by the DIC automatically
There was a problem hiding this comment.
I initially thought that might be the way to do it. However, when I tried it, I wound up getting an error about circular dependencies. So that's why I switched to the entire container. Do you have any ideas on how to get around the circular dependency error? It's also possible I just did it wrong. I assumed I needed to update the places that called the constructor, so I was using the container there to get the TypeStringResolver.
There was a problem hiding this comment.
ah I can see it now.
TypeStringResolver has a TypeNodeResolver contruction dependency which in turn has a ConstantResolver dependency.
so when you add a TypeStringResolver dependency on ConstantResolver you build a cycle.
I will think about it and report back
There was a problem hiding this comment.
I guess one way we could break the cycle is to remove the ConstantResolver construction-time dependency of TypeNodeResolver and turn it into a setter injection (similar how we do it in TypeSpecifierFactory for the TypeSpecifier).
in my local testing I did not yet succeed in doing so :)
There was a problem hiding this comment.
or maybe better instead remove TypeNodeResolver from TypeStringResolver construction-time deps and move it to setter-injection
There was a problem hiding this comment.
Sorry, @staabm. I don't have experience with DI in PHP, so I'm not entirely sure how to do this. I assume you meant to add a setTypeNodeResolver function to TypeStringResolver. I'm not sure where I would call setTypeNodeResolver though. I thought maybe I needed to add a TypeStringResolverFactory. I'm guessing that maybe I need to update conf/config.neon, so I changed it to this:
-
class: PHPStan\PhpDoc\TypeStringResolver
typeStringResolverFactory:
class: PHPStan\PhpDoc\TypeStringResolverFactoryIn a new TypeStringResolverFactory class, I added this:
public function create(): TypeStringResolver
{
$typeStringResolver = new TypeStringResolver(
$this->typeLexer,
$this->typeParser
);
$typeStringResolver->setTypeNodeResolver($this->container->getByType(TypeNodeResolver::class));
return $typeStringResolver;
}I still wound up with this when running the test though:
In Container.php line 327:
Circular reference detected for: reflectionProvider, betterReflectionProvider, 093,
058, 042, 040.
Am I on the right track? Do you have any suggestions? Thanks.
|
@staabm, should I rebase this to resolve the merge conflicts? |
|
Yes, please resolve the conflicts |
8710e09 to
08f8575
Compare
|
@staabm, I rebased it. |
Addresses phpstan/phpstan#7520 Revert "Allow mixed type dynamic constants." This reverts commit 1854985. Allow setting types for dynamic constants Unit test fixes Unit test fix 2 Lint fixes Use TypeStringResolver directly Remove old code
|
I rebased this to fix the merge conflict. |
|
Thank you. |
|
Thanks for cleaning it up and getting it merged. |
Addresses phpstan/phpstan#7520