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\BenevolentUnionType;
9: use PHPStan\Type\IsSuperTypeOfResult;
10: use PHPStan\Type\MixedType;
11: use PHPStan\Type\NeverType;
12: use PHPStan\Type\Type;
13: use function sprintf;
14:
15: /**
16: * @api
17: */
18: final class TemplateTypeVariance
19: {
20:
21: private const INVARIANT = 1;
22: private const COVARIANT = 2;
23: private const CONTRAVARIANT = 3;
24: private const STATIC = 4;
25: private const BIVARIANT = 5;
26:
27: /** @var self[] */
28: private static array $registry;
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 ($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(TemplateType $templateType, Type $a, Type $b): IsSuperTypeOfResult
130: {
131: if ($b instanceof NeverType) {
132: return IsSuperTypeOfResult::createYes();
133: }
134:
135: if ($a instanceof MixedType && !$a instanceof TemplateType) {
136: return IsSuperTypeOfResult::createYes();
137: }
138:
139: if ($a instanceof BenevolentUnionType) {
140: if (!$a->isSuperTypeOf($b)->no()) {
141: return IsSuperTypeOfResult::createYes();
142: }
143: }
144:
145: if ($b instanceof BenevolentUnionType) {
146: if (!$b->isSuperTypeOf($a)->no()) {
147: return IsSuperTypeOfResult::createYes();
148: }
149: }
150:
151: if ($b instanceof MixedType && !$b instanceof TemplateType) {
152: return IsSuperTypeOfResult::createYes();
153: }
154:
155: if ($this->invariant()) {
156: $result = $a->equals($b);
157: $reasons = [];
158: if (!$result) {
159: if (
160: $templateType->getScope()->getClassName() !== null
161: && $a->isSuperTypeOf($b)->yes()
162: ) {
163: $reasons[] = sprintf(
164: 'Template type %s on class %s is not covariant. Learn more: <fg=cyan>https://phpstan.org/blog/whats-up-with-template-covariant</>',
165: $templateType->getName(),
166: $templateType->getScope()->getClassName(),
167: );
168: }
169: }
170:
171: return new IsSuperTypeOfResult(TrinaryLogic::createFromBoolean($result), $reasons);
172: }
173:
174: if ($this->covariant()) {
175: return $a->isSuperTypeOf($b);
176: }
177:
178: if ($this->contravariant()) {
179: return $b->isSuperTypeOf($a);
180: }
181:
182: if ($this->bivariant()) {
183: return IsSuperTypeOfResult::createYes();
184: }
185:
186: throw new ShouldNotHappenException();
187: }
188:
189: public function equals(self $other): bool
190: {
191: return $other->value === $this->value;
192: }
193:
194: public function validPosition(self $other): bool
195: {
196: return $other->value === $this->value
197: || $other->invariant()
198: || $this->bivariant()
199: || $this->static();
200: }
201:
202: public function describe(): string
203: {
204: switch ($this->value) {
205: case self::INVARIANT:
206: return 'invariant';
207: case self::COVARIANT:
208: return 'covariant';
209: case self::CONTRAVARIANT:
210: return 'contravariant';
211: case self::STATIC:
212: return 'static';
213: case self::BIVARIANT:
214: return 'bivariant';
215: }
216:
217: throw new ShouldNotHappenException();
218: }
219:
220: /**
221: * @return GenericTypeNode::VARIANCE_*
222: */
223: public function toPhpDocNodeVariance(): string
224: {
225: switch ($this->value) {
226: case self::INVARIANT:
227: return GenericTypeNode::VARIANCE_INVARIANT;
228: case self::COVARIANT:
229: return GenericTypeNode::VARIANCE_COVARIANT;
230: case self::CONTRAVARIANT:
231: return GenericTypeNode::VARIANCE_CONTRAVARIANT;
232: case self::BIVARIANT:
233: return GenericTypeNode::VARIANCE_BIVARIANT;
234: }
235:
236: throw new ShouldNotHappenException();
237: }
238:
239: }
240: