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