Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -1122,7 +1122,7 @@ parameters:
-
rawMessage: Doing instanceof PHPStan\Type\Generic\GenericObjectType is error-prone and deprecated.
identifier: phpstanApi.instanceofType
count: 3
count: 4
path: src/Type/Generic/GenericObjectType.php

-
Expand All @@ -1134,7 +1134,7 @@ parameters:
-
rawMessage: 'Doing instanceof PHPStan\Type\ObjectType is error-prone and deprecated. Use Type::isObject() or Type::getObjectClassNames() instead.'
identifier: phpstanApi.instanceofType
count: 2
count: 3
path: src/Type/Generic/GenericObjectType.php

-
Expand Down
50 changes: 49 additions & 1 deletion src/Type/Generic/GenericObjectType.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use PHPStan\Type\ObjectType;
use PHPStan\Type\RecursionGuard;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\TypeWithClassName;
use PHPStan\Type\UnionType;
use PHPStan\Type\VerbosityLevel;
Expand Down Expand Up @@ -123,7 +124,17 @@
public function accepts(Type $type, bool $strictTypes): AcceptsResult
{
if ($type instanceof CompoundType) {
return $type->isAcceptedBy($this, $strictTypes);
$result = $type->isAcceptedBy($this, $strictTypes);
if (!$result->yes() && $type instanceof UnionType) {

Check warning on line 128 in src/Type/Generic/GenericObjectType.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.3, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ { if ($type instanceof CompoundType) { $result = $type->isAcceptedBy($this, $strictTypes); - if (!$result->yes() && $type instanceof UnionType) { + if ($result->no() && $type instanceof UnionType) { $mergedType = $this->mergeUnionMembers($type); if ($mergedType !== null) { $mergedResult = $this->isSuperTypeOfInternal($mergedType, true)->toAcceptsResult();

Check warning on line 128 in src/Type/Generic/GenericObjectType.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.4, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ { if ($type instanceof CompoundType) { $result = $type->isAcceptedBy($this, $strictTypes); - if (!$result->yes() && $type instanceof UnionType) { + if ($result->no() && $type instanceof UnionType) { $mergedType = $this->mergeUnionMembers($type); if ($mergedType !== null) { $mergedResult = $this->isSuperTypeOfInternal($mergedType, true)->toAcceptsResult();
$mergedType = $this->mergeUnionMembers($type);
if ($mergedType !== null) {
$mergedResult = $this->isSuperTypeOfInternal($mergedType, true)->toAcceptsResult();
if ($mergedResult->yes()) {

Check warning on line 132 in src/Type/Generic/GenericObjectType.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.3, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ $mergedType = $this->mergeUnionMembers($type); if ($mergedType !== null) { $mergedResult = $this->isSuperTypeOfInternal($mergedType, true)->toAcceptsResult(); - if ($mergedResult->yes()) { + if (!$mergedResult->no()) { return $mergedResult; } }

Check warning on line 132 in src/Type/Generic/GenericObjectType.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.4, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ $mergedType = $this->mergeUnionMembers($type); if ($mergedType !== null) { $mergedResult = $this->isSuperTypeOfInternal($mergedType, true)->toAcceptsResult(); - if ($mergedResult->yes()) { + if (!$mergedResult->no()) { return $mergedResult; } }
return $mergedResult;
}
}
}
return $result;
}

return $this->isSuperTypeOfInternal($type, true)->toAcceptsResult();
Expand Down Expand Up @@ -209,6 +220,43 @@
return $result;
}

private function mergeUnionMembers(UnionType $unionType): ?self
{
$className = $this->getClassName();
$paramCount = count($this->types);
$typeParameterArrays = [];

foreach ($unionType->getTypes() as $memberType) {
if (!$memberType instanceof ObjectType) {
return null;
}
$ancestor = $memberType->getAncestorWithClassName($className);
if (!$ancestor instanceof self) {
return null;
}
if (count($ancestor->getTypes()) !== $paramCount) {
return null;
}
foreach ($ancestor->getTypes() as $typeParam) {
if (count($typeParam->getReferencedTemplateTypes(TemplateTypeVariance::createInvariant())) > 0) {
return null;
}
}
$typeParameterArrays[] = $ancestor->getTypes();
}

$mergedTypes = [];
for ($i = 0; $i < $paramCount; $i++) {
$typesAtPosition = [];
foreach ($typeParameterArrays as $types) {
$typesAtPosition[] = $types[$i];
}
$mergedTypes[$i] = TypeCombinator::union(...$typesAtPosition);
}

return new self($className, $mergedTypes);
}

public function getClassReflection(): ?ClassReflection
{
if ($this->classReflection !== null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2850,4 +2850,9 @@ public function testBug13643(): void
$this->analyse([__DIR__ . '/data/bug-13643.php'], []);
}

public function testBug3136(): void
{
$this->analyse([__DIR__ . '/data/bug-3136.php'], []);
}

}
89 changes: 89 additions & 0 deletions tests/PHPStan/Rules/Functions/data/bug-3136.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?php declare(strict_types = 1);

namespace Bug3136;

interface TypeAorB {}
class TypeA implements TypeAorB {}
class TypeB implements TypeAorB {}
class TypeC implements TypeAorB {}

/** @template T of TypeAorB */
class Container{
/** @var T */
public $value;
/** @param T $value */
public function __construct(TypeAorB $value) { $this->value = $value; }
}

/** @template T of TypeAorB */
class SubContainer extends Container {
/** @param T $value */
public function __construct(TypeAorB $value) { parent::__construct($value); }
}

/**
* @template TKey
* @template TValue of TypeAorB
*/
class Pair {
/** @var TKey */
public $key;
/** @var TValue */
public $value;
/**
* @param TKey $key
* @param TValue $value
*/
public function __construct($key, TypeAorB $value) {
$this->key = $key;
$this->value = $value;
}
}

/**
* @template T of TypeAorB
* @param Container<T> $container
*/
function run(Container $container): void{
var_dump($container->value);
}

/**
* @template TKey
* @template TValue of TypeAorB
* @param Pair<TKey, TValue> $pair
*/
function runPair(Pair $pair): void{
var_dump($pair->key, $pair->value);
}

$a = new Container(new TypeA);
$b = new Container(new TypeB);

run($a);
run($b);

// union of two generic objects
foreach ([$a, $b] as $item){
run($item);
}

// union of three generic objects
$c = new Container(new TypeC);
foreach ([$a, $b, $c] as $item){
run($item);
}

// subclass union
$subA = new SubContainer(new TypeA);
$subB = new SubContainer(new TypeB);
foreach ([$subA, $subB] as $item){
run($item);
}

// multiple template parameters
$p1 = new Pair(1, new TypeA);
$p2 = new Pair(2, new TypeB);
foreach ([$p1, $p2] as $item){
runPair($item);
}
8 changes: 8 additions & 0 deletions tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4017,4 +4017,12 @@ public function testBug13272(): void
$this->analyse([__DIR__ . '/data/bug-13272.php'], []);
}

public function testBug3136(): void
{
$this->checkThisOnly = false;
$this->checkNullables = true;
$this->checkUnionTypes = true;
$this->analyse([__DIR__ . '/data/bug-3136.php'], []);
}

}
43 changes: 43 additions & 0 deletions tests/PHPStan/Rules/Methods/data/bug-3136.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php declare(strict_types = 1);

namespace Bug3136Method;

interface TypeAorB {}
class TypeA implements TypeAorB {}
class TypeB implements TypeAorB {}

/** @template T of TypeAorB */
class Container{
/** @var T */
public $value;
/** @param T $value */
public function __construct(TypeAorB $value) { $this->value = $value; }
}

class Runner {
/**
* @template T of TypeAorB
* @param Container<T> $container
*/
public function run(Container $container): void {
var_dump($container->value);
}

/**
* @template T of TypeAorB
* @param Container<T> $container
*/
public static function runStatic(Container $container): void {
var_dump($container->value);
}
}

$a = new Container(new TypeA);
$b = new Container(new TypeB);

$runner = new Runner();

foreach ([$a, $b] as $item){
$runner->run($item);
Runner::runStatic($item);
}
Loading