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: /** @var self[] */
26: private static array $registry;
27:
28: /**
29: * @param self::* $value
30: */
31: private function __construct(private int $value)
32: {
33: }
34:
35: /**
36: * @param self::* $value
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: /** @return self::* */
45: public function getLevelValue(): int
46: {
47: return $this->value;
48: }
49:
50: /** @api */
51: public static function typeOnly(): self
52: {
53: return self::create(self::TYPE_ONLY);
54: }
55:
56: /** @api */
57: public static function value(): self
58: {
59: return self::create(self::VALUE);
60: }
61:
62: /** @api */
63: public static function precise(): self
64: {
65: return self::create(self::PRECISE);
66: }
67:
68: /** @api */
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: /** @api */
95: public static function getRecommendedLevelByType(Type $acceptingType, ?Type $acceptedType = null): self
96: {
97: $moreVerbose = false;
98: $veryVerbose = false;
99: $moreVerboseCallback = static function (Type $type, callable $traverse) use (&$moreVerbose, &$veryVerbose): Type {
100: // stop deep traversal to not waste resources.
101: if ($veryVerbose) {
102: return $type;
103: }
104:
105: if ($type->isCallable()->yes()) {
106: $moreVerbose = true;
107:
108: // Keep checking if we need to be very verbose.
109: return $traverse($type);
110: }
111: if ($type->isConstantValue()->yes() && $type->isNull()->no()) {
112: $moreVerbose = true;
113:
114: // For ConstantArrayType we need to keep checking if we need to be very verbose.
115: if (!$type->isArray()->no()) {
116: return $traverse($type);
117: }
118:
119: return $type;
120: }
121: if (
122: // synced with IntersectionType::describe()
123: $type instanceof AccessoryNonEmptyStringType
124: || $type instanceof AccessoryNonFalsyStringType
125: || $type instanceof AccessoryLiteralStringType
126: || $type instanceof AccessoryNumericStringType
127: || $type instanceof NonEmptyArrayType
128: || $type instanceof AccessoryArrayListType
129: ) {
130: $moreVerbose = true;
131: return $type;
132: }
133: if (
134: $type instanceof AccessoryLowercaseStringType
135: || $type instanceof AccessoryUppercaseStringType
136: ) {
137: $moreVerbose = true;
138: $veryVerbose = true;
139: return $type;
140: }
141: if ($type instanceof IntegerRangeType) {
142: $moreVerbose = true;
143: return $type;
144: }
145: return $traverse($type);
146: };
147:
148: TypeTraverser::map($acceptingType, $moreVerboseCallback);
149:
150: if ($veryVerbose) {
151: return self::precise();
152: }
153:
154: if ($moreVerbose) {
155: $verbosity = self::value();
156: }
157:
158: if ($acceptedType === null) {
159: return $verbosity ?? self::typeOnly();
160: }
161:
162: $containsInvariantTemplateType = false;
163: TypeTraverser::map($acceptingType, static function (Type $type, callable $traverse) use (&$containsInvariantTemplateType): Type {
164: // stop deep traversal to not waste resources.
165: if ($containsInvariantTemplateType) {
166: return $type;
167: }
168:
169: if ($type instanceof GenericObjectType || $type instanceof GenericStaticType) {
170: $reflection = $type->getClassReflection();
171: if ($reflection !== null) {
172: $templateTypeMap = $reflection->getTemplateTypeMap();
173: foreach ($templateTypeMap->getTypes() as $templateType) {
174: if (!$templateType instanceof TemplateType) {
175: continue;
176: }
177:
178: if (!$templateType->getVariance()->invariant()) {
179: continue;
180: }
181:
182: $containsInvariantTemplateType = true;
183: return $type;
184: }
185: }
186: }
187:
188: return $traverse($type);
189: });
190:
191: if (!$containsInvariantTemplateType) {
192: return $verbosity ?? self::typeOnly();
193: }
194:
195: /** @var bool $moreVerbose */
196: $moreVerbose = false;
197: /** @var bool $veryVerbose */
198: $veryVerbose = false;
199: TypeTraverser::map($acceptedType, $moreVerboseCallback);
200:
201: if ($veryVerbose) {
202: return self::precise();
203: }
204:
205: return $moreVerbose ? self::value() : $verbosity ?? self::typeOnly();
206: }
207:
208: /**
209: * @param callable(): string $typeOnlyCallback
210: * @param callable(): string $valueCallback
211: * @param callable(): string|null $preciseCallback
212: * @param callable(): string|null $cacheCallback
213: */
214: public function handle(
215: callable $typeOnlyCallback,
216: callable $valueCallback,
217: ?callable $preciseCallback = null,
218: ?callable $cacheCallback = null,
219: ): string
220: {
221: if ($this->value === self::TYPE_ONLY) {
222: return $typeOnlyCallback();
223: }
224:
225: if ($this->value === self::VALUE) {
226: return $valueCallback();
227: }
228:
229: if ($this->value === self::PRECISE) {
230: if ($preciseCallback !== null) {
231: return $preciseCallback();
232: }
233:
234: return $valueCallback();
235: }
236:
237: if ($cacheCallback !== null) {
238: return $cacheCallback();
239: }
240:
241: if ($preciseCallback !== null) {
242: return $preciseCallback();
243: }
244:
245: return $valueCallback();
246: }
247:
248: }
249: