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: /** @api */
82: public static function getRecommendedLevelByType(Type $acceptingType, ?Type $acceptedType = null): self
83: {
84: $moreVerboseCallback = static function (Type $type, callable $traverse) use (&$moreVerbose): Type {
85: if ($type->isCallable()->yes()) {
86: $moreVerbose = true;
87: return $type;
88: }
89: if ($type->isConstantValue()->yes() && $type->isNull()->no()) {
90: $moreVerbose = true;
91: return $type;
92: }
93: if (
94: // synced with IntersectionType::describe()
95: $type instanceof AccessoryNonEmptyStringType
96: || $type instanceof AccessoryNonFalsyStringType
97: || $type instanceof AccessoryLiteralStringType
98: || $type instanceof AccessoryNumericStringType
99: || $type instanceof NonEmptyArrayType
100: || $type instanceof AccessoryArrayListType
101: ) {
102: $moreVerbose = true;
103: return $type;
104: }
105: if ($type instanceof IntegerRangeType) {
106: $moreVerbose = true;
107: return $type;
108: }
109: return $traverse($type);
110: };
111:
112: /** @var bool $moreVerbose */
113: $moreVerbose = false;
114: TypeTraverser::map($acceptingType, $moreVerboseCallback);
115:
116: if ($moreVerbose) {
117: return self::value();
118: }
119:
120: if ($acceptedType === null) {
121: return self::typeOnly();
122: }
123:
124: $containsInvariantTemplateType = false;
125: TypeTraverser::map($acceptingType, static function (Type $type, callable $traverse) use (&$containsInvariantTemplateType): Type {
126: if ($type instanceof GenericObjectType) {
127: $reflection = $type->getClassReflection();
128: if ($reflection !== null) {
129: $templateTypeMap = $reflection->getTemplateTypeMap();
130: foreach ($templateTypeMap->getTypes() as $templateType) {
131: if (!$templateType instanceof TemplateType) {
132: continue;
133: }
134:
135: if (!$templateType->getVariance()->invariant()) {
136: continue;
137: }
138:
139: $containsInvariantTemplateType = true;
140: return $type;
141: }
142: }
143: }
144:
145: return $traverse($type);
146: });
147:
148: if (!$containsInvariantTemplateType) {
149: return self::typeOnly();
150: }
151:
152: /** @var bool $moreVerbose */
153: $moreVerbose = false;
154: TypeTraverser::map($acceptedType, $moreVerboseCallback);
155:
156: return $moreVerbose ? self::value() : self::typeOnly();
157: }
158:
159: /**
160: * @param callable(): string $typeOnlyCallback
161: * @param callable(): string $valueCallback
162: * @param callable(): string|null $preciseCallback
163: * @param callable(): string|null $cacheCallback
164: */
165: public function handle(
166: callable $typeOnlyCallback,
167: callable $valueCallback,
168: ?callable $preciseCallback = null,
169: ?callable $cacheCallback = null,
170: ): string
171: {
172: if ($this->value === self::TYPE_ONLY) {
173: return $typeOnlyCallback();
174: }
175:
176: if ($this->value === self::VALUE) {
177: return $valueCallback();
178: }
179:
180: if ($this->value === self::PRECISE) {
181: if ($preciseCallback !== null) {
182: return $preciseCallback();
183: }
184:
185: return $valueCallback();
186: }
187:
188: if ($cacheCallback !== null) {
189: return $cacheCallback();
190: }
191:
192: if ($preciseCallback !== null) {
193: return $preciseCallback();
194: }
195:
196: return $valueCallback();
197: }
198:
199: }
200: