1: | <?php declare(strict_types = 1); |
2: | |
3: | namespace PHPStan\Type; |
4: | |
5: | use PHPStan\Type\Accessory\AccessoryArrayListType; |
6: | use PHPStan\Type\Accessory\AccessoryLiteralStringType; |
7: | use PHPStan\Type\Accessory\AccessoryLowercaseStringType; |
8: | use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; |
9: | use PHPStan\Type\Accessory\AccessoryNonFalsyStringType; |
10: | use PHPStan\Type\Accessory\AccessoryNumericStringType; |
11: | use PHPStan\Type\Accessory\AccessoryUppercaseStringType; |
12: | use PHPStan\Type\Accessory\NonEmptyArrayType; |
13: | use PHPStan\Type\Generic\GenericObjectType; |
14: | use PHPStan\Type\Generic\GenericStaticType; |
15: | use PHPStan\Type\Generic\TemplateType; |
16: | |
17: | final class VerbosityLevel |
18: | { |
19: | |
20: | private const TYPE_ONLY = 1; |
21: | private const VALUE = 2; |
22: | private const PRECISE = 3; |
23: | private const CACHE = 4; |
24: | |
25: | |
26: | private static array $registry; |
27: | |
28: | |
29: | |
30: | |
31: | private function __construct(private int $value) |
32: | { |
33: | } |
34: | |
35: | |
36: | |
37: | |
38: | private static function create(int $value): self |
39: | { |
40: | self::$registry[$value] ??= new self($value); |
41: | return self::$registry[$value]; |
42: | } |
43: | |
44: | |
45: | public function getLevelValue(): int |
46: | { |
47: | return $this->value; |
48: | } |
49: | |
50: | |
51: | public static function typeOnly(): self |
52: | { |
53: | return self::create(self::TYPE_ONLY); |
54: | } |
55: | |
56: | |
57: | public static function value(): self |
58: | { |
59: | return self::create(self::VALUE); |
60: | } |
61: | |
62: | |
63: | public static function precise(): self |
64: | { |
65: | return self::create(self::PRECISE); |
66: | } |
67: | |
68: | |
69: | public static function cache(): self |
70: | { |
71: | return self::create(self::CACHE); |
72: | } |
73: | |
74: | public function isTypeOnly(): bool |
75: | { |
76: | return $this->value === self::TYPE_ONLY; |
77: | } |
78: | |
79: | public function isValue(): bool |
80: | { |
81: | return $this->value === self::VALUE; |
82: | } |
83: | |
84: | public function isPrecise(): bool |
85: | { |
86: | return $this->value === self::PRECISE; |
87: | } |
88: | |
89: | public function isCache(): bool |
90: | { |
91: | return $this->value === self::CACHE; |
92: | } |
93: | |
94: | |
95: | public static function getRecommendedLevelByType(Type $acceptingType, ?Type $acceptedType = null): self |
96: | { |
97: | $moreVerboseCallback = static function (Type $type, callable $traverse) use (&$moreVerbose, &$veryVerbose): Type { |
98: | if ($type->isCallable()->yes()) { |
99: | $moreVerbose = true; |
100: | |
101: | |
102: | return $traverse($type); |
103: | } |
104: | if ($type->isConstantValue()->yes() && $type->isNull()->no()) { |
105: | $moreVerbose = true; |
106: | |
107: | |
108: | if (!$type->isArray()->no()) { |
109: | return $traverse($type); |
110: | } |
111: | |
112: | return $type; |
113: | } |
114: | if ( |
115: | |
116: | $type instanceof AccessoryNonEmptyStringType |
117: | || $type instanceof AccessoryNonFalsyStringType |
118: | || $type instanceof AccessoryLiteralStringType |
119: | || $type instanceof AccessoryNumericStringType |
120: | || $type instanceof NonEmptyArrayType |
121: | || $type instanceof AccessoryArrayListType |
122: | ) { |
123: | $moreVerbose = true; |
124: | return $type; |
125: | } |
126: | if ( |
127: | $type instanceof AccessoryLowercaseStringType |
128: | || $type instanceof AccessoryUppercaseStringType |
129: | ) { |
130: | $moreVerbose = true; |
131: | $veryVerbose = true; |
132: | return $type; |
133: | } |
134: | if ($type instanceof IntegerRangeType) { |
135: | $moreVerbose = true; |
136: | return $type; |
137: | } |
138: | return $traverse($type); |
139: | }; |
140: | |
141: | |
142: | $moreVerbose = false; |
143: | |
144: | $veryVerbose = false; |
145: | TypeTraverser::map($acceptingType, $moreVerboseCallback); |
146: | |
147: | if ($veryVerbose) { |
148: | return self::precise(); |
149: | } |
150: | |
151: | if ($moreVerbose) { |
152: | $verbosity = self::value(); |
153: | } |
154: | |
155: | if ($acceptedType === null) { |
156: | return $verbosity ?? self::typeOnly(); |
157: | } |
158: | |
159: | $containsInvariantTemplateType = false; |
160: | TypeTraverser::map($acceptingType, static function (Type $type, callable $traverse) use (&$containsInvariantTemplateType): Type { |
161: | if ($type instanceof GenericObjectType || $type instanceof GenericStaticType) { |
162: | $reflection = $type->getClassReflection(); |
163: | if ($reflection !== null) { |
164: | $templateTypeMap = $reflection->getTemplateTypeMap(); |
165: | foreach ($templateTypeMap->getTypes() as $templateType) { |
166: | if (!$templateType instanceof TemplateType) { |
167: | continue; |
168: | } |
169: | |
170: | if (!$templateType->getVariance()->invariant()) { |
171: | continue; |
172: | } |
173: | |
174: | $containsInvariantTemplateType = true; |
175: | return $type; |
176: | } |
177: | } |
178: | } |
179: | |
180: | return $traverse($type); |
181: | }); |
182: | |
183: | if (!$containsInvariantTemplateType) { |
184: | return $verbosity ?? self::typeOnly(); |
185: | } |
186: | |
187: | |
188: | $moreVerbose = false; |
189: | |
190: | $veryVerbose = false; |
191: | TypeTraverser::map($acceptedType, $moreVerboseCallback); |
192: | |
193: | if ($veryVerbose) { |
194: | return self::precise(); |
195: | } |
196: | |
197: | return $moreVerbose ? self::value() : $verbosity ?? self::typeOnly(); |
198: | } |
199: | |
200: | |
201: | |
202: | |
203: | |
204: | |
205: | |
206: | public function handle( |
207: | callable $typeOnlyCallback, |
208: | callable $valueCallback, |
209: | ?callable $preciseCallback = null, |
210: | ?callable $cacheCallback = null, |
211: | ): string |
212: | { |
213: | if ($this->value === self::TYPE_ONLY) { |
214: | return $typeOnlyCallback(); |
215: | } |
216: | |
217: | if ($this->value === self::VALUE) { |
218: | return $valueCallback(); |
219: | } |
220: | |
221: | if ($this->value === self::PRECISE) { |
222: | if ($preciseCallback !== null) { |
223: | return $preciseCallback(); |
224: | } |
225: | |
226: | return $valueCallback(); |
227: | } |
228: | |
229: | if ($cacheCallback !== null) { |
230: | return $cacheCallback(); |
231: | } |
232: | |
233: | if ($preciseCallback !== null) { |
234: | return $preciseCallback(); |
235: | } |
236: | |
237: | return $valueCallback(); |
238: | } |
239: | |
240: | } |
241: | |