1: | <?php declare(strict_types = 1); |
2: | |
3: | namespace PHPStan\Type; |
4: | |
5: | use PHPStan\TrinaryLogic; |
6: | use PHPStan\Type\Generic\TemplateTypeMap; |
7: | use function count; |
8: | |
9: | |
10: | class BenevolentUnionType extends UnionType |
11: | { |
12: | |
13: | |
14: | |
15: | |
16: | |
17: | public function __construct(array $types) |
18: | { |
19: | parent::__construct($types); |
20: | } |
21: | |
22: | public function describe(VerbosityLevel $level): string |
23: | { |
24: | return '(' . parent::describe($level) . ')'; |
25: | } |
26: | |
27: | protected function unionTypes(callable $getType): Type |
28: | { |
29: | $resultTypes = []; |
30: | foreach ($this->getTypes() as $type) { |
31: | $result = $getType($type); |
32: | if ($result instanceof ErrorType) { |
33: | continue; |
34: | } |
35: | |
36: | $resultTypes[] = $result; |
37: | } |
38: | |
39: | if (count($resultTypes) === 0) { |
40: | return new ErrorType(); |
41: | } |
42: | |
43: | return TypeUtils::toBenevolentUnion(TypeCombinator::union(...$resultTypes)); |
44: | } |
45: | |
46: | protected function pickFromTypes( |
47: | callable $getValues, |
48: | callable $criteria, |
49: | ): array |
50: | { |
51: | $values = []; |
52: | foreach ($this->getTypes() as $type) { |
53: | $innerValues = $getValues($type); |
54: | if ($innerValues === [] && $criteria($type)) { |
55: | return []; |
56: | } |
57: | |
58: | foreach ($innerValues as $innerType) { |
59: | $values[] = $innerType; |
60: | } |
61: | } |
62: | |
63: | return $values; |
64: | } |
65: | |
66: | public function getOffsetValueType(Type $offsetType): Type |
67: | { |
68: | $types = []; |
69: | foreach ($this->getTypes() as $innerType) { |
70: | $valueType = $innerType->getOffsetValueType($offsetType); |
71: | if ($valueType instanceof ErrorType) { |
72: | continue; |
73: | } |
74: | |
75: | $types[] = $valueType; |
76: | } |
77: | |
78: | if (count($types) === 0) { |
79: | return new ErrorType(); |
80: | } |
81: | |
82: | return TypeUtils::toBenevolentUnion(TypeCombinator::union(...$types)); |
83: | } |
84: | |
85: | protected function unionResults(callable $getResult): TrinaryLogic |
86: | { |
87: | return TrinaryLogic::createNo()->lazyOr($this->getTypes(), $getResult); |
88: | } |
89: | |
90: | public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic |
91: | { |
92: | return $this->isAcceptedWithReasonBy($acceptingType, $strictTypes)->result; |
93: | } |
94: | |
95: | public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult |
96: | { |
97: | $result = AcceptsResult::createNo(); |
98: | foreach ($this->getTypes() as $innerType) { |
99: | $result = $result->or($acceptingType->acceptsWithReason($innerType, $strictTypes)); |
100: | } |
101: | |
102: | return $result; |
103: | } |
104: | |
105: | public function inferTemplateTypes(Type $receivedType): TemplateTypeMap |
106: | { |
107: | $types = TemplateTypeMap::createEmpty(); |
108: | |
109: | foreach ($this->getTypes() as $type) { |
110: | $types = $types->benevolentUnion($type->inferTemplateTypes($receivedType)); |
111: | } |
112: | |
113: | return $types; |
114: | } |
115: | |
116: | public function inferTemplateTypesOn(Type $templateType): TemplateTypeMap |
117: | { |
118: | $types = TemplateTypeMap::createEmpty(); |
119: | |
120: | foreach ($this->getTypes() as $type) { |
121: | $types = $types->benevolentUnion($templateType->inferTemplateTypes($type)); |
122: | } |
123: | |
124: | return $types; |
125: | } |
126: | |
127: | public function traverse(callable $cb): Type |
128: | { |
129: | $types = []; |
130: | $changed = false; |
131: | |
132: | foreach ($this->getTypes() as $type) { |
133: | $newType = $cb($type); |
134: | if ($type !== $newType) { |
135: | $changed = true; |
136: | } |
137: | $types[] = $newType; |
138: | } |
139: | |
140: | if ($changed) { |
141: | return TypeUtils::toBenevolentUnion(TypeCombinator::union(...$types)); |
142: | } |
143: | |
144: | return $this; |
145: | } |
146: | |
147: | public function traverseSimultaneously(Type $right, callable $cb): Type |
148: | { |
149: | $types = []; |
150: | $changed = false; |
151: | |
152: | if (!$right instanceof UnionType) { |
153: | return $this; |
154: | } |
155: | |
156: | if (count($this->getTypes()) !== count($right->getTypes())) { |
157: | return $this; |
158: | } |
159: | |
160: | foreach ($this->getSortedTypes() as $i => $leftType) { |
161: | $rightType = $right->getSortedTypes()[$i]; |
162: | $newType = $cb($leftType, $rightType); |
163: | if ($leftType !== $newType) { |
164: | $changed = true; |
165: | } |
166: | $types[] = $newType; |
167: | } |
168: | |
169: | if ($changed) { |
170: | return TypeUtils::toBenevolentUnion(TypeCombinator::union(...$types)); |
171: | } |
172: | |
173: | return $this; |
174: | } |
175: | |
176: | |
177: | |
178: | |
179: | public static function __set_state(array $properties): Type |
180: | { |
181: | return new self($properties['types']); |
182: | } |
183: | |
184: | } |
185: | |