Skip to content

Commit

Permalink
Merge branch refs/heads/1.10.x into 1.11.x
Browse files Browse the repository at this point in the history
  • Loading branch information
phpstan-bot committed Mar 1, 2024
2 parents 6f85669 + 4b978ba commit 39dc97b
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 23 deletions.
85 changes: 62 additions & 23 deletions src/Type/ConditionalType.php
Expand Up @@ -18,6 +18,14 @@ final class ConditionalType implements CompoundType, LateResolvableType
use LateResolvableTypeTrait;
use NonGeneralizableTypeTrait;

private ?Type $normalizedIf = null;

private ?Type $normalizedElse = null;

private ?Type $subjectWithTargetIntersectedType = null;

private ?Type $subjectWithTargetRemovedType = null;

public function __construct(
private Type $subject,
private Type $target,
Expand Down Expand Up @@ -113,37 +121,33 @@ protected function getResult(): Type
{
$isSuperType = $this->target->isSuperTypeOf($this->subject);

$intersectedType = TypeCombinator::intersect($this->subject, $this->target);
$removedType = TypeCombinator::remove($this->subject, $this->target);

$yesType = fn () => TypeTraverser::map(
!$this->negated ? $this->if : $this->else,
fn (Type $type, callable $traverse) => $type === $this->subject ? (!$this->negated ? $intersectedType : $removedType) : $traverse($type),
);
$noType = fn () => TypeTraverser::map(
!$this->negated ? $this->else : $this->if,
fn (Type $type, callable $traverse) => $type === $this->subject ? (!$this->negated ? $removedType : $intersectedType) : $traverse($type),
);

if ($isSuperType->yes()) {
return $yesType();
return !$this->negated ? $this->getNormalizedIf() : $this->getNormalizedElse();
}

if ($isSuperType->no()) {
return $noType();
return !$this->negated ? $this->getNormalizedElse() : $this->getNormalizedIf();
}

return TypeCombinator::union($yesType(), $noType());
return TypeCombinator::union(
$this->getNormalizedIf(),
$this->getNormalizedElse(),
);
}

public function traverse(callable $cb): Type
{
$subject = $cb($this->subject);
$target = $cb($this->target);
$if = $cb($this->if);
$else = $cb($this->else);

if ($this->subject === $subject && $this->target === $target && $this->if === $if && $this->else === $else) {
$if = $cb($this->getNormalizedIf());
$else = $cb($this->getNormalizedElse());

if (
$this->subject === $subject
&& $this->target === $target
&& $this->getNormalizedIf() === $if
&& $this->getNormalizedElse() === $else
) {
return $this;
}

Expand All @@ -158,10 +162,15 @@ public function traverseSimultaneously(Type $right, callable $cb): Type

$subject = $cb($this->subject, $right->subject);
$target = $cb($this->target, $right->target);
$if = $cb($this->if, $right->if);
$else = $cb($this->else, $right->else);

if ($this->subject === $subject && $this->target === $target && $this->if === $if && $this->else === $else) {
$if = $cb($this->getNormalizedIf(), $right->getNormalizedIf());
$else = $cb($this->getNormalizedElse(), $right->getNormalizedElse());

if (
$this->subject === $subject
&& $this->target === $target
&& $this->getNormalizedIf() === $if
&& $this->getNormalizedElse() === $else
) {
return $this;
}

Expand Down Expand Up @@ -193,4 +202,34 @@ public static function __set_state(array $properties): Type
);
}

private function getNormalizedIf(): Type
{
return $this->normalizedIf ??= TypeTraverser::map(
$this->if,
fn (Type $type, callable $traverse) => $type === $this->subject
? (!$this->negated ? $this->getSubjectWithTargetIntersectedType() : $this->getSubjectWithTargetRemovedType())
: $traverse($type),
);
}

private function getNormalizedElse(): Type
{
return $this->normalizedElse ??= TypeTraverser::map(
$this->else,
fn (Type $type, callable $traverse) => $type === $this->subject
? (!$this->negated ? $this->getSubjectWithTargetRemovedType() : $this->getSubjectWithTargetIntersectedType())
: $traverse($type),
);
}

private function getSubjectWithTargetIntersectedType(): Type
{
return $this->subjectWithTargetIntersectedType ??= TypeCombinator::intersect($this->subject, $this->target);
}

private function getSubjectWithTargetRemovedType(): Type
{
return $this->subjectWithTargetRemovedType ??= TypeCombinator::remove($this->subject, $this->target);
}

}
5 changes: 5 additions & 0 deletions tests/PHPStan/Rules/PhpDoc/IncompatiblePhpDocTypeRuleTest.php
Expand Up @@ -420,4 +420,9 @@ public function testGenericCallables(): void
]);
}

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

}
30 changes: 30 additions & 0 deletions tests/PHPStan/Rules/PhpDoc/data/bug-10622.php
@@ -0,0 +1,30 @@
<?php

namespace Bug10622;

class Model {}

/**
* @template TKey of array-key
* @template TModel
*
*/
class SupportCollection {}

/**
* @template TKey of array-key
* @template TModel of Model
*
*/
class Collection
{
/**
* Run a map over each of the items.
*
* @template TMapValue
*
* @param callable(TModel, TKey): TMapValue $callback
* @return (TMapValue is Model ? self<TKey, TMapValue> : SupportCollection<TKey, TMapValue>)
*/
public function map(callable $callback) {}
}

0 comments on commit 39dc97b

Please sign in to comment.