Skip to content

Support ::class magic constant in array shape keys and const types#5891

Merged
staabm merged 2 commits into
phpstan:2.2.xfrom
janedbal:support-class-magic-constant-in-array-shapes
Jun 17, 2026
Merged

Support ::class magic constant in array shape keys and const types#5891
staabm merged 2 commits into
phpstan:2.2.xfrom
janedbal:support-class-magic-constant-in-array-shapes

Conversation

@janedbal

@janedbal janedbal commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

PHPStan didn't support the ::class magic constant as a value or key in PHPDoc array shapes.

class Test {
    /** @return array{Test::class: int} */
    static function foo(): array {
        $r = [self::class => 1];
        // inferred directly: array{X\Test: 1}  ✅
        return $r;
    }
}

\PHPStan\dumpType(Test::foo()); // was: non-empty-array<int>  ❌

The inferred type inside the method was correct (array{X\Test: 1}), but the declared @return resolved the Test::class key to ErrorType, collapsing the whole shape into non-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 ::class is a magic pseudo-constant, not a declared one, so hasConstant('class') is false and resolution falls through to ErrorType. Regular class constants as keys (array{Foo::BAR: int}, already documented) were unaffected.

Fix: handle ::class in both paths, mirroring InitializerExprTypeResolver::getClassConstFetchTypeByReflection():

  • static::class on a non-final class → class-string<static>
  • everything else (self, parent, literal names, static on final) → a constant class-string

This also unblocks consumers that rely on exact Class::class keys surviving through array_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

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
@VincentLanglet VincentLanglet requested a review from staabm June 17, 2026 09:08
$constantName = $constExpr->name;
if (strtolower($constantName) === 'class') {
if ($isStatic) {
return new GenericClassStringType(new StaticType($classReflection));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this line seems to miss test-coverage

@janedbal janedbal Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can place similar check to CI btw: https://github.com/shipmonk-rnd/coverage-guard ;) (even for MR-affected lines only)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anyway, coverage fixed

@staabm staabm Jun 17, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) 🤷

@janedbal janedbal requested a review from staabm June 17, 2026 10:24
@staabm staabm merged commit a131dd0 into phpstan:2.2.x Jun 17, 2026
670 of 674 checks passed
@staabm

staabm commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

thank you!

@staabm

staabm commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

@janedbal what do you think about the magic "name" property and UnitEnum, EnumCase.

I think it has a similar problem?

https://phpstan.org/r/57dcadcd-0bba-428a-b8b1-319dc134e5bf

@janedbal

Copy link
Copy Markdown
Contributor Author

Seems rare to me :) I didnt even know (forgot?) we have enum-string

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants