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