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