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