Skip to content

Infer non-falsy-string for the magic name property on the UnitEnum/BackedEnum interfaces#5894

Open
phpstan-bot wants to merge 7 commits into
phpstan:2.2.xfrom
phpstan-bot:create-pull-request/patch-xlf7wmo
Open

Infer non-falsy-string for the magic name property on the UnitEnum/BackedEnum interfaces#5894
phpstan-bot wants to merge 7 commits into
phpstan:2.2.xfrom
phpstan-bot:create-pull-request/patch-xlf7wmo

Conversation

@phpstan-bot

Copy link
Copy Markdown
Collaborator

Summary

Accessing the magic name property on a value typed as the \UnitEnum or \BackedEnum interface (or a @template T of \UnitEnum bound) inferred plain string. Since an enum case name is always a valid PHP label, it can never be an empty string or "0", so it should be inferred as non-falsy-string.

Changes

  • src/Reflection/Php/PhpClassReflectionExtension.php: in createProperty(), when the resolved property is the magic name declared on the UnitEnum interface and no PHPDoc type was found, set its PHPDoc type to non-falsy-string (StringType intersected with AccessoryNonFalsyStringType).
  • Added a regression test tests/PHPStan/Analyser/nsrt/bug-14839.php.

Root cause

BetterReflection synthesizes the magic enum members (ReflectionClass::getEnumProperties()): the name property on the UnitEnum interface is created with the native type string. PHPStan already special-cases concrete enum classes (producing a union of constant-string case names) and individual enum-case objects (EnumCaseObjectType, producing a single ConstantStringType) — both of which are non-falsy. But values typed only by the UnitEnum/BackedEnum interface (or a template bounded by them) never hit those special cases and fell back to the synthesized string.

The fix narrows the name property to non-falsy-string at the single place where the interface-declared property is reflected, which also covers BackedEnum (inherits name from UnitEnum) and @template T of \UnitEnum parameters.

Test

tests/PHPStan/Analyser/nsrt/bug-14839.php asserts:

  • Foo $foo / Bar $bar (concrete enums) → 'A'|'B' (already correct, kept as a guard).
  • \UnitEnum $unon-falsy-string (was string).
  • \BackedEnum $bnon-falsy-string for ->name (was string) and int|string for ->value (unchanged).
  • @template T of \UnitEnum parameter → non-falsy-string (was string).

Verified the test fails before the fix (the UnitEnum/BackedEnum/template assertions reported string) and passes after.

Analogous cases probed

  • Concrete enum types and individual enum-case objects (EnumCaseObjectType) — already non-falsy, no change needed.
  • The backed-enum value property — intentionally left as int|string, since a backing value may legitimately be an empty string or "0".

Fixes phpstan/phpstan#14839

phpstan-bot and others added 2 commits June 18, 2026 07:11
…um`/`BackedEnum` interfaces

- In `PhpClassReflectionExtension::createProperty()`, set the PHPDoc type of the
  magic `name` property to `non-falsy-string` when it is declared on the
  `UnitEnum` interface (also inherited by `BackedEnum` and any `T of UnitEnum`
  template bound). Previously BetterReflection synthesized it as plain `string`.
- Enum case names are always valid PHP labels, so they can never be empty or "0".
- Concrete enum types (`Foo $foo`) and individual enum case objects
  (`Foo::A->name`) were already correct — they resolve to a union of constant
  strings / a single constant string, which are non-falsy. The backed-enum
  `value` property is intentionally left as-is, since it may legitimately be an
  empty string or "0".
Comment thread tests/PHPStan/Analyser/nsrt/bug-14839.php
phpstan-bot and others added 3 commits June 18, 2026 08:01
Move the UnitEnum/BackedEnum interface `name` narrowing next to the
existing concrete-enum `name`/`value` narrowing, so all the synthesized
enum-property logic lives in one block instead of being split across the
method. The interface branch produces non-falsy-string; `value` is
deliberately left as its native int|string (a backing value may be "" or
"0"), which the test now documents and guards with `Bar::value`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@staabm staabm requested a review from VincentLanglet June 18, 2026 08:37
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.

magic "name" property on enum case should be non-falsy-string

2 participants