Skip to content

Fix #13828: Reference to static const behaves as self const#5510

Merged
VincentLanglet merged 7 commits intophpstan:2.1.xfrom
VincentLanglet:create-pull-request/patch-gripjte
Apr 22, 2026
Merged

Fix #13828: Reference to static const behaves as self const#5510
VincentLanglet merged 7 commits intophpstan:2.1.xfrom
VincentLanglet:create-pull-request/patch-gripjte

Conversation

@VincentLanglet
Copy link
Copy Markdown
Contributor

@VincentLanglet VincentLanglet commented Apr 22, 2026

PR #4906 with a rebase.

Was previously approved by ondrej.

Fixes phpstan/phpstan#13828
Closes #4906

ondrejmirtes and others added 7 commits April 22, 2026 14:49
- Added ClassConstantAccessType that wraps StaticType + constant name and
  implements LateResolvableType, deferring resolution until the caller type is known
- Modified TypeNodeResolver::resolveConstTypeNode() and resolveArrayShapeOffsetType()
  to create ClassConstantAccessType when the keyword is 'static' instead of
  resolving eagerly like 'self'
- The StaticType inside ClassConstantAccessType gets replaced with the concrete
  ObjectType during CalledOnTypeUnresolvedMethodPrototypeReflection::transformStaticType(),
  then the constant is resolved on the correct class
- New regression test in tests/PHPStan/Analyser/nsrt/bug-13828.php
When the inner type is still a StaticType (not yet resolved to a concrete
class), use the declared type (native or PHPDoc) instead of the literal
value for non-final constants on non-final classes. For final constants
or final classes, the literal value is preserved since subclasses cannot
override them.

Added test cases for:
- Constants with native type (const string)
- Constants with PHPDoc type (@var non-empty-string)
- Constants with both native and PHPDoc types
- Final constants on non-final classes
- Untyped constants

Co-authored-by: Ondřej Mirtes <ondrejmirtes@users.noreply.github.com>
…non-final classes

When static::CONST is used and the constant has no native type or PHPDoc type,
and neither the class nor the constant is final, return mixed instead of the
literal value — since a subclass could override the constant with any value.

Also unified the logic to use getObjectClassReflections() instead of
instanceof StaticType, so it works for both unresolved StaticType and
resolved ObjectType (e.g., FooBar $foo where $foo could be a subclass).

Co-authored-by: Ondřej Mirtes <ondrejmirtes@users.noreply.github.com>
…solver logic

Restructured getResult() to match the $isObject path in
InitializerExprTypeResolver::getClassConstFetchTypeByReflection():

- Extract ClassReflection first via getObjectClassReflections()
- Add enum case handling (EnumCaseObjectType)
- Match exact condition structure: class not final AND constant not final
  AND no phpDocType AND no nativeType → mixed
- Otherwise use getValueType() (declared type for typed constants,
  literal for untyped constants)

Added test cases for final child class inheriting @return static::CONST
and for final typed constant on non-final class.

Co-authored-by: Ondřej Mirtes <ondrejmirtes@users.noreply.github.com>
…comparison

Co-authored-by: Markus Staab <markus.staab@redaxo.de>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Automated fix attempt 1 for CI failures.
Automated fix attempt 2 for CI failures.
@VincentLanglet VincentLanglet marked this pull request as ready for review April 22, 2026 13:03
@VincentLanglet VincentLanglet requested a review from staabm April 22, 2026 13:03
@phpstan-bot
Copy link
Copy Markdown
Collaborator

This pull request has been marked as ready for review.

@VincentLanglet VincentLanglet merged commit 5c0e026 into phpstan:2.1.x Apr 22, 2026
652 of 657 checks passed
@pindab0ter
Copy link
Copy Markdown

Many thanks!

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.

5 participants