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