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: /** @api */
90: public static function getRecommendedLevelByType(Type $acceptingType, ?Type $acceptedType = null): self
91: {
92: $moreVerboseCallback = static function (Type $type, callable $traverse) use (&$moreVerbose, &$veryVerbose): Type {
93: if ($type->isCallable()->yes()) {
94: $moreVerbose = true;
95:
96: // Keep checking if we need to be very verbose.
97: return $traverse($type);
98: }
99: if ($type->isConstantValue()->yes() && $type->isNull()->no()) {
100: $moreVerbose = true;
101:
102: // For ConstantArrayType we need to keep checking if we need to be very verbose.
103: if (!$type->isArray()->no()) {
104: return $traverse($type);
105: }
106:
107: return $type;
108: }
109: if (
110: // synced with IntersectionType::describe()
111: $type instanceof AccessoryNonEmptyStringType
112: || $type instanceof AccessoryNonFalsyStringType
113: || $type instanceof AccessoryLiteralStringType
114: || $type instanceof AccessoryNumericStringType
115: || $type instanceof NonEmptyArrayType
116: || $type instanceof AccessoryArrayListType
117: ) {
118: $moreVerbose = true;
119: return $type;
120: }
121: if (
122: $type instanceof AccessoryLowercaseStringType
123: || $type instanceof AccessoryUppercaseStringType
124: ) {
125: $moreVerbose = true;
126: $veryVerbose = true;
127: return $type;
128: }
129: if ($type instanceof IntegerRangeType) {
130: $moreVerbose = true;
131: return $type;
132: }
133: return $traverse($type);
134: };
135:
136: /** @var bool $moreVerbose */
137: $moreVerbose = false;
138: /** @var bool $veryVerbose */
139: $veryVerbose = false;
140: TypeTraverser::map($acceptingType, $moreVerboseCallback);
141:
142: if ($veryVerbose) {
143: return self::precise();
144: }
145:
146: if ($moreVerbose) {
147: $verbosity = self::value();
148: }
149:
150: if ($acceptedType === null) {
151: return $verbosity ?? self::typeOnly();
152: }
153:
154: $containsInvariantTemplateType = false;
155: TypeTraverser::map($acceptingType, static function (Type $type, callable $traverse) use (&$containsInvariantTemplateType): Type {
156: if ($type instanceof GenericObjectType || $type instanceof GenericStaticType) {
157: $reflection = $type->getClassReflection();
158: if ($reflection !== null) {
159: $templateTypeMap = $reflection->getTemplateTypeMap();
160: foreach ($templateTypeMap->getTypes() as $templateType) {
161: if (!$templateType instanceof TemplateType) {
162: continue;
163: }
164:
165: if (!$templateType->getVariance()->invariant()) {
166: continue;
167: }
168:
169: $containsInvariantTemplateType = true;
170: return $type;
171: }
172: }
173: }
174:
175: return $traverse($type);
176: });
177:
178: if (!$containsInvariantTemplateType) {
179: return $verbosity ?? self::typeOnly();
180: }
181:
182: /** @var bool $moreVerbose */
183: $moreVerbose = false;
184: /** @var bool $veryVerbose */
185: $veryVerbose = false;
186: TypeTraverser::map($acceptedType, $moreVerboseCallback);
187:
188: if ($veryVerbose) {
189: return self::precise();
190: }
191:
192: return $moreVerbose ? self::value() : $verbosity ?? self::typeOnly();
193: }
194:
195: /**
196: * @param callable(): string $typeOnlyCallback
197: * @param callable(): string $valueCallback
198: * @param callable(): string|null $preciseCallback
199: * @param callable(): string|null $cacheCallback
200: */
201: public function handle(
202: callable $typeOnlyCallback,
203: callable $valueCallback,
204: ?callable $preciseCallback = null,
205: ?callable $cacheCallback = null,
206: ): string
207: {
208: if ($this->value === self::TYPE_ONLY) {
209: return $typeOnlyCallback();
210: }
211:
212: if ($this->value === self::VALUE) {
213: return $valueCallback();
214: }
215:
216: if ($this->value === self::PRECISE) {
217: if ($preciseCallback !== null) {
218: return $preciseCallback();
219: }
220:
221: return $valueCallback();
222: }
223:
224: if ($cacheCallback !== null) {
225: return $cacheCallback();
226: }
227:
228: if ($preciseCallback !== null) {
229: return $preciseCallback();
230: }
231:
232: return $valueCallback();
233: }
234:
235: }
236: