Skip to content

Detect enum cases through ReflectionClassConstant::isEnumCase() before reading a class constant's value expression - #6803

Open
phpstan-bot wants to merge 1 commit into
phpstan:2.3.xfrom
phpstan-bot:create-pull-request/patch-437si1r
Open

phpstan-bot wants to merge 1 commit into
phpstan:2.3.xfrom
phpstan-bot:create-pull-request/patch-437si1r

Conversation

@phpstan-bot

Copy link
Copy Markdown
Collaborator

Summary

Running PHPStan on PHP 7.4 with phpVersion: 80500 crashed on round(1.5) with LogicException: This enum case does not have a value. The PHP 8.4+ round() signature uses RoundingMode::HalfAwayFromZero as a default value, and PHPStan tried to read the value expression of that unit enum case.

Changes

  • src/Reflection/InitializerExprTypeResolver.php: getClassConstFetchTypeByReflection() now fetches the native reflection constant once and returns EnumCaseObjectType when isEnumCase() is true. This happens before the static path (getValueExpression()) and the object path (ClassReflection::getConstant()->getValueExpr()); both crashed before.
  • turbo-ext/src/InitializerExprTypeResolver.cpp: the same change in the native mirror. The now unused classReflectionIsEnum helper is removed.
  • src/PhpDoc/TypeNodeResolver.php: same check for @param Foo::Case, Foo::Prefix* wildcards and array shape keys array{Foo::Case: …}.
  • src/PhpDoc/ConstExprNodeResolver.php: same check for constant expressions in PHPDocs (e.g. @method void m($a = Suit::Hearts)).

Root cause

On PHP < 8.0, BetterReflectionProvider and ClassLikeHandler wrap enums in the generic ReflectionClass adapter, because the ReflectionEnum adapter cannot be loaded there. As a result ClassReflection::isEnum() / hasEnumCase() return false. Every place that guarded getValueExpression() with isEnum() && hasEnumCase() then called getValueExpression() on a ReflectionEnumCase, which throws for unit cases. ReflectionClassConstant::isEnumCase() works the same with both adapters, so the guards now use it.

All five sites crashed when I simulated the PHP 7.4 adapter selection (both the PHP code and the turbo extension), and none crash after the fix.

There was also a separate crash on every PHP version: an array shape key array{Suit::Hearts: int} called getValueExpression() without any enum guard. It now resolves to the enum case and is reported as "cannot be used as an array shape key". A backed enum case key also no longer silently resolves to its backing value.

Test

AnalyserIntegrationTest::testBug15380 analyses a fixture with a unit enum. The fixture uses enum cases in a class constant, a parameter default, a static and a dynamic ($suit::Spades) class constant fetch, @param Suit::Hearts, @param Suit::*, an @method default, an array shape key, and calls round(). The test does not require PHP 8.1, so it exercises the reported path on the PHP 7.4 CI job. There, the enum.caseOutsideOfEnum errors caused by the missing enum support are filtered out. Without the fix the test throws the LogicException, also under the simulated PHP 7.4 adapter selection.

Fixes phpstan/phpstan#15380

…ore reading a class constant's value expression

- On a PHP < 8.0 runtime, enums are reflected through the generic `ReflectionClass` adapter, so `ClassReflection::isEnum()` returns false and `Foo::Case` fell through to `getValueExpression()`, which throws `LogicException` for unit enum cases. This crashed e.g. `round()` with `phpVersion: 80500`, whose signature defaults to `RoundingMode::HalfAwayFromZero`.
- `InitializerExprTypeResolver::getClassConstFetchTypeByReflection()` now asks the native reflection constant `isEnumCase()`, covering both the static (`Foo::Case`) and object (`$foo::Case`) paths. The turbo-ext C++ mirror got the same change.
- Same fix in the analogous PHPDoc sites: `TypeNodeResolver` (`@param Foo::Case`, `Foo::*` wildcards, array shape keys) and `ConstExprNodeResolver` (`Foo::Case` defaults in `@method`).
- An array shape key `array{Suit::Hearts: int}` crashed on every PHP version. It now resolves to the enum case and is reported as an unresolvable type instead of crashing. A backed enum case key no longer silently resolves to its backing value.
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.

PHPStan crashes with LogicException: This enum case does not have a value when analysing PHP 8.5 round() from PHP 7.4

1 participant