1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Type\Generic;
4:
5: use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
6: use PHPStan\ShouldNotHappenException;
7: use PHPStan\TrinaryLogic;
8: use PHPStan\Type\AcceptsResult;
9: use PHPStan\Type\BenevolentUnionType;
10: use PHPStan\Type\MixedType;
11: use PHPStan\Type\NeverType;
12: use PHPStan\Type\Type;
13: use function sprintf;
14:
15: /** @api */
16: class TemplateTypeVariance
17: {
18:
19: private const INVARIANT = 1;
20: private const COVARIANT = 2;
21: private const CONTRAVARIANT = 3;
22: private const STATIC = 4;
23: private const BIVARIANT = 5;
24:
25: /** @var self[] */
26: private static array $registry;
27:
28: private static bool $invarianceCompositionEnabled = false;
29:
30: private function __construct(private int $value)
31: {
32: }
33:
34: private static function create(int $value): self
35: {
36: self::$registry[$value] ??= new self($value);
37: return self::$registry[$value];
38: }
39:
40: public static function createInvariant(): self
41: {
42: return self::create(self::INVARIANT);
43: }
44:
45: public static function createCovariant(): self
46: {
47: return self::create(self::COVARIANT);
48: }
49:
50: public static function createContravariant(): self
51: {
52: return self::create(self::CONTRAVARIANT);
53: }
54:
55: public static function createStatic(): self
56: {
57: return self::create(self::STATIC);
58: }
59:
60: public static function createBivariant(): self
61: {
62: return self::create(self::BIVARIANT);
63: }
64:
65: public function invariant(): bool
66: {
67: return $this->value === self::INVARIANT;
68: }
69:
70: public function covariant(): bool
71: {
72: return $this->value === self::COVARIANT;
73: }
74:
75: public function contravariant(): bool
76: {
77: return $this->value === self::CONTRAVARIANT;
78: }
79:
80: public function static(): bool
81: {
82: return $this->value === self::STATIC;
83: }
84:
85: public function bivariant(): bool
86: {
87: return $this->value === self::BIVARIANT;
88: }
89:
90: public function compose(self $other): self
91: {
92: if ($this->contravariant()) {
93: if ($other->contravariant()) {
94: return self::createCovariant();
95: }
96: if ($other->covariant()) {
97: return self::createContravariant();
98: }
99: if ($other->bivariant()) {
100: return self::createBivariant();
101: }
102: return self::createInvariant();
103: }
104:
105: if ($this->covariant()) {
106: if ($other->contravariant()) {
107: return self::createContravariant();
108: }
109: if ($other->covariant()) {
110: return self::createCovariant();
111: }
112: if ($other->bivariant()) {
113: return self::createBivariant();
114: }
115: return self::createInvariant();
116: }
117:
118: if (self::$invarianceCompositionEnabled && $this->invariant()) {
119: return self::createInvariant();
120: }
121:
122: if ($this->bivariant()) {
123: return self::createBivariant();
124: }
125:
126: return $other;
127: }
128:
129: public function isValidVariance(Type $a, Type $b): TrinaryLogic
130: {
131: return $this->isValidVarianceWithReason(null, $a, $b)->result;
132: }
133:
134: public function isValidVarianceWithReason(?TemplateType $templateType, Type $a, Type $b): AcceptsResult
135: {
136: if ($b instanceof NeverType) {
137: return AcceptsResult::createYes();
138: }
139:
140: if ($a instanceof MixedType && !$a instanceof TemplateType) {
141: return AcceptsResult::createYes();
142: }
143:
144: if ($a instanceof BenevolentUnionType) {
145: if (!$a->isSuperTypeOf($b)->no()) {
146: return AcceptsResult::createYes();
147: }
148: }
149:
150: if ($b instanceof BenevolentUnionType) {
151: if (!$b->isSuperTypeOf($a)->no()) {
152: return AcceptsResult::createYes();
153: }
154: }
155:
156: if ($b instanceof MixedType && !$b instanceof TemplateType) {
157: return AcceptsResult::createYes();
158: }
159:
160: if ($this->invariant()) {
161: $result = $a->equals($b);
162: $reasons = [];
163: if (!$result) {
164: if (
165: $templateType !== null
166: && $templateType->getScope()->getClassName() !== null
167: && $a->isSuperTypeOf($b)->yes()
168: ) {
169: $reasons[] = sprintf(
170: 'Template type %s on class %s is not covariant. Learn more: <fg=cyan>https://phpstan.org/blog/whats-up-with-template-covariant</>',
171: $templateType->getName(),
172: $templateType->getScope()->getClassName(),
173: );
174: }
175: }
176:
177: return new AcceptsResult(TrinaryLogic::createFromBoolean($result), $reasons);
178: }
179:
180: if ($this->covariant()) {
181: return new AcceptsResult($a->isSuperTypeOf($b), []);
182: }
183:
184: if ($this->contravariant()) {
185: return new AcceptsResult($b->isSuperTypeOf($a), []);
186: }
187:
188: if ($this->bivariant()) {
189: return AcceptsResult::createYes();
190: }
191:
192: throw new ShouldNotHappenException();
193: }
194:
195: public function equals(self $other): bool
196: {
197: return $other->value === $this->value;
198: }
199:
200: public function validPosition(self $other): bool
201: {
202: return $other->value === $this->value
203: || $other->invariant()
204: || $this->bivariant()
205: || $this->static();
206: }
207:
208: public function describe(): string
209: {
210: switch ($this->value) {
211: case self::INVARIANT:
212: return 'invariant';
213: case self::COVARIANT:
214: return 'covariant';
215: case self::CONTRAVARIANT:
216: return 'contravariant';
217: case self::STATIC:
218: return 'static';
219: case self::BIVARIANT:
220: return 'bivariant';
221: }
222:
223: throw new ShouldNotHappenException();
224: }
225:
226: /**
227: * @return GenericTypeNode::VARIANCE_*
228: */
229: public function toPhpDocNodeVariance(): string
230: {
231: switch ($this->value) {
232: case self::INVARIANT:
233: return GenericTypeNode::VARIANCE_INVARIANT;
234: case self::COVARIANT:
235: return GenericTypeNode::VARIANCE_COVARIANT;
236: case self::CONTRAVARIANT:
237: return GenericTypeNode::VARIANCE_CONTRAVARIANT;
238: case self::BIVARIANT:
239: return GenericTypeNode::VARIANCE_BIVARIANT;
240: }
241:
242: throw new ShouldNotHappenException();
243: }
244:
245: /**
246: * @param array{value: int} $properties
247: */
248: public static function __set_state(array $properties): self
249: {
250: return new self($properties['value']);
251: }
252:
253: public static function setInvarianceCompositionEnabled(bool $enabled): void
254: {
255: self::$invarianceCompositionEnabled = $enabled;
256: }
257:
258: }
259: