Skip to content

Commit

Permalink
Infer explicit mixed when instantiating generic class with unknown te…
Browse files Browse the repository at this point in the history
…mplate types only in bleeding edge
  • Loading branch information
ondrejmirtes committed Feb 28, 2022
1 parent b49df58 commit 089d4c6
Show file tree
Hide file tree
Showing 9 changed files with 104 additions and 2 deletions.
1 change: 1 addition & 0 deletions conf/bleedingEdge.neon
Expand Up @@ -2,5 +2,6 @@ parameters:
featureToggles:
bleedingEdge: true
skipCheckGenericClasses: []
explicitMixedInUnknownGenericNew: true
stubFiles:
- ../stubs/bleedingEdge/Countable.stub
2 changes: 2 additions & 0 deletions conf/config.neon
Expand Up @@ -27,6 +27,7 @@ parameters:
- CallbackFilterIterator
- FilterIterator
- RecursiveCallbackFilterIterator
explicitMixedInUnknownGenericNew: false
fileExtensions:
- php
checkAdvancedIsset: false
Expand Down Expand Up @@ -200,6 +201,7 @@ parametersSchema:
bleedingEdge: bool(),
disableRuntimeReflectionProvider: bool(),
skipCheckGenericClasses: listOf(string()),
explicitMixedInUnknownGenericNew: bool(),
])
fileExtensions: listOf(string())
checkAdvancedIsset: bool()
Expand Down
2 changes: 2 additions & 0 deletions src/Analyser/DirectScopeFactory.php
Expand Up @@ -39,6 +39,7 @@ public function __construct(
private bool $treatPhpDocTypesAsCertain,
Container $container,
private PhpVersion $phpVersion,
private bool $explicitMixedInUnknownGenericNew,
)
{
$this->dynamicConstantNames = $container->getParameter('dynamicConstantNames');
Expand Down Expand Up @@ -107,6 +108,7 @@ public function create(
$this->treatPhpDocTypesAsCertain,
$afterExtractCall,
$parentScope,
$this->explicitMixedInUnknownGenericNew,
);
}

Expand Down
4 changes: 4 additions & 0 deletions src/Analyser/LazyScopeFactory.php
Expand Up @@ -24,13 +24,16 @@ class LazyScopeFactory implements ScopeFactory

private bool $treatPhpDocTypesAsCertain;

private bool $explicitMixedInUnknownGenericNew;

public function __construct(
private string $scopeClass,
private Container $container,
)
{
$this->dynamicConstantNames = $container->getParameter('dynamicConstantNames');
$this->treatPhpDocTypesAsCertain = $container->getParameter('treatPhpDocTypesAsCertain');
$this->explicitMixedInUnknownGenericNew = $this->container->getParameter('featureToggles')['explicitMixedInUnknownGenericNew'];
}

/**
Expand Down Expand Up @@ -96,6 +99,7 @@ public function create(
$this->treatPhpDocTypesAsCertain,
$afterExtractCall,
$parentScope,
$this->explicitMixedInUnknownGenericNew,
);
}

Expand Down
31 changes: 30 additions & 1 deletion src/Analyser/MutatingScope.php
Expand Up @@ -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 === '') {
Expand Down Expand Up @@ -2894,6 +2895,7 @@ public function doNotTreatPhpDocTypesAsCertain(): Scope
false,
$this->afterExtractCall,
$this->parentScope,
$this->explicitMixedInUnknownGenericNew,
);
}

Expand Down Expand Up @@ -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,
);
}

Expand Down
1 change: 1 addition & 0 deletions src/Testing/PHPStanTestCase.php
Expand Up @@ -162,6 +162,7 @@ public function createScopeFactory(ReflectionProvider $reflectionProvider, TypeS
$this->shouldTreatPhpDocTypesAsCertain(),
$container,
$container->getByType(PhpVersion::class),
$container->getParameter('featureToggles')['explicitMixedInUnknownGenericNew'],
);
}

Expand Down
@@ -0,0 +1,34 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Properties;

use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleLevelHelper;
use PHPStan\Testing\RuleTestCase;

/**
* @extends RuleTestCase<TypesAssignedToPropertiesRule>
*/
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 [];
}

}
Expand Up @@ -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
Expand Down Expand Up @@ -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<int, string>) does not accept ArrayObject<(int|string), mixed>.',
13,
],
]);
}

}
@@ -0,0 +1,16 @@
<?php

namespace GenericObjectUnspecifiedTemplateTypes;

class Foo
{

/** @var \ArrayObject<int, string> */
private $obj;

public function __construct()
{
$this->obj = new \ArrayObject();
}

}

0 comments on commit 089d4c6

Please sign in to comment.