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(callable $getValues): array
47: {
48: $values = [];
49: foreach ($this->getTypes() as $type) {
50: $innerValues = $getValues($type);
51: foreach ($innerValues as $innerType) {
52: $values[] = $innerType;
53: }
54: }
55:
56: return $values;
57: }
58:
59: public function getOffsetValueType(Type $offsetType): Type
60: {
61: $types = [];
62: foreach ($this->getTypes() as $innerType) {
63: $valueType = $innerType->getOffsetValueType($offsetType);
64: if ($valueType instanceof ErrorType) {
65: continue;
66: }
67:
68: $types[] = $valueType;
69: }
70:
71: if (count($types) === 0) {
72: return new ErrorType();
73: }
74:
75: return TypeUtils::toBenevolentUnion(TypeCombinator::union(...$types));
76: }
77:
78: protected function unionResults(callable $getResult): TrinaryLogic
79: {
80: return TrinaryLogic::createNo()->lazyOr($this->getTypes(), $getResult);
81: }
82:
83: public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic
84: {
85: return $this->isAcceptedWithReasonBy($acceptingType, $strictTypes)->result;
86: }
87:
88: public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult
89: {
90: $result = AcceptsResult::createNo();
91: foreach ($this->getTypes() as $innerType) {
92: $result = $result->or($acceptingType->acceptsWithReason($innerType, $strictTypes));
93: }
94:
95: return $result;
96: }
97:
98: public function inferTemplateTypes(Type $receivedType): TemplateTypeMap
99: {
100: $types = TemplateTypeMap::createEmpty();
101:
102: foreach ($this->getTypes() as $type) {
103: $types = $types->benevolentUnion($type->inferTemplateTypes($receivedType));
104: }
105:
106: return $types;
107: }
108:
109: public function inferTemplateTypesOn(Type $templateType): TemplateTypeMap
110: {
111: $types = TemplateTypeMap::createEmpty();
112:
113: foreach ($this->getTypes() as $type) {
114: $types = $types->benevolentUnion($templateType->inferTemplateTypes($type));
115: }
116:
117: return $types;
118: }
119:
120: public function traverse(callable $cb): Type
121: {
122: $types = [];
123: $changed = false;
124:
125: foreach ($this->getTypes() as $type) {
126: $newType = $cb($type);
127: if ($type !== $newType) {
128: $changed = true;
129: }
130: $types[] = $newType;
131: }
132:
133: if ($changed) {
134: return TypeUtils::toBenevolentUnion(TypeCombinator::union(...$types));
135: }
136:
137: return $this;
138: }
139:
140: /**
141: * @param mixed[] $properties
142: */
143: public static function __set_state(array $properties): Type
144: {
145: return new self($properties['types']);
146: }
147:
148: }
149: