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