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 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): AcceptsResult
91: {
92: $result = AcceptsResult::createNo();
93: foreach ($this->getTypes() as $innerType) {
94: $result = $result->or($acceptingType->accepts($innerType, $strictTypes));
95: }
96:
97: return $result;
98: }
99:
100: public function inferTemplateTypes(Type $receivedType): TemplateTypeMap
101: {
102: $types = TemplateTypeMap::createEmpty();
103:
104: foreach ($this->getTypes() as $type) {
105: $types = $types->benevolentUnion($type->inferTemplateTypes($receivedType));
106: }
107:
108: return $types;
109: }
110:
111: public function inferTemplateTypesOn(Type $templateType): TemplateTypeMap
112: {
113: $types = TemplateTypeMap::createEmpty();
114:
115: foreach ($this->getTypes() as $type) {
116: $types = $types->benevolentUnion($templateType->inferTemplateTypes($type));
117: }
118:
119: return $types;
120: }
121:
122: public function traverse(callable $cb): Type
123: {
124: $types = [];
125: $changed = false;
126:
127: foreach ($this->getTypes() as $type) {
128: $newType = $cb($type);
129: if ($type !== $newType) {
130: $changed = true;
131: }
132: $types[] = $newType;
133: }
134:
135: if ($changed) {
136: return TypeUtils::toBenevolentUnion(TypeCombinator::union(...$types));
137: }
138:
139: return $this;
140: }
141:
142: public function traverseSimultaneously(Type $right, callable $cb): Type
143: {
144: $types = [];
145: $changed = false;
146:
147: if (!$right instanceof UnionType) {
148: return $this;
149: }
150:
151: if (count($this->getTypes()) !== count($right->getTypes())) {
152: return $this;
153: }
154:
155: foreach ($this->getSortedTypes() as $i => $leftType) {
156: $rightType = $right->getSortedTypes()[$i];
157: $newType = $cb($leftType, $rightType);
158: if ($leftType !== $newType) {
159: $changed = true;
160: }
161: $types[] = $newType;
162: }
163:
164: if ($changed) {
165: return TypeUtils::toBenevolentUnion(TypeCombinator::union(...$types));
166: }
167:
168: return $this;
169: }
170:
171: }
172: