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: $moreVerboseCallback = static function (Type $type, callable $traverse) use (&$moreVerbose, &$veryVerbose): Type {
98: if ($type->isCallable()->yes()) {
99: $moreVerbose = true;
100:
101: // Keep checking if we need to be very verbose.
102: return $traverse($type);
103: }
104: if ($type->isConstantValue()->yes() && $type->isNull()->no()) {
105: $moreVerbose = true;
106:
107: // For ConstantArrayType we need to keep checking if we need to be very verbose.
108: if (!$type->isArray()->no()) {
109: return $traverse($type);
110: }
111:
112: return $type;
113: }
114: if (
115: // synced with IntersectionType::describe()
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: /** @var bool $moreVerbose */
142: $moreVerbose = false;
143: /** @var bool $veryVerbose */
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: /** @var bool $moreVerbose */
188: $moreVerbose = false;
189: /** @var bool $veryVerbose */
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: * @param callable(): string $typeOnlyCallback
202: * @param callable(): string $valueCallback
203: * @param callable(): string|null $preciseCallback
204: * @param callable(): string|null $cacheCallback
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: