Skip to content

Generalize dynamicConstantNames constants whose type cannot be widened by generalize() to mixed#5457

Open
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-zwukgp1
Open

Generalize dynamicConstantNames constants whose type cannot be widened by generalize() to mixed#5457
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-zwukgp1

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

Summary

When a constant with value null (or empty array []) was listed in dynamicConstantNames, PHPStan still treated it as having its literal type because NullType::generalize() returns $this — there is no broader "null family" type to generalize to. This made dynamicConstantNames ineffective for null-valued constants.

Changes

  • Modified ConstantResolver::resolveConstantType() in src/Analyser/ConstantResolver.php to check whether generalize() actually widened the type; if the result still reports isConstantValue()->yes(), return MixedType instead
  • Applied the same fix to ConstantResolver::resolveClassConstantType() for class constants
  • Added test cases for null-valued class constants, null-valued global constants, and empty-array class constants in tests/PHPStan/Analyser/data/dynamic-constant.php
  • Added corresponding dynamicConstantNames entries in tests/PHPStan/Analyser/dynamic-constants.neon

Analogous cases probed

  • true/false constants: Already generalize correctly to bool — confirmed with tests (already passing)
  • String/integer/float constants: Already generalize correctly to their parent scalar types — not affected
  • Empty ConstantArrayType ([]): generalize() returns $this for empty arrays — was broken, now fixed by the same check
  • EnumCaseObjectType: Reports isConstantValue()->no(), so not affected
  • VoidType: Reports isConstantValue()->no(), so not affected
  • Explicit type path (array_key_exists): Uses TypeStringResolver to resolve user-specified type strings — not affected
  • Native type path ($nativeType): Returns the declared native type (PHP 8.3+) — not affected

Root cause

NullType::generalize() returns $this because null is a singleton type with no broader family. Similarly, ConstantArrayType::generalize() returns $this for empty arrays. When dynamicConstantNames called generalize() on these types, the result was unchanged, so the constant was still treated as having its literal value. The fix detects this case by checking whether the generalized type is still a constant value and falls back to mixed.

Test

  • Added assertType('mixed', DynamicConstantClass::DYNAMIC_NULL_CONSTANT) — null-valued class constant
  • Added assertType('mixed', GLOBAL_DYNAMIC_NULL_CONSTANT) — null-valued global constant
  • Added assertType('mixed', DynamicConstantClass::DYNAMIC_EMPTY_ARRAY_CONSTANT) — empty-array class constant
  • Added assertType('bool', DynamicConstantClass::DYNAMIC_TRUE_CONSTANT) and assertType('bool', DynamicConstantClass::DYNAMIC_FALSE_CONSTANT) — confirm boolean constants still generalize correctly

Fixes phpstan/phpstan#9218

…ned by `generalize()` to `mixed`

- NullType::generalize() returns $this because null has no broader type
  family, so dynamicConstantNames had no effect on null-valued constants
- After calling generalize(), check if the result is still a constant
  value; if so, return MixedType instead
- Fix applies to both resolveConstantType (global constants) and
  resolveClassConstantType (class constants)
- Also fixes the analogous case for empty ConstantArrayType ([]),
  whose generalize() also returns $this
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.

2 participants