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