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::accepts() check — whether one type accepts another.
14: *
15: * Wraps a TrinaryLogic result together with human-readable reasons explaining
16: * why the acceptance failed. These reasons are surfaced in PHPStan error messages
17: * to help developers understand type mismatches.
18: *
19: * For example, when checking if `int` accepts `string`, the result would be No
20: * with a reason like "string is not a subtype of int".
21: *
22: * The `accepts()` method is used to check assignability — whether a value of one
23: * type can be assigned to a variable/parameter of another type. This is stricter
24: * than `isSuperTypeOf()` because it accounts for PHPStan's rule level and
25: * generics variance.
26: *
27: * @api
28: */
29: final class AcceptsResult
30: {
31:
32: /**
33: * @api
34: * @param list<string> $reasons Human-readable explanations of why acceptance failed
35: */
36: public function __construct(
37: public readonly TrinaryLogic $result,
38: public readonly array $reasons,
39: )
40: {
41: }
42:
43: /**
44: * @phpstan-assert-if-true =false $this->no()
45: * @phpstan-assert-if-true =false $this->maybe()
46: */
47: public function yes(): bool
48: {
49: return $this->result->yes();
50: }
51:
52: /**
53: * @phpstan-assert-if-true =false $this->no()
54: * @phpstan-assert-if-true =false $this->yes()
55: */
56: public function maybe(): bool
57: {
58: return $this->result->maybe();
59: }
60:
61: /**
62: * @phpstan-assert-if-true =false $this->maybe()
63: * @phpstan-assert-if-true =false $this->yes()
64: */
65: public function no(): bool
66: {
67: return $this->result->no();
68: }
69:
70: public static function createYes(): self
71: {
72: return new self(TrinaryLogic::createYes(), []);
73: }
74:
75: /** @param list<string> $reasons */
76: public static function createNo(array $reasons = []): self
77: {
78: return new self(TrinaryLogic::createNo(), $reasons);
79: }
80:
81: public static function createMaybe(): self
82: {
83: return new self(TrinaryLogic::createMaybe(), []);
84: }
85:
86: public static function createFromBoolean(bool $value): self
87: {
88: return new self(TrinaryLogic::createFromBoolean($value), []);
89: }
90:
91: public function and(self $other): self
92: {
93: return new self(
94: $this->result->and($other->result),
95: array_values(array_unique(array_merge($this->reasons, $other->reasons))),
96: );
97: }
98:
99: public function or(self $other): self
100: {
101: return new self(
102: $this->result->or($other->result),
103: array_values(array_unique(array_merge($this->reasons, $other->reasons))),
104: );
105: }
106:
107: /** @param callable(string): string $cb */
108: public function decorateReasons(callable $cb): self
109: {
110: $reasons = [];
111: foreach ($this->reasons as $reason) {
112: $reasons[] = $cb($reason);
113: }
114:
115: return new self($this->result, $reasons);
116: }
117:
118: /** @see TrinaryLogic::extremeIdentity() */
119: public static function extremeIdentity(self ...$operands): self
120: {
121: if ($operands === []) {
122: throw new ShouldNotHappenException();
123: }
124:
125: $result = TrinaryLogic::extremeIdentity(...array_map(static fn (self $result) => $result->result, $operands));
126: $reasons = [];
127: foreach ($operands as $operand) {
128: foreach ($operand->reasons as $reason) {
129: $reasons[] = $reason;
130: }
131: }
132:
133: return new self($result, array_values(array_unique($reasons)));
134: }
135:
136: /** @see TrinaryLogic::maxMin() */
137: public static function maxMin(self ...$operands): self
138: {
139: if ($operands === []) {
140: throw new ShouldNotHappenException();
141: }
142:
143: $result = TrinaryLogic::maxMin(...array_map(static fn (self $result) => $result->result, $operands));
144: $reasons = [];
145: foreach ($operands as $operand) {
146: foreach ($operand->reasons as $reason) {
147: $reasons[] = $reason;
148: }
149: }
150:
151: return new self($result, array_values(array_unique($reasons)));
152: }
153:
154: }
155: