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)
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: * @param mixed[] $properties
178: */
179: public static function __set_state(array $properties): Type
180: {
181: return new self($properties['types']);
182: }
183:
184: }
185: