diff --git a/src/Type/Accessory/HasMethodType.php b/src/Type/Accessory/HasMethodType.php index 896c5ec28b..66d8365595 100644 --- a/src/Type/Accessory/HasMethodType.php +++ b/src/Type/Accessory/HasMethodType.php @@ -13,6 +13,7 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\AcceptsResult; use PHPStan\Type\CompoundType; +use PHPStan\Type\Enum\EnumCaseObjectType; use PHPStan\Type\ErrorType; use PHPStan\Type\IntersectionType; use PHPStan\Type\IsSuperTypeOfResult; @@ -166,6 +167,11 @@ public function getEnumCases(): array return []; } + public function getEnumCaseObject(): ?EnumCaseObjectType + { + return null; + } + public function traverse(callable $cb): Type { return $this; diff --git a/src/Type/Accessory/HasOffsetType.php b/src/Type/Accessory/HasOffsetType.php index b3c18326cb..bd5a03682d 100644 --- a/src/Type/Accessory/HasOffsetType.php +++ b/src/Type/Accessory/HasOffsetType.php @@ -11,6 +11,7 @@ use PHPStan\Type\CompoundType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; +use PHPStan\Type\Enum\EnumCaseObjectType; use PHPStan\Type\ErrorType; use PHPStan\Type\IntegerRangeType; use PHPStan\Type\IntersectionType; @@ -412,6 +413,11 @@ public function getEnumCases(): array return []; } + public function getEnumCaseObject(): ?EnumCaseObjectType + { + return null; + } + public function traverse(callable $cb): Type { return $this; diff --git a/src/Type/Accessory/HasOffsetValueType.php b/src/Type/Accessory/HasOffsetValueType.php index b36742ffd3..60a4478a06 100644 --- a/src/Type/Accessory/HasOffsetValueType.php +++ b/src/Type/Accessory/HasOffsetValueType.php @@ -13,6 +13,7 @@ use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\ConstantScalarType; +use PHPStan\Type\Enum\EnumCaseObjectType; use PHPStan\Type\ErrorType; use PHPStan\Type\IntegerRangeType; use PHPStan\Type\IntegerType; @@ -476,6 +477,11 @@ public function getEnumCases(): array return []; } + public function getEnumCaseObject(): ?EnumCaseObjectType + { + return null; + } + public function traverse(callable $cb): Type { $newValueType = $cb($this->valueType); diff --git a/src/Type/Accessory/HasPropertyType.php b/src/Type/Accessory/HasPropertyType.php index 6de5c55fb7..a364e32731 100644 --- a/src/Type/Accessory/HasPropertyType.php +++ b/src/Type/Accessory/HasPropertyType.php @@ -9,6 +9,7 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\AcceptsResult; use PHPStan\Type\CompoundType; +use PHPStan\Type\Enum\EnumCaseObjectType; use PHPStan\Type\ErrorType; use PHPStan\Type\IntersectionType; use PHPStan\Type\IsSuperTypeOfResult; @@ -149,6 +150,11 @@ public function getEnumCases(): array return []; } + public function getEnumCaseObject(): ?EnumCaseObjectType + { + return null; + } + public function traverse(callable $cb): Type { return $this; diff --git a/src/Type/CallableType.php b/src/Type/CallableType.php index fb17c04b4f..effded23eb 100644 --- a/src/Type/CallableType.php +++ b/src/Type/CallableType.php @@ -26,6 +26,7 @@ use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; +use PHPStan\Type\Enum\EnumCaseObjectType; use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\Generic\TemplateTypeHelper; use PHPStan\Type\Generic\TemplateTypeMap; @@ -645,6 +646,11 @@ public function getEnumCases(): array return []; } + public function getEnumCaseObject(): ?EnumCaseObjectType + { + return null; + } + public function isCommonCallable(): bool { return $this->isCommonCallable; diff --git a/src/Type/ClosureType.php b/src/Type/ClosureType.php index cdca5ba61e..b9267e05c5 100644 --- a/src/Type/ClosureType.php +++ b/src/Type/ClosureType.php @@ -35,6 +35,7 @@ use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantIntegerType; +use PHPStan\Type\Enum\EnumCaseObjectType; use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\Generic\TemplateTypeHelper; use PHPStan\Type\Generic\TemplateTypeMap; @@ -423,6 +424,11 @@ public function getEnumCases(): array return []; } + public function getEnumCaseObject(): ?EnumCaseObjectType + { + return null; + } + public function isCommonCallable(): bool { return $this->isCommonCallable; diff --git a/src/Type/Enum/EnumCaseObjectType.php b/src/Type/Enum/EnumCaseObjectType.php index ab1091846a..8b948b3f93 100644 --- a/src/Type/Enum/EnumCaseObjectType.php +++ b/src/Type/Enum/EnumCaseObjectType.php @@ -217,6 +217,11 @@ public function getEnumCases(): array return [$this]; } + public function getEnumCaseObject(): ?EnumCaseObjectType + { + return $this; + } + public function toPhpDocNode(): TypeNode { return new ConstTypeNode( diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index 95d615c0ea..70fcd2b54d 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -37,6 +37,7 @@ use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; +use PHPStan\Type\Enum\EnumCaseObjectType; use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\Generic\TemplateTypeMap; use PHPStan\Type\Generic\TemplateTypeVariance; @@ -1032,6 +1033,25 @@ public function getEnumCases(): array return array_values(array_intersect_key(...$compare)); } + public function getEnumCaseObject(): ?EnumCaseObjectType + { + $singleCase = null; + foreach ($this->types as $type) { + $caseObject = $type->getEnumCaseObject(); + if ($caseObject === null) { + continue; + } + + if ($singleCase !== null) { + return null; + } + + $singleCase = $caseObject; + } + + return $singleCase; + } + public function isCallable(): TrinaryLogic { return $this->intersectResults(static fn (Type $type): TrinaryLogic => $type->isCallable()); diff --git a/src/Type/IterableType.php b/src/Type/IterableType.php index eef9b9d44d..46e004ae23 100644 --- a/src/Type/IterableType.php +++ b/src/Type/IterableType.php @@ -7,6 +7,7 @@ use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\TrinaryLogic; +use PHPStan\Type\Enum\EnumCaseObjectType; use PHPStan\Type\Generic\GenericObjectType; use PHPStan\Type\Generic\TemplateMixedType; use PHPStan\Type\Generic\TemplateTypeMap; @@ -421,6 +422,11 @@ public function getEnumCases(): array return []; } + public function getEnumCaseObject(): ?EnumCaseObjectType + { + return null; + } + public function inferTemplateTypes(Type $receivedType): TemplateTypeMap { if ($receivedType instanceof UnionType || $receivedType instanceof IntersectionType) { diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index de92281a62..80a0bc2f0b 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -32,6 +32,7 @@ use PHPStan\Type\Constant\ConstantFloatType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; +use PHPStan\Type\Enum\EnumCaseObjectType; use PHPStan\Type\Generic\TemplateMixedType; use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\Traits\NonGeneralizableTypeTrait; @@ -320,6 +321,11 @@ public function getEnumCases(): array return []; } + public function getEnumCaseObject(): ?EnumCaseObjectType + { + return null; + } + public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array { return [new TrivialParametersAcceptor()]; diff --git a/src/Type/NeverType.php b/src/Type/NeverType.php index 79195ec997..c762202f0b 100644 --- a/src/Type/NeverType.php +++ b/src/Type/NeverType.php @@ -13,6 +13,7 @@ use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; +use PHPStan\Type\Enum\EnumCaseObjectType; use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\NonRemoveableTypeTrait; @@ -558,6 +559,11 @@ public function getEnumCases(): array return []; } + public function getEnumCaseObject(): ?EnumCaseObjectType + { + return null; + } + public function exponentiate(Type $exponent): Type { return $this; diff --git a/src/Type/NonexistentParentClassType.php b/src/Type/NonexistentParentClassType.php index 83a9f8f875..74fb77a130 100644 --- a/src/Type/NonexistentParentClassType.php +++ b/src/Type/NonexistentParentClassType.php @@ -13,6 +13,7 @@ use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; +use PHPStan\Type\Enum\EnumCaseObjectType; use PHPStan\Type\Traits\NonArrayTypeTrait; use PHPStan\Type\Traits\NonCallableTypeTrait; use PHPStan\Type\Traits\NonGeneralizableTypeTrait; @@ -212,6 +213,11 @@ public function getEnumCases(): array return []; } + public function getEnumCaseObject(): ?EnumCaseObjectType + { + return null; + } + public function exponentiate(Type $exponent): Type { return new ErrorType(); diff --git a/src/Type/ObjectShapeType.php b/src/Type/ObjectShapeType.php index 98b9389af0..57d48a0d0d 100644 --- a/src/Type/ObjectShapeType.php +++ b/src/Type/ObjectShapeType.php @@ -21,6 +21,7 @@ use PHPStan\Type\Accessory\HasPropertyType; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantStringType; +use PHPStan\Type\Enum\EnumCaseObjectType; use PHPStan\Type\Generic\TemplateTypeMap; use PHPStan\Type\Generic\TemplateTypeVariance; use PHPStan\Type\Traits\NonGeneralizableTypeTrait; @@ -463,6 +464,11 @@ public function getEnumCases(): array return []; } + public function getEnumCaseObject(): ?EnumCaseObjectType + { + return null; + } + public function traverse(callable $cb): Type { $properties = []; diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 55338b83c7..0ada7a301c 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -1516,6 +1516,17 @@ public function getEnumCases(): array return self::$enumCases[$cacheKey] = $cases; } + public function getEnumCaseObject(): ?EnumCaseObjectType + { + $cases = $this->getEnumCases(); + + if (count($cases) === 1) { + return $cases[0]; + } + + return null; + } + public function isCallable(): TrinaryLogic { $parametersAcceptors = RecursionGuard::run($this, fn () => $this->findCallableParametersAcceptors()); diff --git a/src/Type/ObjectWithoutClassType.php b/src/Type/ObjectWithoutClassType.php index e14f31b77d..c1417f24d6 100644 --- a/src/Type/ObjectWithoutClassType.php +++ b/src/Type/ObjectWithoutClassType.php @@ -4,6 +4,7 @@ use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; +use PHPStan\Type\Enum\EnumCaseObjectType; use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\ObjectTypeTrait; @@ -130,6 +131,11 @@ public function getEnumCases(): array return []; } + public function getEnumCaseObject(): ?EnumCaseObjectType + { + return null; + } + public function subtract(Type $type): Type { if ($type instanceof self) { diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php index 3ba08635ea..0e5404d8a9 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -15,6 +15,7 @@ use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection; use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection; use PHPStan\TrinaryLogic; +use PHPStan\Type\Enum\EnumCaseObjectType; use PHPStan\Type\Generic\GenericObjectType; use PHPStan\Type\Generic\TemplateTypeHelper; use PHPStan\Type\Traits\NonGeneralizableTypeTrait; @@ -539,6 +540,11 @@ public function getEnumCases(): array return $this->getStaticObjectType()->getEnumCases(); } + public function getEnumCaseObject(): ?EnumCaseObjectType + { + return $this->getStaticObjectType()->getEnumCaseObject(); + } + public function isArray(): TrinaryLogic { return $this->getStaticObjectType()->isArray(); diff --git a/src/Type/StrictMixedType.php b/src/Type/StrictMixedType.php index ed15141294..953cb19054 100644 --- a/src/Type/StrictMixedType.php +++ b/src/Type/StrictMixedType.php @@ -13,6 +13,7 @@ use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; +use PHPStan\Type\Enum\EnumCaseObjectType; use PHPStan\Type\Generic\TemplateMixedType; use PHPStan\Type\Generic\TemplateTypeMap; use PHPStan\Type\Generic\TemplateTypeVariance; @@ -445,6 +446,11 @@ public function getEnumCases(): array return []; } + public function getEnumCaseObject(): ?EnumCaseObjectType + { + return null; + } + public function traverse(callable $cb): Type { return $this; diff --git a/src/Type/Traits/LateResolvableTypeTrait.php b/src/Type/Traits/LateResolvableTypeTrait.php index df4fb851e1..4a5dd1e0b9 100644 --- a/src/Type/Traits/LateResolvableTypeTrait.php +++ b/src/Type/Traits/LateResolvableTypeTrait.php @@ -13,6 +13,7 @@ use PHPStan\Type\AcceptsResult; use PHPStan\Type\BooleanType; use PHPStan\Type\CompoundType; +use PHPStan\Type\Enum\EnumCaseObjectType; use PHPStan\Type\Generic\TemplateTypeMap; use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\LateResolvableType; @@ -358,6 +359,11 @@ public function getEnumCases(): array return $this->resolve()->getEnumCases(); } + public function getEnumCaseObject(): ?EnumCaseObjectType + { + return $this->resolve()->getEnumCaseObject(); + } + public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array { return $this->resolve()->getCallableParametersAcceptors($scope); diff --git a/src/Type/Traits/NonObjectTypeTrait.php b/src/Type/Traits/NonObjectTypeTrait.php index 0d55341b46..c441d3204c 100644 --- a/src/Type/Traits/NonObjectTypeTrait.php +++ b/src/Type/Traits/NonObjectTypeTrait.php @@ -10,6 +10,7 @@ use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; +use PHPStan\Type\Enum\EnumCaseObjectType; use PHPStan\Type\ErrorType; use PHPStan\Type\Type; @@ -126,6 +127,11 @@ public function getEnumCases(): array return []; } + public function getEnumCaseObject(): ?EnumCaseObjectType + { + return null; + } + public function getTemplateType(string $ancestorClassName, string $templateTypeName): Type { return new ErrorType(); diff --git a/src/Type/Type.php b/src/Type/Type.php index 6ab33a9cee..58d37a52a4 100644 --- a/src/Type/Type.php +++ b/src/Type/Type.php @@ -186,6 +186,8 @@ public function spliceArray(Type $offsetType, Type $lengthType, Type $replacemen */ public function getEnumCases(): array; + public function getEnumCaseObject(): ?EnumCaseObjectType; + /** * Returns a list of finite values. * diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index 0ecbf62ad3..b5db780c34 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -209,8 +209,8 @@ public static function union(Type ...$types): Type } elseif ($types[$i]->isString()->yes() && $types[$i]->isClassString()->no() && TypeUtils::getAccessoryTypes($types[$i]) === []) { $hasGenericScalarTypes[ConstantStringType::class] = true; } else { - $enumCases = $types[$i]->getEnumCases(); - if (count($enumCases) === 1) { + $enumCase = $types[$i]->getEnumCaseObject(); + if ($enumCase !== null) { $enumCaseTypes[$types[$i]->describe(VerbosityLevel::cache())] = $types[$i]; unset($types[$i]); diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index a2f5d0aa39..ee794af799 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -23,6 +23,7 @@ use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; +use PHPStan\Type\Enum\EnumCaseObjectType; use PHPStan\Type\Generic\GenericClassStringType; use PHPStan\Type\Generic\TemplateIterableType; use PHPStan\Type\Generic\TemplateMixedType; @@ -876,6 +877,17 @@ public function getEnumCases(): array ); } + public function getEnumCaseObject(): ?EnumCaseObjectType + { + $cases = $this->getEnumCases(); + + if (count($cases) === 1) { + return $cases[0]; + } + + return null; + } + public function isCallable(): TrinaryLogic { return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->isCallable());