From 089d4c6fb6eb709c44123548d33990113d174b86 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 28 Feb 2022 10:57:45 +0100 Subject: [PATCH] Infer explicit mixed when instantiating generic class with unknown template types only in bleeding edge --- conf/bleedingEdge.neon | 1 + conf/config.neon | 2 ++ src/Analyser/DirectScopeFactory.php | 2 ++ src/Analyser/LazyScopeFactory.php | 4 +++ src/Analyser/MutatingScope.php | 31 ++++++++++++++++- src/Testing/PHPStanTestCase.php | 1 + ...gnedToPropertiesRuleNoBleedingEdgeTest.php | 34 +++++++++++++++++++ .../TypesAssignedToPropertiesRuleTest.php | 15 +++++++- ...eric-object-unspecified-template-types.php | 16 +++++++++ 9 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleNoBleedingEdgeTest.php create mode 100644 tests/PHPStan/Rules/Properties/data/generic-object-unspecified-template-types.php diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 82f842fe44..5af3a763db 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -2,5 +2,6 @@ parameters: featureToggles: bleedingEdge: true skipCheckGenericClasses: [] + explicitMixedInUnknownGenericNew: true stubFiles: - ../stubs/bleedingEdge/Countable.stub diff --git a/conf/config.neon b/conf/config.neon index 09b9017bc3..af1f949a1c 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -27,6 +27,7 @@ parameters: - CallbackFilterIterator - FilterIterator - RecursiveCallbackFilterIterator + explicitMixedInUnknownGenericNew: false fileExtensions: - php checkAdvancedIsset: false @@ -200,6 +201,7 @@ parametersSchema: bleedingEdge: bool(), disableRuntimeReflectionProvider: bool(), skipCheckGenericClasses: listOf(string()), + explicitMixedInUnknownGenericNew: bool(), ]) fileExtensions: listOf(string()) checkAdvancedIsset: bool() diff --git a/src/Analyser/DirectScopeFactory.php b/src/Analyser/DirectScopeFactory.php index eb4f890e06..2299cd7dd2 100644 --- a/src/Analyser/DirectScopeFactory.php +++ b/src/Analyser/DirectScopeFactory.php @@ -39,6 +39,7 @@ public function __construct( private bool $treatPhpDocTypesAsCertain, Container $container, private PhpVersion $phpVersion, + private bool $explicitMixedInUnknownGenericNew, ) { $this->dynamicConstantNames = $container->getParameter('dynamicConstantNames'); @@ -107,6 +108,7 @@ public function create( $this->treatPhpDocTypesAsCertain, $afterExtractCall, $parentScope, + $this->explicitMixedInUnknownGenericNew, ); } diff --git a/src/Analyser/LazyScopeFactory.php b/src/Analyser/LazyScopeFactory.php index 3ed6b777c0..e704d36419 100644 --- a/src/Analyser/LazyScopeFactory.php +++ b/src/Analyser/LazyScopeFactory.php @@ -24,6 +24,8 @@ class LazyScopeFactory implements ScopeFactory private bool $treatPhpDocTypesAsCertain; + private bool $explicitMixedInUnknownGenericNew; + public function __construct( private string $scopeClass, private Container $container, @@ -31,6 +33,7 @@ public function __construct( { $this->dynamicConstantNames = $container->getParameter('dynamicConstantNames'); $this->treatPhpDocTypesAsCertain = $container->getParameter('treatPhpDocTypesAsCertain'); + $this->explicitMixedInUnknownGenericNew = $this->container->getParameter('featureToggles')['explicitMixedInUnknownGenericNew']; } /** @@ -96,6 +99,7 @@ public function create( $this->treatPhpDocTypesAsCertain, $afterExtractCall, $parentScope, + $this->explicitMixedInUnknownGenericNew, ); } diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 75b4663ef1..a152b831a7 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -200,6 +200,7 @@ public function __construct( private bool $treatPhpDocTypesAsCertain = true, private bool $afterExtractCall = false, private ?Scope $parentScope = null, + private bool $explicitMixedInUnknownGenericNew = false, ) { if ($namespace === '') { @@ -2894,6 +2895,7 @@ public function doNotTreatPhpDocTypesAsCertain(): Scope false, $this->afterExtractCall, $this->parentScope, + $this->explicitMixedInUnknownGenericNew, ); } @@ -5649,9 +5651,36 @@ private function exactInstantiation(New_ $node, string $className): ?Type $constructorMethod->getVariants(), ); + if ($this->explicitMixedInUnknownGenericNew) { + return new GenericObjectType( + $resolvedClassName, + $classReflection->typeMapToList($parametersAcceptor->getResolvedTemplateTypeMap()), + ); + } + + $resolvedPhpDoc = $classReflection->getResolvedPhpDoc(); + if ($resolvedPhpDoc === null) { + return $objectType; + } + + $list = []; + $typeMap = $parametersAcceptor->getResolvedTemplateTypeMap(); + foreach ($resolvedPhpDoc->getTemplateTags() as $tag) { + $templateType = $typeMap->getType($tag->getName()); + if ($templateType !== null) { + $list[] = $templateType; + continue; + } + $bound = $tag->getBound(); + if ($bound instanceof MixedType && $bound->isExplicitMixed()) { + $bound = new MixedType(false); + } + $list[] = $bound; + } + return new GenericObjectType( $resolvedClassName, - $classReflection->typeMapToList($parametersAcceptor->getResolvedTemplateTypeMap()), + $list, ); } diff --git a/src/Testing/PHPStanTestCase.php b/src/Testing/PHPStanTestCase.php index 07b0e70ffb..b9653bb434 100644 --- a/src/Testing/PHPStanTestCase.php +++ b/src/Testing/PHPStanTestCase.php @@ -162,6 +162,7 @@ public function createScopeFactory(ReflectionProvider $reflectionProvider, TypeS $this->shouldTreatPhpDocTypesAsCertain(), $container, $container->getByType(PhpVersion::class), + $container->getParameter('featureToggles')['explicitMixedInUnknownGenericNew'], ); } diff --git a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleNoBleedingEdgeTest.php b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleNoBleedingEdgeTest.php new file mode 100644 index 0000000000..cbf65adb2c --- /dev/null +++ b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleNoBleedingEdgeTest.php @@ -0,0 +1,34 @@ + + */ +class TypesAssignedToPropertiesRuleNoBleedingEdgeTest extends RuleTestCase +{ + + private bool $checkExplicitMixed = false; + + protected function getRule(): Rule + { + return new TypesAssignedToPropertiesRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed), new PropertyDescriptor(), new PropertyReflectionFinder()); + } + + public function testGenericObjectWithUnspecifiedTemplateTypes(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/generic-object-unspecified-template-types.php'], []); + } + + public static function getAdditionalConfigFiles(): array + { + // no bleeding edge + return []; + } + +} diff --git a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php index efebd69b3a..4ad293f48f 100644 --- a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php @@ -13,9 +13,11 @@ class TypesAssignedToPropertiesRuleTest extends RuleTestCase { + private bool $checkExplicitMixed = false; + protected function getRule(): Rule { - return new TypesAssignedToPropertiesRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false), new PropertyDescriptor(), new PropertyReflectionFinder()); + return new TypesAssignedToPropertiesRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed), new PropertyDescriptor(), new PropertyReflectionFinder()); } public function testTypesAssignedToProperties(): void @@ -387,4 +389,15 @@ public function testBug6117(): void $this->analyse([__DIR__ . '/data/bug-6117.php'], []); } + public function testGenericObjectWithUnspecifiedTemplateTypes(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/generic-object-unspecified-template-types.php'], [ + [ + 'Property GenericObjectUnspecifiedTemplateTypes\Foo::$obj (ArrayObject) does not accept ArrayObject<(int|string), mixed>.', + 13, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Properties/data/generic-object-unspecified-template-types.php b/tests/PHPStan/Rules/Properties/data/generic-object-unspecified-template-types.php new file mode 100644 index 0000000000..ae81bb5af0 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/generic-object-unspecified-template-types.php @@ -0,0 +1,16 @@ + */ + private $obj; + + public function __construct() + { + $this->obj = new \ArrayObject(); + } + +}