Repository navigation
Detect enum cases through ReflectionClassConstant::isEnumCase() before reading a class constant's value expression - #6803
Open
phpstan-bot wants to merge 1 commit into
Conversation
…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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Running PHPStan on PHP 7.4 with
phpVersion: 80500crashed onround(1.5)withLogicException: This enum case does not have a value. The PHP 8.4+round()signature usesRoundingMode::HalfAwayFromZeroas 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 returnsEnumCaseObjectTypewhenisEnumCase()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 unusedclassReflectionIsEnumhelper is removed.src/PhpDoc/TypeNodeResolver.php: same check for@param Foo::Case,Foo::Prefix*wildcards and array shape keysarray{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,
BetterReflectionProviderandClassLikeHandlerwrap enums in the genericReflectionClassadapter, because theReflectionEnumadapter cannot be loaded there. As a resultClassReflection::isEnum()/hasEnumCase()return false. Every place that guardedgetValueExpression()withisEnum() && hasEnumCase()then calledgetValueExpression()on aReflectionEnumCase, 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}calledgetValueExpression()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::testBug15380analyses 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@methoddefault, an array shape key, and callsround(). The test does not require PHP 8.1, so it exercises the reported path on the PHP 7.4 CI job. There, theenum.caseOutsideOfEnumerrors caused by the missing enum support are filtered out. Without the fix the test throws theLogicException, also under the simulated PHP 7.4 adapter selection.Fixes phpstan/phpstan#15380