1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Type\Generic;
4:
5: use PHPStan\ShouldNotHappenException;
6: use PHPStan\TrinaryLogic;
7: use PHPStan\Type\BenevolentUnionType;
8: use PHPStan\Type\MixedType;
9: use PHPStan\Type\NeverType;
10: use PHPStan\Type\Type;
11:
12: /** @api */
13: class TemplateTypeVariance
14: {
15:
16: private const INVARIANT = 1;
17: private const COVARIANT = 2;
18: private const CONTRAVARIANT = 3;
19: private const STATIC = 4;
20:
21: /** @var self[] */
22: private static array $registry;
23:
24: private static bool $invarianceCompositionEnabled = false;
25:
26: private function __construct(private int $value)
27: {
28: }
29:
30: private static function create(int $value): self
31: {
32: self::$registry[$value] ??= new self($value);
33: return self::$registry[$value];
34: }
35:
36: public static function createInvariant(): self
37: {
38: return self::create(self::INVARIANT);
39: }
40:
41: public static function createCovariant(): self
42: {
43: return self::create(self::COVARIANT);
44: }
45:
46: public static function createContravariant(): self
47: {
48: return self::create(self::CONTRAVARIANT);
49: }
50:
51: public static function createStatic(): self
52: {
53: return self::create(self::STATIC);
54: }
55:
56: public function invariant(): bool
57: {
58: return $this->value === self::INVARIANT;
59: }
60:
61: public function covariant(): bool
62: {
63: return $this->value === self::COVARIANT;
64: }
65:
66: public function contravariant(): bool
67: {
68: return $this->value === self::CONTRAVARIANT;
69: }
70:
71: public function static(): bool
72: {
73: return $this->value === self::STATIC;
74: }
75:
76: public function compose(self $other): self
77: {
78: if ($this->contravariant()) {
79: if ($other->contravariant()) {
80: return self::createCovariant();
81: }
82: if ($other->covariant()) {
83: return self::createContravariant();
84: }
85: return self::createInvariant();
86: }
87:
88: if ($this->covariant()) {
89: if ($other->contravariant()) {
90: return self::createContravariant();
91: }
92: if ($other->covariant()) {
93: return self::createCovariant();
94: }
95: return self::createInvariant();
96: }
97:
98: if (self::$invarianceCompositionEnabled && $this->invariant()) {
99: return self::createInvariant();
100: }
101:
102: return $other;
103: }
104:
105: public function isValidVariance(Type $a, Type $b): TrinaryLogic
106: {
107: if ($b instanceof NeverType) {
108: return TrinaryLogic::createYes();
109: }
110:
111: if ($a instanceof MixedType && !$a instanceof TemplateType) {
112: return TrinaryLogic::createYes();
113: }
114:
115: if ($a instanceof BenevolentUnionType) {
116: if (!$a->isSuperTypeOf($b)->no()) {
117: return TrinaryLogic::createYes();
118: }
119: }
120:
121: if ($b instanceof BenevolentUnionType) {
122: if (!$b->isSuperTypeOf($a)->no()) {
123: return TrinaryLogic::createYes();
124: }
125: }
126:
127: if ($b instanceof MixedType && !$b instanceof TemplateType) {
128: return TrinaryLogic::createYes();
129: }
130:
131: if ($this->invariant()) {
132: return TrinaryLogic::createFromBoolean($a->equals($b));
133: }
134:
135: if ($this->covariant()) {
136: return $a->isSuperTypeOf($b);
137: }
138:
139: if ($this->contravariant()) {
140: return $b->isSuperTypeOf($a);
141: }
142:
143: throw new ShouldNotHappenException();
144: }
145:
146: public function equals(self $other): bool
147: {
148: return $other->value === $this->value;
149: }
150:
151: public function validPosition(self $other): bool
152: {
153: return $other->value === $this->value
154: || $other->invariant()
155: || $this->static();
156: }
157:
158: public function describe(): string
159: {
160: switch ($this->value) {
161: case self::INVARIANT:
162: return 'invariant';
163: case self::COVARIANT:
164: return 'covariant';
165: case self::CONTRAVARIANT:
166: return 'contravariant';
167: case self::STATIC:
168: return 'static';
169: }
170:
171: throw new ShouldNotHappenException();
172: }
173:
174: /**
175: * @param array{value: int} $properties
176: */
177: public static function __set_state(array $properties): self
178: {
179: return new self($properties['value']);
180: }
181:
182: public static function setInvarianceCompositionEnabled(bool $enabled): void
183: {
184: self::$invarianceCompositionEnabled = $enabled;
185: }
186:
187: }
188: