Skip to content

Commit

Permalink
Fixed assigning generic object without a constructor (like SplObjectS…
Browse files Browse the repository at this point in the history
…torage) to a property
  • Loading branch information
ondrejmirtes committed Feb 11, 2021
1 parent 6ef87d1 commit 2017318
Show file tree
Hide file tree
Showing 4 changed files with 199 additions and 1 deletion.
37 changes: 36 additions & 1 deletion src/Analyser/MutatingScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
use PHPStan\Type\FloatType;
use PHPStan\Type\Generic\GenericClassStringType;
use PHPStan\Type\Generic\GenericObjectType;
use PHPStan\Type\Generic\TemplateType;
use PHPStan\Type\Generic\TemplateTypeHelper;
use PHPStan\Type\Generic\TemplateTypeMap;
use PHPStan\Type\GenericTypeVariableResolver;
Expand Down Expand Up @@ -4565,8 +4566,42 @@ private function exactInstantiation(New_ $node, string $className): ?Type
return $methodResult;
}

$objectType = new ObjectType($resolvedClassName);
if (!$classReflection->isGeneric()) {
return new ObjectType($resolvedClassName);
return $objectType;
}

$parentNode = $node->getAttribute('parent');
if (
(
$parentNode instanceof Expr\Assign
|| $parentNode instanceof Expr\AssignRef
)
&& $parentNode->var instanceof PropertyFetch
) {
$constructorVariant = ParametersAcceptorSelector::selectSingle($constructorMethod->getVariants());
$classTemplateTypes = $classReflection->getTemplateTypeMap()->getTypes();
$originalClassTemplateTypes = $classTemplateTypes;
foreach ($constructorVariant->getParameters() as $parameter) {
TypeTraverser::map($parameter->getType(), static function (Type $type, callable $traverse) use (&$classTemplateTypes): Type {
if ($type instanceof TemplateType && array_key_exists($type->getName(), $classTemplateTypes)) {
$classTemplateType = $classTemplateTypes[$type->getName()];
if ($classTemplateType instanceof TemplateType && $classTemplateType->getScope()->equals($type->getScope())) {
unset($classTemplateTypes[$type->getName()]);
}
return $type;
}

return $traverse($type);
});
}

if (count($classTemplateTypes) === count($originalClassTemplateTypes)) {
$propertyType = $this->getType($parentNode->var);
if ($objectType->isSuperTypeOf($propertyType)->yes()) {
return $propertyType;
}
}
}

if ($constructorMethod instanceof DummyConstructorReflection || $constructorMethod->getDeclaringClass()->getName() !== $classReflection->getName()) {
Expand Down
6 changes: 6 additions & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10772,6 +10772,11 @@ public function dataBug4436(): array
return $this->gatherAssertTypes(__DIR__ . '/data/bug-4436.php');
}

public function dataBug3777(): array
{
return $this->gatherAssertTypes(__DIR__ . '/../Rules/Properties/data/bug-3777.php');
}

/**
* @param string $file
* @return array<string, mixed[]>
Expand Down Expand Up @@ -10988,6 +10993,7 @@ private function gatherAssertTypes(string $file): array
* @dataProvider dataBug4500
* @dataProvider dataBug4504
* @dataProvider dataBug4436
* @dataProvider dataBug3777
* @param string $assertType
* @param string $file
* @param mixed ...$args
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,4 +163,26 @@ public function testTypesAssignedToStaticPropertiesExpressionNames(): void
]);
}

public function testBug3777(): void
{
$this->analyse([__DIR__ . '/data/bug-3777.php'], [
[
'Property Bug3777\Bar::$foo (Bug3777\Foo<stdClass>) does not accept Bug3777\Fooo<object>.',
58,
],
[
'Property Bug3777\Ipsum::$ipsum (Bug3777\Lorem<stdClass, Exception>) does not accept Bug3777\Lorem<Exception, stdClass>.',
95,
],
[
'Property Bug3777\Ipsum2::$lorem2 (Bug3777\Lorem2<stdClass, Exception>) does not accept Bug3777\Lorem2<stdClass, object>.',
129,
],
[
'Property Bug3777\Ipsum2::$ipsum2 (Bug3777\Lorem2<stdClass, Exception>) does not accept Bug3777\Lorem2<Exception, object>.',
131,
],
]);
}

}
135 changes: 135 additions & 0 deletions tests/PHPStan/Rules/Properties/data/bug-3777.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
<?php

namespace Bug3777;

use function PHPStan\Analyser\assertType;

class HelloWorld
{
/**
* @var \SplObjectStorage<\DateTimeImmutable, null>
*/
public $dates;

public function __construct()
{
$this->dates = new \SplObjectStorage();
assertType('SplObjectStorage<DateTimeImmutable, null>', $this->dates);
}
}

/** @template T of object */
class Foo
{

public function __construct()
{

}

}

/** @template T of object */
class Fooo
{

}

class Bar
{

/** @var Foo<\stdClass> */
private $foo;

/** @var Fooo<\stdClass> */
private $fooo;

public function __construct()
{
$this->foo = new Foo();
assertType('Bug3777\Foo<stdClass>', $this->foo);

$this->fooo = new Fooo();
assertType('Bug3777\Fooo<stdClass>', $this->fooo);
}

public function doBar()
{
$this->foo = new Fooo();
assertType('Bug3777\Fooo<object>', $this->foo);
}

}

/**
* @template T of object
* @template U of object
*/
class Lorem
{

/**
* @param T $t
* @param U $u
*/
public function __construct($t, $u)
{

}

}

class Ipsum
{

/** @var Lorem<\stdClass, \Exception> */
private $lorem;

/** @var Lorem<\stdClass, \Exception> */
private $ipsum;

public function __construct()
{
$this->lorem = new Lorem(new \stdClass, new \Exception());
assertType('Bug3777\Lorem<stdClass, Exception>', $this->lorem);
$this->ipsum = new Lorem(new \Exception(), new \stdClass);
assertType('Bug3777\Lorem<Exception, stdClass>', $this->ipsum);
}

}

/**
* @template T of object
* @template U of object
*/
class Lorem2
{

/**
* @param T $t
*/
public function __construct($t)
{

}

}

class Ipsum2
{

/** @var Lorem2<\stdClass, \Exception> */
private $lorem2;

/** @var Lorem2<\stdClass, \Exception> */
private $ipsum2;

public function __construct()
{
$this->lorem2 = new Lorem2(new \stdClass);
assertType('Bug3777\Lorem2<stdClass, object>', $this->lorem2);
$this->ipsum2 = new Lorem2(new \Exception());
assertType('Bug3777\Lorem2<Exception, object>', $this->ipsum2);
}

}

0 comments on commit 2017318

Please sign in to comment.