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