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 pickTypes(callable $getTypes): array
47: {
48: $types = [];
49: foreach ($this->getTypes() as $type) {
50: $innerTypes = $getTypes($type);
51: foreach ($innerTypes as $innerType) {
52: $types[] = $innerType;
53: }
54: }
55:
56: return $types;
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 TrinaryLogic::createNo()->lazyOr($this->getTypes(), static fn (Type $innerType) => $acceptingType->accepts($innerType, $strictTypes));
86: }
87:
88: public function inferTemplateTypes(Type $receivedType): TemplateTypeMap
89: {
90: $types = TemplateTypeMap::createEmpty();
91:
92: foreach ($this->getTypes() as $type) {
93: $types = $types->benevolentUnion($type->inferTemplateTypes($receivedType));
94: }
95:
96: return $types;
97: }
98:
99: public function inferTemplateTypesOn(Type $templateType): TemplateTypeMap
100: {
101: $types = TemplateTypeMap::createEmpty();
102:
103: foreach ($this->getTypes() as $type) {
104: $types = $types->benevolentUnion($templateType->inferTemplateTypes($type));
105: }
106:
107: return $types;
108: }
109:
110: public function traverse(callable $cb): Type
111: {
112: $types = [];
113: $changed = false;
114:
115: foreach ($this->getTypes() as $type) {
116: $newType = $cb($type);
117: if ($type !== $newType) {
118: $changed = true;
119: }
120: $types[] = $newType;
121: }
122:
123: if ($changed) {
124: return TypeUtils::toBenevolentUnion(TypeCombinator::union(...$types));
125: }
126:
127: return $this;
128: }
129:
130: /**
131: * @param mixed[] $properties
132: */
133: public static function __set_state(array $properties): Type
134: {
135: return new self($properties['types']);
136: }
137:
138: }
139: