1: | <?php declare(strict_types = 1); |
2: | |
3: | namespace PHPStan; |
4: | |
5: | use PHPStan\Type\BooleanType; |
6: | use PHPStan\Type\Constant\ConstantBooleanType; |
7: | use function array_column; |
8: | use function max; |
9: | use function min; |
10: | |
11: | |
12: | |
13: | |
14: | |
15: | final class TrinaryLogic |
16: | { |
17: | |
18: | private const YES = 1; |
19: | private const MAYBE = 0; |
20: | private const NO = -1; |
21: | |
22: | |
23: | private static array $registry = []; |
24: | |
25: | private function __construct(private int $value) |
26: | { |
27: | } |
28: | |
29: | public static function createYes(): self |
30: | { |
31: | return self::$registry[self::YES] ??= new self(self::YES); |
32: | } |
33: | |
34: | public static function createNo(): self |
35: | { |
36: | return self::$registry[self::NO] ??= new self(self::NO); |
37: | } |
38: | |
39: | public static function createMaybe(): self |
40: | { |
41: | return self::$registry[self::MAYBE] ??= new self(self::MAYBE); |
42: | } |
43: | |
44: | public static function createFromBoolean(bool $value): self |
45: | { |
46: | $yesNo = $value ? self::YES : self::NO; |
47: | return self::$registry[$yesNo] ??= new self($yesNo); |
48: | } |
49: | |
50: | private static function create(int $value): self |
51: | { |
52: | self::$registry[$value] ??= new self($value); |
53: | return self::$registry[$value]; |
54: | } |
55: | |
56: | public function yes(): bool |
57: | { |
58: | return $this->value === self::YES; |
59: | } |
60: | |
61: | public function maybe(): bool |
62: | { |
63: | return $this->value === self::MAYBE; |
64: | } |
65: | |
66: | public function no(): bool |
67: | { |
68: | return $this->value === self::NO; |
69: | } |
70: | |
71: | public function toBooleanType(): BooleanType |
72: | { |
73: | if ($this->value === self::MAYBE) { |
74: | return new BooleanType(); |
75: | } |
76: | |
77: | return new ConstantBooleanType($this->value === self::YES); |
78: | } |
79: | |
80: | public function and(self ...$operands): self |
81: | { |
82: | $operandValues = array_column($operands, 'value'); |
83: | $operandValues[] = $this->value; |
84: | return self::create(min($operandValues)); |
85: | } |
86: | |
87: | |
88: | |
89: | |
90: | |
91: | |
92: | public function lazyAnd( |
93: | array $objects, |
94: | callable $callback, |
95: | ): self |
96: | { |
97: | if ($this->no()) { |
98: | return $this; |
99: | } |
100: | |
101: | $results = []; |
102: | foreach ($objects as $object) { |
103: | $result = $callback($object); |
104: | if ($result->no()) { |
105: | return $result; |
106: | } |
107: | |
108: | $results[] = $result; |
109: | } |
110: | |
111: | return $this->and(...$results); |
112: | } |
113: | |
114: | public function or(self ...$operands): self |
115: | { |
116: | $operandValues = array_column($operands, 'value'); |
117: | $operandValues[] = $this->value; |
118: | return self::create(max($operandValues)); |
119: | } |
120: | |
121: | |
122: | |
123: | |
124: | |
125: | |
126: | public function lazyOr( |
127: | array $objects, |
128: | callable $callback, |
129: | ): self |
130: | { |
131: | if ($this->yes()) { |
132: | return $this; |
133: | } |
134: | |
135: | $results = []; |
136: | foreach ($objects as $object) { |
137: | $result = $callback($object); |
138: | if ($result->yes()) { |
139: | return $result; |
140: | } |
141: | |
142: | $results[] = $result; |
143: | } |
144: | |
145: | return $this->or(...$results); |
146: | } |
147: | |
148: | public static function extremeIdentity(self ...$operands): self |
149: | { |
150: | if ($operands === []) { |
151: | throw new ShouldNotHappenException(); |
152: | } |
153: | $operandValues = array_column($operands, 'value'); |
154: | $min = min($operandValues); |
155: | $max = max($operandValues); |
156: | return self::create($min === $max ? $min : self::MAYBE); |
157: | } |
158: | |
159: | |
160: | |
161: | |
162: | |
163: | |
164: | public static function lazyExtremeIdentity( |
165: | array $objects, |
166: | callable $callback, |
167: | ): self |
168: | { |
169: | if ($objects === []) { |
170: | throw new ShouldNotHappenException(); |
171: | } |
172: | |
173: | $lastResult = null; |
174: | foreach ($objects as $object) { |
175: | $result = $callback($object); |
176: | if ($lastResult === null) { |
177: | $lastResult = $result; |
178: | continue; |
179: | } |
180: | if ($lastResult->equals($result)) { |
181: | continue; |
182: | } |
183: | |
184: | return self::createMaybe(); |
185: | } |
186: | |
187: | return $lastResult; |
188: | } |
189: | |
190: | public static function maxMin(self ...$operands): self |
191: | { |
192: | if ($operands === []) { |
193: | throw new ShouldNotHappenException(); |
194: | } |
195: | $operandValues = array_column($operands, 'value'); |
196: | return self::create(max($operandValues) > 0 ? 1 : min($operandValues)); |
197: | } |
198: | |
199: | |
200: | |
201: | |
202: | |
203: | |
204: | public static function lazyMaxMin( |
205: | array $objects, |
206: | callable $callback, |
207: | ): self |
208: | { |
209: | $results = []; |
210: | foreach ($objects as $object) { |
211: | $result = $callback($object); |
212: | if ($result->yes()) { |
213: | return $result; |
214: | } |
215: | |
216: | $results[] = $result; |
217: | } |
218: | |
219: | return self::maxMin(...$results); |
220: | } |
221: | |
222: | public function negate(): self |
223: | { |
224: | return self::create(-$this->value); |
225: | } |
226: | |
227: | public function equals(self $other): bool |
228: | { |
229: | return $this === $other; |
230: | } |
231: | |
232: | public function compareTo(self $other): ?self |
233: | { |
234: | if ($this->value > $other->value) { |
235: | return $this; |
236: | } elseif ($other->value > $this->value) { |
237: | return $other; |
238: | } |
239: | |
240: | return null; |
241: | } |
242: | |
243: | public function describe(): string |
244: | { |
245: | static $labels = [ |
246: | self::NO => 'No', |
247: | self::MAYBE => 'Maybe', |
248: | self::YES => 'Yes', |
249: | ]; |
250: | |
251: | return $labels[$this->value]; |
252: | } |
253: | |
254: | } |
255: | |