1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Type;
4:
5: use PHPStan\ShouldNotHappenException;
6: use PHPStan\TrinaryLogic;
7: use function array_merge;
8: use function array_unique;
9: use function array_values;
10:
11: /**
12: * Result of a Type::isSuperTypeOf() check — whether one type is a supertype of another.
13: *
14: * Wraps a TrinaryLogic result together with human-readable reasons explaining the
15: * relationship. This is the primary mechanism for comparing types in PHPStan's type system.
16: *
17: * `isSuperTypeOf()` answers: "Can all values of type B also be values of type A?"
18: * For example:
19: * - `(new StringType())->isSuperTypeOf(new ConstantStringType('hello'))` → Yes
20: * - `(new IntegerType())->isSuperTypeOf(new StringType())` → No
21: * - `(new StringType())->isSuperTypeOf(new MixedType())` → Maybe
22: *
23: * This is distinct from `accepts()` which also considers rule levels and PHPDoc context.
24: * Use `isSuperTypeOf()` for type-theoretic comparisons and `accepts()` for assignability checks.
25: *
26: * Can be converted to AcceptsResult via toAcceptsResult().
27: *
28: * @api
29: */
30: final class IsSuperTypeOfResult
31: {
32:
33: private static self $YES;
34:
35: private static self $MAYBE;
36:
37: private static self $NO;
38:
39: /**
40: * @api
41: * @param list<string> $reasons Human-readable explanations of the type relationship
42: */
43: public function __construct(
44: public readonly TrinaryLogic $result,
45: public readonly array $reasons,
46: )
47: {
48: }
49:
50: /**
51: * @phpstan-assert-if-true =false $this->no()
52: * @phpstan-assert-if-true =false $this->maybe()
53: */
54: public function yes(): bool
55: {
56: return $this->result->yes();
57: }
58:
59: /**
60: * @phpstan-assert-if-true =false $this->no()
61: * @phpstan-assert-if-true =false $this->yes()
62: */
63: public function maybe(): bool
64: {
65: return $this->result->maybe();
66: }
67:
68: /**
69: * @phpstan-assert-if-true =false $this->maybe()
70: * @phpstan-assert-if-true =false $this->yes()
71: */
72: public function no(): bool
73: {
74: return $this->result->no();
75: }
76:
77: public static function createYes(): self
78: {
79: return self::$YES ??= new self(TrinaryLogic::createYes(), []);
80: }
81:
82: /** @param list<string> $reasons */
83: public static function createNo(array $reasons = []): self
84: {
85: if ($reasons === []) {
86: return self::$NO ??= new self(TrinaryLogic::createNo(), $reasons);
87: }
88: return new self(TrinaryLogic::createNo(), $reasons);
89: }
90:
91: public static function createMaybe(): self
92: {
93: return self::$MAYBE ??= new self(TrinaryLogic::createMaybe(), []);
94: }
95:
96: public static function createFromBoolean(bool $value): self
97: {
98: if ($value === true) {
99: return self::createYes();
100: }
101: return self::createNo();
102: }
103:
104: public function toAcceptsResult(): AcceptsResult
105: {
106: return new AcceptsResult($this->result, $this->reasons);
107: }
108:
109: public function and(self ...$others): self
110: {
111: $results = [];
112: $reasons = [];
113: foreach ($others as $other) {
114: $results[] = $other->result;
115: $reasons[] = $other->reasons;
116: }
117:
118: return new self(
119: $this->result->and(...$results),
120: array_values(array_unique(array_merge($this->reasons, ...$reasons))),
121: );
122: }
123:
124: public function or(self ...$others): self
125: {
126: $results = [];
127: $reasons = [];
128: foreach ($others as $other) {
129: $results[] = $other->result;
130: $reasons[] = $other->reasons;
131: }
132:
133: return new self(
134: $this->result->or(...$results),
135: array_values(array_unique(array_merge($this->reasons, ...$reasons))),
136: );
137: }
138:
139: /** @param callable(string): string $cb */
140: public function decorateReasons(callable $cb): self
141: {
142: $reasons = [];
143: foreach ($this->reasons as $reason) {
144: $reasons[] = $cb($reason);
145: }
146:
147: return new self($this->result, $reasons);
148: }
149:
150: /** @see TrinaryLogic::extremeIdentity() */
151: public static function extremeIdentity(self ...$operands): self
152: {
153: if ($operands === []) {
154: throw new ShouldNotHappenException();
155: }
156:
157: $results = [];
158: $reasons = [];
159: foreach ($operands as $operand) {
160: $results[] = $operand->result;
161: foreach ($operand->reasons as $reason) {
162: $reasons[] = $reason;
163: }
164: }
165:
166: return new self(TrinaryLogic::extremeIdentity(...$results), array_values(array_unique($reasons)));
167: }
168:
169: /** @see TrinaryLogic::maxMin() */
170: public static function maxMin(self ...$operands): self
171: {
172: if ($operands === []) {
173: throw new ShouldNotHappenException();
174: }
175:
176: $results = [];
177: $reasons = [];
178: foreach ($operands as $operand) {
179: $results[] = $operand->result;
180: foreach ($operand->reasons as $reason) {
181: $reasons[] = $reason;
182: }
183: }
184:
185: return new self(TrinaryLogic::maxMin(...$results), array_values(array_unique($reasons)));
186: }
187:
188: /**
189: * @template T
190: * @param T[] $objects
191: * @param callable(T): self $callback
192: */
193: public static function lazyMaxMin(
194: array $objects,
195: callable $callback,
196: ): self
197: {
198: $reasons = [];
199: $hasNo = false;
200: foreach ($objects as $object) {
201: $isSuperTypeOf = $callback($object);
202: if ($isSuperTypeOf->result->yes()) {
203: return $isSuperTypeOf;
204: } elseif ($isSuperTypeOf->result->no()) {
205: $hasNo = true;
206: }
207:
208: foreach ($isSuperTypeOf->reasons as $reason) {
209: $reasons[] = $reason;
210: }
211: }
212:
213: return new self(
214: $hasNo ? TrinaryLogic::createNo() : TrinaryLogic::createMaybe(),
215: array_values(array_unique($reasons)),
216: );
217: }
218:
219: public function negate(): self
220: {
221: return new self($this->result->negate(), $this->reasons);
222: }
223:
224: public function describe(): string
225: {
226: return $this->result->describe();
227: }
228:
229: }
230: