diff --git a/src/Type/Accessory/AccessoryLiteralStringType.php b/src/Type/Accessory/AccessoryLiteralStringType.php index ad874199e68..9eb9e3d6cd7 100644 --- a/src/Type/Accessory/AccessoryLiteralStringType.php +++ b/src/Type/Accessory/AccessoryLiteralStringType.php @@ -9,6 +9,7 @@ use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\ErrorType; use PHPStan\Type\FloatType; +use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\IntegerType; use PHPStan\Type\IntersectionType; use PHPStan\Type\MixedType; @@ -189,6 +190,11 @@ public function traverse(callable $cb): Type return $this; } + public function generalize(GeneralizePrecision $precision): Type + { + return new StringType(); + } + public static function __set_state(array $properties): Type { return new self(); diff --git a/src/Type/Accessory/AccessoryNonEmptyStringType.php b/src/Type/Accessory/AccessoryNonEmptyStringType.php index 048a1d946ea..0219866213b 100644 --- a/src/Type/Accessory/AccessoryNonEmptyStringType.php +++ b/src/Type/Accessory/AccessoryNonEmptyStringType.php @@ -8,6 +8,7 @@ use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\ErrorType; use PHPStan\Type\FloatType; +use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\IntegerType; use PHPStan\Type\IntersectionType; use PHPStan\Type\StringType; @@ -185,6 +186,11 @@ public function traverse(callable $cb): Type return $this; } + public function generalize(GeneralizePrecision $precision): Type + { + return new StringType(); + } + public static function __set_state(array $properties): Type { return new self(); diff --git a/src/Type/Accessory/AccessoryNumericStringType.php b/src/Type/Accessory/AccessoryNumericStringType.php index 1a2123b833e..28438a2a372 100644 --- a/src/Type/Accessory/AccessoryNumericStringType.php +++ b/src/Type/Accessory/AccessoryNumericStringType.php @@ -8,6 +8,7 @@ use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\ErrorType; use PHPStan\Type\FloatType; +use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\IntegerType; use PHPStan\Type\IntersectionType; use PHPStan\Type\StringType; @@ -181,6 +182,11 @@ public function traverse(callable $cb): Type return $this; } + public function generalize(GeneralizePrecision $precision): Type + { + return new StringType(); + } + public static function __set_state(array $properties): Type { return new self(); diff --git a/src/Type/Accessory/HasMethodType.php b/src/Type/Accessory/HasMethodType.php index 1ab34073c9f..e63285cdebc 100644 --- a/src/Type/Accessory/HasMethodType.php +++ b/src/Type/Accessory/HasMethodType.php @@ -11,6 +11,7 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\CompoundType; use PHPStan\Type\IntersectionType; +use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\NonRemoveableTypeTrait; use PHPStan\Type\Traits\ObjectTypeTrait; @@ -28,6 +29,7 @@ class HasMethodType implements AccessoryType, CompoundType use NonGenericTypeTrait; use UndecidedComparisonCompoundTypeTrait; use NonRemoveableTypeTrait; + use NonGeneralizableTypeTrait; /** @api */ public function __construct(private string $methodName) diff --git a/src/Type/Accessory/HasOffsetType.php b/src/Type/Accessory/HasOffsetType.php index 5804ded061a..cbe2da116f6 100644 --- a/src/Type/Accessory/HasOffsetType.php +++ b/src/Type/Accessory/HasOffsetType.php @@ -11,6 +11,7 @@ use PHPStan\Type\Traits\MaybeCallableTypeTrait; use PHPStan\Type\Traits\MaybeIterableTypeTrait; use PHPStan\Type\Traits\MaybeObjectTypeTrait; +use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\NonRemoveableTypeTrait; use PHPStan\Type\Traits\TruthyBooleanTypeTrait; @@ -30,6 +31,7 @@ class HasOffsetType implements CompoundType, AccessoryType use NonGenericTypeTrait; use UndecidedComparisonCompoundTypeTrait; use NonRemoveableTypeTrait; + use NonGeneralizableTypeTrait; /** @api */ public function __construct(private Type $offsetType) diff --git a/src/Type/Accessory/HasPropertyType.php b/src/Type/Accessory/HasPropertyType.php index d5810851254..1d650578788 100644 --- a/src/Type/Accessory/HasPropertyType.php +++ b/src/Type/Accessory/HasPropertyType.php @@ -7,6 +7,7 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\CompoundType; use PHPStan\Type\IntersectionType; +use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\NonRemoveableTypeTrait; use PHPStan\Type\Traits\ObjectTypeTrait; @@ -23,6 +24,7 @@ class HasPropertyType implements AccessoryType, CompoundType use NonGenericTypeTrait; use UndecidedComparisonCompoundTypeTrait; use NonRemoveableTypeTrait; + use NonGeneralizableTypeTrait; /** @api */ public function __construct(private string $propertyName) diff --git a/src/Type/Accessory/NonEmptyArrayType.php b/src/Type/Accessory/NonEmptyArrayType.php index 9c4419d69b8..9ed064559cd 100644 --- a/src/Type/Accessory/NonEmptyArrayType.php +++ b/src/Type/Accessory/NonEmptyArrayType.php @@ -10,6 +10,7 @@ use PHPStan\Type\IntersectionType; use PHPStan\Type\MixedType; use PHPStan\Type\Traits\MaybeCallableTypeTrait; +use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\NonObjectTypeTrait; use PHPStan\Type\Traits\NonRemoveableTypeTrait; @@ -28,6 +29,7 @@ class NonEmptyArrayType implements CompoundType, AccessoryType use NonGenericTypeTrait; use UndecidedComparisonCompoundTypeTrait; use NonRemoveableTypeTrait; + use NonGeneralizableTypeTrait; /** @api */ public function __construct() diff --git a/src/Type/ArrayType.php b/src/Type/ArrayType.php index 82272a1a3e0..99094470a40 100644 --- a/src/Type/ArrayType.php +++ b/src/Type/ArrayType.php @@ -18,6 +18,7 @@ use PHPStan\Type\Generic\TemplateTypeMap; use PHPStan\Type\Generic\TemplateTypeVariance; use PHPStan\Type\Traits\MaybeCallableTypeTrait; +use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonObjectTypeTrait; use PHPStan\Type\Traits\UndecidedBooleanTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonTypeTrait; @@ -36,6 +37,7 @@ class ArrayType implements Type use NonObjectTypeTrait; use UndecidedBooleanTypeTrait; use UndecidedComparisonTypeTrait; + use NonGeneralizableTypeTrait; private Type $keyType; diff --git a/src/Type/BooleanType.php b/src/Type/BooleanType.php index 70bf387db5e..5521b2bacb5 100644 --- a/src/Type/BooleanType.php +++ b/src/Type/BooleanType.php @@ -8,6 +8,7 @@ use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\Traits\NonCallableTypeTrait; +use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\NonIterableTypeTrait; use PHPStan\Type\Traits\NonObjectTypeTrait; @@ -27,6 +28,7 @@ class BooleanType implements Type use UndecidedComparisonTypeTrait; use NonGenericTypeTrait; use NonOffsetAccessibleTypeTrait; + use NonGeneralizableTypeTrait; /** @api */ public function __construct() diff --git a/src/Type/CallableType.php b/src/Type/CallableType.php index d13f571e2ef..cf05b87486a 100644 --- a/src/Type/CallableType.php +++ b/src/Type/CallableType.php @@ -16,6 +16,7 @@ use PHPStan\Type\Traits\MaybeIterableTypeTrait; use PHPStan\Type\Traits\MaybeObjectTypeTrait; use PHPStan\Type\Traits\MaybeOffsetAccessibleTypeTrait; +use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonRemoveableTypeTrait; use PHPStan\Type\Traits\TruthyBooleanTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; @@ -34,6 +35,7 @@ class CallableType implements CompoundType, ParametersAcceptor use TruthyBooleanTypeTrait; use UndecidedComparisonCompoundTypeTrait; use NonRemoveableTypeTrait; + use NonGeneralizableTypeTrait; /** @var array */ private array $parameters; diff --git a/src/Type/ClosureType.php b/src/Type/ClosureType.php index ed82d341179..a28df65cc14 100644 --- a/src/Type/ClosureType.php +++ b/src/Type/ClosureType.php @@ -22,6 +22,7 @@ use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\Generic\TemplateTypeHelper; use PHPStan\Type\Generic\TemplateTypeMap; +use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\NonOffsetAccessibleTypeTrait; use PHPStan\Type\Traits\NonRemoveableTypeTrait; @@ -39,6 +40,7 @@ class ClosureType implements TypeWithClassName, ParametersAcceptor use UndecidedComparisonTypeTrait; use NonOffsetAccessibleTypeTrait; use NonRemoveableTypeTrait; + use NonGeneralizableTypeTrait; private ObjectType $objectType; diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 51aaea08c5e..57432badcf4 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -625,6 +625,10 @@ public function generalize(GeneralizePrecision $precision): Type return $this; } + if ($precision->isTemplateArgument()) { + return $this->traverse(static fn (Type $type) => $type->generalize($precision)); + } + $arrayType = new ArrayType( TypeUtils::generalizeType($this->getKeyType(), $precision), TypeUtils::generalizeType($this->getItemType(), $precision), diff --git a/src/Type/FloatType.php b/src/Type/FloatType.php index 790752ed579..091f427e86d 100644 --- a/src/Type/FloatType.php +++ b/src/Type/FloatType.php @@ -7,6 +7,7 @@ use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Traits\NonCallableTypeTrait; +use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\NonIterableTypeTrait; use PHPStan\Type\Traits\NonObjectTypeTrait; @@ -28,6 +29,7 @@ class FloatType implements Type use NonGenericTypeTrait; use NonOffsetAccessibleTypeTrait; use NonRemoveableTypeTrait; + use NonGeneralizableTypeTrait; /** @api */ public function __construct() diff --git a/src/Type/GeneralizePrecision.php b/src/Type/GeneralizePrecision.php index 17830570824..0cce10ed32b 100644 --- a/src/Type/GeneralizePrecision.php +++ b/src/Type/GeneralizePrecision.php @@ -7,6 +7,7 @@ class GeneralizePrecision private const LESS_SPECIFIC = 1; private const MORE_SPECIFIC = 2; + private const TEMPLATE_ARGUMENT = 3; /** @var self[] */ private static array $registry; @@ -33,9 +34,25 @@ public static function moreSpecific(): self return self::create(self::MORE_SPECIFIC); } + /** @api */ + public static function templateArgument(): self + { + return self::create(self::TEMPLATE_ARGUMENT); + } + + public function isLessSpecific(): bool + { + return $this->value === self::LESS_SPECIFIC; + } + public function isMoreSpecific(): bool { return $this->value === self::MORE_SPECIFIC; } + public function isTemplateArgument(): bool + { + return $this->value === self::TEMPLATE_ARGUMENT; + } + } diff --git a/src/Type/Generic/TemplateTypeHelper.php b/src/Type/Generic/TemplateTypeHelper.php index b8d15d47e1a..6d5d6335745 100644 --- a/src/Type/Generic/TemplateTypeHelper.php +++ b/src/Type/Generic/TemplateTypeHelper.php @@ -2,11 +2,7 @@ namespace PHPStan\Type\Generic; -use PHPStan\Type\Constant\ConstantArrayType; -use PHPStan\Type\ConstantType; use PHPStan\Type\ErrorType; -use PHPStan\Type\GeneralizePrecision; -use PHPStan\Type\StringType; use PHPStan\Type\Type; use PHPStan\Type\TypeTraverser; @@ -64,23 +60,4 @@ public static function toArgument(Type $type): Type }); } - public static function generalizeType(Type $type): Type - { - return TypeTraverser::map($type, static function (Type $type, callable $traverse): Type { - if ($type instanceof ConstantType && !$type instanceof ConstantArrayType) { - return $type->generalize(GeneralizePrecision::lessSpecific()); - } - - if ($type->isNonEmptyString()->yes()) { - return new StringType(); - } - - if ($type->isLiteralString()->yes()) { - return new StringType(); - } - - return $traverse($type); - }); - } - } diff --git a/src/Type/Generic/TemplateTypeTrait.php b/src/Type/Generic/TemplateTypeTrait.php index 50c5e2b320d..bd93a9601eb 100644 --- a/src/Type/Generic/TemplateTypeTrait.php +++ b/src/Type/Generic/TemplateTypeTrait.php @@ -3,6 +3,7 @@ namespace PHPStan\Type\Generic; use PHPStan\TrinaryLogic; +use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\IntersectionType; use PHPStan\Type\MixedType; use PHPStan\Type\Type; @@ -194,7 +195,7 @@ public function inferTemplateTypes(Type $receivedType): TemplateTypeMap $resolvedBound = TemplateTypeHelper::resolveTemplateTypes($this->getBound(), $map); if ($resolvedBound->isSuperTypeOf($receivedType)->yes()) { return (new TemplateTypeMap([ - $this->name => $this->shouldGeneralizeInferredType() ? TemplateTypeHelper::generalizeType($receivedType) : $receivedType, + $this->name => $this->shouldGeneralizeInferredType() ? $receivedType->generalize(GeneralizePrecision::templateArgument()) : $receivedType, ]))->union($map); } diff --git a/src/Type/IntegerType.php b/src/Type/IntegerType.php index b60c1ae9f83..3ff1e487780 100644 --- a/src/Type/IntegerType.php +++ b/src/Type/IntegerType.php @@ -6,6 +6,7 @@ use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Traits\NonCallableTypeTrait; +use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\NonIterableTypeTrait; use PHPStan\Type\Traits\NonObjectTypeTrait; @@ -25,6 +26,7 @@ class IntegerType implements Type use UndecidedComparisonTypeTrait; use NonGenericTypeTrait; use NonOffsetAccessibleTypeTrait; + use NonGeneralizableTypeTrait; /** @api */ public function __construct() diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index d98c2681aef..60c3c59d36e 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -22,6 +22,8 @@ use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\Generic\TemplateTypeMap; use PHPStan\Type\Generic\TemplateTypeVariance; +use PHPStan\Type\Traits\NonGeneralizableTypeTrait; +use PHPStan\Type\Traits\NonRemoveableTypeTrait; use function array_map; use function count; use function implode; @@ -34,6 +36,9 @@ class IntersectionType implements CompoundType { + use NonRemoveableTypeTrait; + use NonGeneralizableTypeTrait; + /** @var Type[] */ private array $types; diff --git a/src/Type/IterableType.php b/src/Type/IterableType.php index a9e4c7e0d1b..3b696ed5142 100644 --- a/src/Type/IterableType.php +++ b/src/Type/IterableType.php @@ -12,6 +12,7 @@ use PHPStan\Type\Traits\MaybeCallableTypeTrait; use PHPStan\Type\Traits\MaybeObjectTypeTrait; use PHPStan\Type\Traits\MaybeOffsetAccessibleTypeTrait; +use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\UndecidedBooleanTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; use Traversable; @@ -27,6 +28,7 @@ class IterableType implements CompoundType use MaybeOffsetAccessibleTypeTrait; use UndecidedBooleanTypeTrait; use UndecidedComparisonCompoundTypeTrait; + use NonGeneralizableTypeTrait; /** @api */ public function __construct( diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index 1213e3fc42a..a813645869d 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -19,6 +19,7 @@ use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Generic\TemplateMixedType; use PHPStan\Type\Generic\TemplateType; +use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; use function sprintf; @@ -29,6 +30,7 @@ class MixedType implements CompoundType, SubtractableType use NonGenericTypeTrait; use UndecidedComparisonCompoundTypeTrait; + use NonGeneralizableTypeTrait; private ?Type $subtractedType; diff --git a/src/Type/NeverType.php b/src/Type/NeverType.php index 440de1a262c..ec0c5a66dd2 100644 --- a/src/Type/NeverType.php +++ b/src/Type/NeverType.php @@ -12,6 +12,7 @@ use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; +use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\NonRemoveableTypeTrait; use PHPStan\Type\Traits\UndecidedBooleanTypeTrait; @@ -25,6 +26,7 @@ class NeverType implements CompoundType use NonGenericTypeTrait; use UndecidedComparisonCompoundTypeTrait; use NonRemoveableTypeTrait; + use NonGeneralizableTypeTrait; /** @api */ public function __construct(private bool $isExplicit = false) diff --git a/src/Type/NonexistentParentClassType.php b/src/Type/NonexistentParentClassType.php index 9de84a4c966..21f5d783ebc 100644 --- a/src/Type/NonexistentParentClassType.php +++ b/src/Type/NonexistentParentClassType.php @@ -11,6 +11,7 @@ use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; use PHPStan\Type\Traits\NonCallableTypeTrait; +use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\NonIterableTypeTrait; use PHPStan\Type\Traits\NonOffsetAccessibleTypeTrait; @@ -29,6 +30,7 @@ class NonexistentParentClassType implements Type use NonGenericTypeTrait; use UndecidedComparisonTypeTrait; use NonRemoveableTypeTrait; + use NonGeneralizableTypeTrait; public function describe(VerbosityLevel $level): string { diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 671bb56205a..920631de840 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -33,6 +33,7 @@ use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\Enum\EnumCaseObjectType; use PHPStan\Type\Generic\GenericObjectType; +use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonTypeTrait; use Traversable; @@ -50,6 +51,7 @@ class ObjectType implements TypeWithClassName, SubtractableType use NonGenericTypeTrait; use UndecidedComparisonTypeTrait; + use NonGeneralizableTypeTrait; private const EXTRA_OFFSET_CLASSES = ['SimpleXMLElement', 'DOMNodeList', 'Threaded']; diff --git a/src/Type/ObjectWithoutClassType.php b/src/Type/ObjectWithoutClassType.php index 5aa5fe3fec7..b602f4651bd 100644 --- a/src/Type/ObjectWithoutClassType.php +++ b/src/Type/ObjectWithoutClassType.php @@ -3,6 +3,7 @@ namespace PHPStan\Type; use PHPStan\TrinaryLogic; +use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\ObjectTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonTypeTrait; @@ -15,6 +16,7 @@ class ObjectWithoutClassType implements SubtractableType use ObjectTypeTrait; use NonGenericTypeTrait; use UndecidedComparisonTypeTrait; + use NonGeneralizableTypeTrait; private ?Type $subtractedType; diff --git a/src/Type/ResourceType.php b/src/Type/ResourceType.php index afd00056653..1f1028b11a2 100644 --- a/src/Type/ResourceType.php +++ b/src/Type/ResourceType.php @@ -5,6 +5,7 @@ use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Traits\NonCallableTypeTrait; +use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\NonIterableTypeTrait; use PHPStan\Type\Traits\NonObjectTypeTrait; @@ -26,6 +27,7 @@ class ResourceType implements Type use UndecidedComparisonTypeTrait; use NonOffsetAccessibleTypeTrait; use NonRemoveableTypeTrait; + use NonGeneralizableTypeTrait; /** @api */ public function __construct() diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php index 18599c356eb..e4d2567b3dc 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -17,6 +17,7 @@ use PHPStan\Type\Enum\EnumCaseObjectType; use PHPStan\Type\Generic\GenericObjectType; use PHPStan\Type\Generic\TemplateTypeHelper; +use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonTypeTrait; use function array_keys; @@ -31,6 +32,7 @@ class StaticType implements TypeWithClassName, SubtractableType use NonGenericTypeTrait; use UndecidedComparisonTypeTrait; + use NonGeneralizableTypeTrait; private ?Type $subtractedType; diff --git a/src/Type/StrictMixedType.php b/src/Type/StrictMixedType.php index 09e00d0bd04..1250c77a5ee 100644 --- a/src/Type/StrictMixedType.php +++ b/src/Type/StrictMixedType.php @@ -13,6 +13,7 @@ use PHPStan\Type\Generic\TemplateMixedType; use PHPStan\Type\Generic\TemplateTypeMap; use PHPStan\Type\Generic\TemplateTypeVariance; +use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonRemoveableTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; @@ -21,6 +22,7 @@ class StrictMixedType implements CompoundType use UndecidedComparisonCompoundTypeTrait; use NonRemoveableTypeTrait; + use NonGeneralizableTypeTrait; public function getReferencedClasses(): array { diff --git a/src/Type/StringType.php b/src/Type/StringType.php index 8abcad27694..f151910b3d2 100644 --- a/src/Type/StringType.php +++ b/src/Type/StringType.php @@ -9,6 +9,7 @@ use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\Traits\MaybeCallableTypeTrait; +use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\NonIterableTypeTrait; use PHPStan\Type\Traits\NonObjectTypeTrait; @@ -26,6 +27,7 @@ class StringType implements Type use UndecidedBooleanTypeTrait; use UndecidedComparisonTypeTrait; use NonGenericTypeTrait; + use NonGeneralizableTypeTrait; /** @api */ public function __construct() diff --git a/src/Type/Traits/NonGeneralizableTypeTrait.php b/src/Type/Traits/NonGeneralizableTypeTrait.php new file mode 100644 index 00000000000..e943051c95c --- /dev/null +++ b/src/Type/Traits/NonGeneralizableTypeTrait.php @@ -0,0 +1,16 @@ +traverse(static fn (Type $type) => $type->generalize($precision)); + } + +} diff --git a/src/Type/Type.php b/src/Type/Type.php index 5c0b727d7cf..44d71ef154c 100644 --- a/src/Type/Type.php +++ b/src/Type/Type.php @@ -157,6 +157,8 @@ public function traverse(callable $cb): Type; */ public function tryRemove(Type $typeToRemove): ?Type; + public function generalize(GeneralizePrecision $precision): Type; + /** * @param mixed[] $properties */ diff --git a/src/Type/TypeUtils.php b/src/Type/TypeUtils.php index 4c89568237c..45425686766 100644 --- a/src/Type/TypeUtils.php +++ b/src/Type/TypeUtils.php @@ -116,13 +116,7 @@ public static function getAnyArrays(Type $type): array public static function generalizeType(Type $type, GeneralizePrecision $precision): Type { - return TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($precision): Type { - if ($type instanceof ConstantType) { - return $type->generalize($precision); - } - - return $traverse($type); - }); + return $type->generalize($precision); } /** diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index 77740ed0538..fb76f4d1128 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -22,6 +22,7 @@ use PHPStan\Type\Generic\TemplateTypeMap; use PHPStan\Type\Generic\TemplateTypeVariance; use PHPStan\Type\Generic\TemplateUnionType; +use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use function array_map; use function count; use function implode; @@ -32,6 +33,8 @@ class UnionType implements CompoundType { + use NonGeneralizableTypeTrait; + /** @var Type[] */ private array $types; diff --git a/src/Type/VoidType.php b/src/Type/VoidType.php index 166ca12ee49..db99d85e7ed 100644 --- a/src/Type/VoidType.php +++ b/src/Type/VoidType.php @@ -5,6 +5,7 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\Traits\FalseyBooleanTypeTrait; use PHPStan\Type\Traits\NonCallableTypeTrait; +use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\NonIterableTypeTrait; use PHPStan\Type\Traits\NonObjectTypeTrait; @@ -24,6 +25,7 @@ class VoidType implements Type use NonGenericTypeTrait; use UndecidedComparisonTypeTrait; use NonRemoveableTypeTrait; + use NonGeneralizableTypeTrait; /** @api */ public function __construct() diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index bcf743e8454..f27e9107b67 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -33,6 +33,10 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/generic-class-string.php'); + require_once __DIR__ . '/data/generic-generalization.php'; + + yield from $this->gatherAssertTypes(__DIR__ . '/data/generic-generalization.php'); + require_once __DIR__ . '/data/instanceof.php'; yield from $this->gatherAssertTypes(__DIR__ . '/data/date.php'); diff --git a/tests/PHPStan/Analyser/data/generic-generalization.php b/tests/PHPStan/Analyser/data/generic-generalization.php new file mode 100644 index 00000000000..34280cdd257 --- /dev/null +++ b/tests/PHPStan/Analyser/data/generic-generalization.php @@ -0,0 +1,75 @@ + $genericClassString + * @param array{foo: 42} $arrayShape + * @param numeric-string $numericString + * @param non-empty-string $nonEmptyString + */ +function testUnbounded( + string $classString, + string $genericClassString, + string $string, + array $arrayShape, + string $numericString, + string $nonEmptyString +): void { + assertType('string', unbounded('hello')); + assertType('string', unbounded('stdClass')); + assertType('class-string', unbounded($classString)); + assertType('class-string', unbounded($genericClassString)); + + assertType('string', unbounded(rand(0,1) === 1 ? 'hello' : $classString)); + + assertType('array{foo: int}', unbounded($arrayShape)); + + assertType('string', unbounded($numericString)); + assertType('string', unbounded($nonEmptyString)); +} + +/** + * @template T of string + * @param T $arg + * @return T + */ +function boundToString($arg) +{ + return $arg; +} + +/** + * @param class-string $classString + * @param class-string<\stdClass> $genericClassString + * @param non-empty-string $nonEmptyString + */ +function testBoundToString( + string $classString, + string $genericClassString, + string $nonEmptyString, + string $string +): void { + assertType('\'hello\'', boundToString('hello')); + assertType('\'stdClass\'', boundToString('stdClass')); + assertType('class-string', boundToString($classString)); + assertType('class-string', boundToString($genericClassString)); + + assertType('\'hello\'|class-string', boundToString(rand(0,1) === 1 ? 'hello' : $classString)); +} diff --git a/tests/PHPStan/Rules/Api/ApiClassImplementsRuleTest.php b/tests/PHPStan/Rules/Api/ApiClassImplementsRuleTest.php index c88aaa107a1..127fcd7877e 100644 --- a/tests/PHPStan/Rules/Api/ApiClassImplementsRuleTest.php +++ b/tests/PHPStan/Rules/Api/ApiClassImplementsRuleTest.php @@ -32,12 +32,12 @@ public function testRuleOutOfPhpStan(): void $this->analyse([__DIR__ . '/data/class-implements-out-of-phpstan.php'], [ [ 'Implementing PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider is not covered by backward compatibility promise. The interface might change in a minor PHPStan version.', - 16, + 17, $tip, ], [ 'Implementing PHPStan\Type\Type is not covered by backward compatibility promise. The interface might change in a minor PHPStan version.', - 50, + 51, $tip, ], ]); diff --git a/tests/PHPStan/Rules/Api/data/class-implements-out-of-phpstan.php b/tests/PHPStan/Rules/Api/data/class-implements-out-of-phpstan.php index ddcbbe3fb86..680bd40c57e 100644 --- a/tests/PHPStan/Rules/Api/data/class-implements-out-of-phpstan.php +++ b/tests/PHPStan/Rules/Api/data/class-implements-out-of-phpstan.php @@ -8,6 +8,7 @@ use PHPStan\Reflection\ClassMemberAccessAnswerer; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\DynamicFunctionThrowTypeExtension; +use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\Generic\TemplateTypeReference; use PHPStan\Type\Generic\TemplateTypeVariance; use PHPStan\Type\Type; @@ -289,6 +290,11 @@ public function traverse(callable $cb): \PHPStan\Type\Type // TODO: Implement traverse() method. } + public function generalize(GeneralizePrecision $precision): Type + { + // TODO: Implement generalize() method. + } + public function tryRemove(Type $typeToRemove): ?Type { // TODO: Implement tryRemove() method. diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index bbe47af18b6..9eaa82d4cb5 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -2104,6 +2104,10 @@ public function testBug5372(): void $this->checkNullables = true; $this->checkUnionTypes = true; $this->analyse([__DIR__ . '/data/bug-5372.php'], [ + [ + 'Parameter #1 $list of method Bug5372\Foo::takesStrings() expects Bug5372\Collection, Bug5372\Collection given.', + 68, + ], [ 'Parameter #1 $list of method Bug5372\Foo::takesStrings() expects Bug5372\Collection, Bug5372\Collection given.', 72, diff --git a/tests/PHPStan/Rules/Methods/data/bug-5372.php b/tests/PHPStan/Rules/Methods/data/bug-5372.php index 5626ef795d6..69a0a30a199 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-5372.php +++ b/tests/PHPStan/Rules/Methods/data/bug-5372.php @@ -64,7 +64,7 @@ public function doFoo(string $classString) $this->takesStrings($newCol); $newCol = $col->map(static fn(string $var): string => $classString); - assertType('Bug5372\Collection', $newCol); + assertType('Bug5372\Collection', $newCol); $this->takesStrings($newCol); $newCol = $col->map2(static fn(string $var): string => $classString);