Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement conditional types slightly better
- Loading branch information
1 parent
9240f16
commit e0e7ba4
Showing
12 changed files
with
757 additions
and
46 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Type; | ||
|
||
use PHPStan\Type\Generic\TemplateType; | ||
use PHPStan\Type\Generic\TemplateTypeVariance; | ||
use PHPStan\Type\Traits\ConditionalTypeTrait; | ||
use PHPStan\Type\Traits\NonGeneralizableTypeTrait; | ||
use function array_merge; | ||
use function sprintf; | ||
|
||
/** @api */ | ||
final class ConditionalType implements CompoundType | ||
{ | ||
|
||
use ConditionalTypeTrait; | ||
use NonGeneralizableTypeTrait; | ||
|
||
private function __construct( | ||
private Type $subject, | ||
private Type $target, | ||
Type $if, | ||
Type $else, | ||
private bool $negated, | ||
) | ||
{ | ||
$this->if = $if; | ||
$this->else = $else; | ||
} | ||
|
||
public function getReferencedClasses(): array | ||
{ | ||
return array_merge( | ||
$this->subject->getReferencedClasses(), | ||
$this->target->getReferencedClasses(), | ||
$this->if->getReferencedClasses(), | ||
$this->else->getReferencedClasses(), | ||
); | ||
} | ||
|
||
public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array | ||
{ | ||
return array_merge( | ||
$this->subject->getReferencedTemplateTypes($positionVariance), | ||
$this->target->getReferencedTemplateTypes($positionVariance), | ||
$this->if->getReferencedTemplateTypes($positionVariance), | ||
$this->else->getReferencedTemplateTypes($positionVariance), | ||
); | ||
} | ||
|
||
public function equals(Type $type): bool | ||
{ | ||
return $type instanceof self | ||
&& $this->subject->equals($type->subject) | ||
&& $this->target->equals($type->target) | ||
&& $this->if->equals($type->if) | ||
&& $this->else->equals($type->else); | ||
} | ||
|
||
public function describe(VerbosityLevel $level): string | ||
{ | ||
return sprintf( | ||
'(%s %s %s ? %s : %s)', | ||
$this->subject->describe($level), | ||
$this->negated ? 'is not' : 'is', | ||
$this->target->describe($level), | ||
$this->if->describe($level), | ||
$this->else->describe($level), | ||
); | ||
} | ||
|
||
public static function create( | ||
Type $subject, | ||
Type $target, | ||
Type $if, | ||
Type $else, | ||
bool $negated, | ||
): Type | ||
{ | ||
return (new self($subject, $target, $if, $else, $negated))->resolve(); | ||
} | ||
|
||
private function resolve(): Type | ||
{ | ||
$isSuperType = $this->target->isSuperTypeOf($this->subject); | ||
|
||
if ($isSuperType->yes()) { | ||
return !$this->negated ? $this->if : $this->else; | ||
} elseif ($isSuperType->no()) { | ||
return !$this->negated ? $this->else : $this->if; | ||
} | ||
|
||
if ($this->isResolved()) { | ||
return TypeCombinator::union($this->if, $this->else); | ||
} | ||
|
||
return $this; | ||
} | ||
|
||
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) { | ||
return $this; | ||
} | ||
|
||
return self::create($subject, $target, $if, $else, $this->negated); | ||
} | ||
|
||
private function isResolved(): bool | ||
{ | ||
return !$this->containsTemplate($this->subject) && !$this->containsTemplate($this->target); | ||
} | ||
|
||
private function containsTemplate(Type $type): bool | ||
{ | ||
$containsTemplate = false; | ||
TypeTraverser::map($type, static function (Type $type, callable $traverse) use (&$containsTemplate): Type { | ||
if ($type instanceof TemplateType) { | ||
$containsTemplate = true; | ||
} | ||
|
||
return $containsTemplate ? $type : $traverse($type); | ||
}); | ||
|
||
return $containsTemplate; | ||
} | ||
|
||
/** | ||
* @param mixed[] $properties | ||
*/ | ||
public static function __set_state(array $properties): Type | ||
{ | ||
return new self( | ||
$properties['subject'], | ||
$properties['target'], | ||
$properties['if'], | ||
$properties['else'], | ||
$properties['negated'], | ||
); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Type; | ||
|
||
use PHPStan\Type\Generic\TemplateTypeVariance; | ||
use PHPStan\Type\Traits\ConditionalTypeTrait; | ||
use PHPStan\Type\Traits\NonGeneralizableTypeTrait; | ||
use function array_merge; | ||
use function sprintf; | ||
|
||
/** @api */ | ||
final class ConditionalTypeForParameter implements CompoundType | ||
{ | ||
|
||
use ConditionalTypeTrait; | ||
use NonGeneralizableTypeTrait; | ||
|
||
public function __construct( | ||
private string $parameterName, | ||
private Type $target, | ||
Type $if, | ||
Type $else, | ||
private bool $negated, | ||
) | ||
{ | ||
$this->if = $if; | ||
$this->else = $else; | ||
} | ||
|
||
public function getParameterName(): string | ||
{ | ||
return $this->parameterName; | ||
} | ||
|
||
public function toConditional(Type $subject): Type | ||
{ | ||
return ConditionalType::create( | ||
$subject, | ||
$this->target, | ||
$this->if, | ||
$this->else, | ||
$this->negated, | ||
); | ||
} | ||
|
||
public function getReferencedClasses(): array | ||
{ | ||
return array_merge( | ||
$this->target->getReferencedClasses(), | ||
$this->if->getReferencedClasses(), | ||
$this->else->getReferencedClasses(), | ||
); | ||
} | ||
|
||
public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array | ||
{ | ||
return array_merge( | ||
$this->target->getReferencedTemplateTypes($positionVariance), | ||
$this->if->getReferencedTemplateTypes($positionVariance), | ||
$this->else->getReferencedTemplateTypes($positionVariance), | ||
); | ||
} | ||
|
||
public function equals(Type $type): bool | ||
{ | ||
return $type instanceof self | ||
&& $this->parameterName === $type->parameterName | ||
&& $this->target->equals($type->target) | ||
&& $this->if->equals($type->if) | ||
&& $this->else->equals($type->else); | ||
} | ||
|
||
public function describe(VerbosityLevel $level): string | ||
{ | ||
return sprintf( | ||
'(%s %s %s ? %s : %s)', | ||
$this->parameterName, | ||
$this->negated ? 'is not' : 'is', | ||
$this->target->describe($level), | ||
$this->if->describe($level), | ||
$this->else->describe($level), | ||
); | ||
} | ||
|
||
/** | ||
* @param callable(Type): Type $cb | ||
*/ | ||
public function traverse(callable $cb): Type | ||
{ | ||
$target = $cb($this->target); | ||
$if = $cb($this->if); | ||
$else = $cb($this->else); | ||
|
||
if ($this->target === $target && $this->if === $if && $this->else === $else) { | ||
return $this; | ||
} | ||
|
||
return new ConditionalTypeForParameter($this->parameterName, $target, $if, $else, $this->negated); | ||
} | ||
|
||
/** | ||
* @param mixed[] $properties | ||
*/ | ||
public static function __set_state(array $properties): Type | ||
{ | ||
return new self( | ||
$properties['parameterName'], | ||
$properties['target'], | ||
$properties['if'], | ||
$properties['else'], | ||
$properties['negated'], | ||
); | ||
} | ||
|
||
} |
Oops, something went wrong.