1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Type;
4:
5: use PHPStan\TrinaryLogic;
6: use PHPStan\Type\Generic\TemplateTypeVariance;
7: use PHPStan\Type\Traits\LateResolvableTypeTrait;
8: use PHPStan\Type\Traits\NonGeneralizableTypeTrait;
9: use function array_merge;
10: use function sprintf;
11:
12: /** @api */
13: final class ConditionalType implements CompoundType, LateResolvableType
14: {
15:
16: use LateResolvableTypeTrait;
17: use NonGeneralizableTypeTrait;
18:
19: public function __construct(
20: private Type $subject,
21: private Type $target,
22: private Type $if,
23: private Type $else,
24: private bool $negated,
25: )
26: {
27: }
28:
29: public function getSubject(): Type
30: {
31: return $this->subject;
32: }
33:
34: public function getTarget(): Type
35: {
36: return $this->target;
37: }
38:
39: public function getIf(): Type
40: {
41: return $this->if;
42: }
43:
44: public function getElse(): Type
45: {
46: return $this->else;
47: }
48:
49: public function isNegated(): bool
50: {
51: return $this->negated;
52: }
53:
54: public function isSuperTypeOf(Type $type): TrinaryLogic
55: {
56: if ($type instanceof self) {
57: return $this->if->isSuperTypeOf($type->if)
58: ->and($this->else->isSuperTypeOf($type->else));
59: }
60:
61: return $this->isSuperTypeOfDefault($type);
62: }
63:
64: public function getReferencedClasses(): array
65: {
66: return array_merge(
67: $this->subject->getReferencedClasses(),
68: $this->target->getReferencedClasses(),
69: $this->if->getReferencedClasses(),
70: $this->else->getReferencedClasses(),
71: );
72: }
73:
74: public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array
75: {
76: return array_merge(
77: $this->subject->getReferencedTemplateTypes($positionVariance),
78: $this->target->getReferencedTemplateTypes($positionVariance),
79: $this->if->getReferencedTemplateTypes($positionVariance),
80: $this->else->getReferencedTemplateTypes($positionVariance),
81: );
82: }
83:
84: public function equals(Type $type): bool
85: {
86: return $type instanceof self
87: && $this->subject->equals($type->subject)
88: && $this->target->equals($type->target)
89: && $this->if->equals($type->if)
90: && $this->else->equals($type->else);
91: }
92:
93: public function describe(VerbosityLevel $level): string
94: {
95: return sprintf(
96: '(%s %s %s ? %s : %s)',
97: $this->subject->describe($level),
98: $this->negated ? 'is not' : 'is',
99: $this->target->describe($level),
100: $this->if->describe($level),
101: $this->else->describe($level),
102: );
103: }
104:
105: public function isResolvable(): bool
106: {
107: return !TypeUtils::containsTemplateType($this->subject) && !TypeUtils::containsTemplateType($this->target);
108: }
109:
110: protected function getResult(): Type
111: {
112: $isSuperType = $this->target->isSuperTypeOf($this->subject);
113:
114: if ($isSuperType->yes()) {
115: return !$this->negated ? $this->if : $this->else;
116: }
117:
118: if ($isSuperType->no()) {
119: return !$this->negated ? $this->else : $this->if;
120: }
121:
122: return TypeCombinator::union($this->if, $this->else);
123: }
124:
125: public function traverse(callable $cb): Type
126: {
127: $subject = $cb($this->subject);
128: $target = $cb($this->target);
129: $if = $cb($this->if);
130: $else = $cb($this->else);
131:
132: if ($this->subject === $subject && $this->target === $target && $this->if === $if && $this->else === $else) {
133: return $this;
134: }
135:
136: return new self($subject, $target, $if, $else, $this->negated);
137: }
138:
139: /**
140: * @param mixed[] $properties
141: */
142: public static function __set_state(array $properties): Type
143: {
144: return new self(
145: $properties['subject'],
146: $properties['target'],
147: $properties['if'],
148: $properties['else'],
149: $properties['negated'],
150: );
151: }
152:
153: }
154: