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