Support ::class magic constant in array shape keys and const types#5891
Conversation
Resolve the `::class` pseudo-constant in PHPDoc `ConstFetchNode`s instead
of failing `hasConstant('class')` and degrading to ErrorType. Applies to
both array-shape keys (`array{Foo::class: int}`) and const value types
(`@return Foo::class`).
Co-Authored-By: Claude Code
| $constantName = $constExpr->name; | ||
| if (strtolower($constantName) === 'class') { | ||
| if ($isStatic) { | ||
| return new GenericClassStringType(new StaticType($classReflection)); |
There was a problem hiding this comment.
this line seems to miss test-coverage
There was a problem hiding this comment.
You can place similar check to CI btw: https://github.com/shipmonk-rnd/coverage-guard ;) (even for MR-affected lines only)
There was a problem hiding this comment.
Anyway, coverage fixed
There was a problem hiding this comment.
You can place similar check to CI btw: shipmonk-rnd/coverage-guard ;) (even for MR-affected lines only)
sounds like something we could try in a separate PR. might be useful, I am not sure yet.
could you send a PR?
There was a problem hiding this comment.
I can send a PR, but the rule decisions should be done by core maintainers. Or maybe you can use it to only observe what is tested in MRs (let fail all the time when coverage of new files < 100% or so) 🤷
|
thank you! |
|
@janedbal what do you think about the magic "name" property and UnitEnum, EnumCase. I think it has a similar problem? |
|
Seems rare to me :) I didnt even know (forgot?) we have |
PHPStan didn't support the
::classmagic constant as a value or key in PHPDoc array shapes.The inferred type inside the method was correct (
array{X\Test: 1}), but the declared@returnresolved theTest::classkey toErrorType, collapsing the whole shape intonon-empty-array<int>.Cause: both PHPDoc resolution paths for
Foo::CONST(resolveArrayShapeOffsetType()for keys,resolveConstTypeNode()for value types) look the constant up via$classReflection->hasConstant($constantName). But::classis a magic pseudo-constant, not a declared one, sohasConstant('class')isfalseand resolution falls through toErrorType. Regular class constants as keys (array{Foo::BAR: int}, already documented) were unaffected.Fix: handle
::classin both paths, mirroringInitializerExprTypeResolver::getClassConstFetchTypeByReflection():static::classon a non-final class →class-string<static>self,parent, literal names,staticon final) → a constant class-stringThis also unblocks consumers that rely on exact
Class::classkeys surviving througharray_merge, e.g. shipmonk-rnd/dead-code-detector#374 (shipmonk-rnd/dead-code-detector#374 (comment)).Co-Authored-By: Claude Code
Docs: phpstan/phpstan#14837