1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Type;
4:
5: use PHPStan\DependencyInjection\ReportUnsafeArrayStringKeyCastingToggle;
6: use PHPStan\Php\PhpVersion;
7: use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
8: use PHPStan\PhpDocParser\Ast\Type\TypeNode;
9: use PHPStan\Reflection\ReflectionProviderStaticAccessor;
10: use PHPStan\ShouldNotHappenException;
11: use PHPStan\TrinaryLogic;
12: use PHPStan\Turbo\ShadowedByTurboExtension;
13: use PHPStan\Type\Accessory\AccessoryDecimalIntegerStringType;
14: use PHPStan\Type\Accessory\AccessoryNonEmptyStringType;
15: use PHPStan\Type\Constant\ConstantArrayType;
16: use PHPStan\Type\Constant\ConstantBooleanType;
17: use PHPStan\Type\Constant\ConstantIntegerType;
18: use PHPStan\Type\Constant\ConstantStringType;
19: use PHPStan\Type\Traits\MaybeCallableTypeTrait;
20: use PHPStan\Type\Traits\NonArrayTypeTrait;
21: use PHPStan\Type\Traits\NonGeneralizableTypeTrait;
22: use PHPStan\Type\Traits\NonGenericTypeTrait;
23: use PHPStan\Type\Traits\NonIterableTypeTrait;
24: use PHPStan\Type\Traits\NonObjectTypeTrait;
25: use PHPStan\Type\Traits\UndecidedBooleanTypeTrait;
26: use PHPStan\Type\Traits\UndecidedComparisonTypeTrait;
27: use function count;
28:
29: /** @api */
30: #[InstanceofDeprecated(insteadUse: 'Type::isString()')]
31: #[ShadowedByTurboExtension(implementation: __DIR__ . '/../../turbo-ext/src/StringType.cpp')]
32: class StringType implements Type
33: {
34:
35: use JustNullableTypeTrait;
36: use MaybeCallableTypeTrait;
37: use NonArrayTypeTrait;
38: use NonIterableTypeTrait;
39: use NonObjectTypeTrait;
40: use UndecidedBooleanTypeTrait;
41: use UndecidedComparisonTypeTrait;
42: use NonGenericTypeTrait;
43: use NonGeneralizableTypeTrait;
44:
45: /** @api */
46: public function __construct()
47: {
48: }
49:
50: public function describe(VerbosityLevel $level): string
51: {
52: return 'string';
53: }
54:
55: public function getConstantStrings(): array
56: {
57: return [];
58: }
59:
60: public function isOffsetAccessible(): TrinaryLogic
61: {
62: return TrinaryLogic::createYes();
63: }
64:
65: public function isOffsetAccessLegal(): TrinaryLogic
66: {
67: return TrinaryLogic::createYes();
68: }
69:
70: public function hasOffsetValueType(Type $offsetType): TrinaryLogic
71: {
72: return $offsetType->isInteger()->and(TrinaryLogic::createMaybe());
73: }
74:
75: public function getOffsetValueType(Type $offsetType): Type
76: {
77: if ($this->hasOffsetValueType($offsetType)->no()) {
78: return new ErrorType();
79: }
80:
81: return new IntersectionType([
82: new StringType(),
83: new AccessoryNonEmptyStringType(),
84: ]);
85: }
86:
87: public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type
88: {
89: if ($offsetType === null) {
90: return new ErrorType();
91: }
92:
93: $valueStringType = $valueType->toString();
94: if ($valueStringType instanceof ErrorType) {
95: return new ErrorType();
96: }
97:
98: if ($offsetType->isInteger()->yes() || $offsetType instanceof MixedType) {
99: return new IntersectionType([
100: new StringType(),
101: new AccessoryNonEmptyStringType(),
102: ]);
103: }
104:
105: return new ErrorType();
106: }
107:
108: public function setExistingOffsetValueType(Type $offsetType, Type $valueType): Type
109: {
110: return $this;
111: }
112:
113: public function unsetOffset(Type $offsetType): Type
114: {
115: return new ErrorType();
116: }
117:
118: public function accepts(Type $type, bool $strictTypes): AcceptsResult
119: {
120: if ($type instanceof self) {
121: return AcceptsResult::createYes();
122: }
123:
124: if ($type instanceof CompoundType) {
125: return $type->isAcceptedBy($this, $strictTypes);
126: }
127:
128: $thatClassNames = $type->getObjectClassNames();
129: if (count($thatClassNames) > 1) {
130: throw new ShouldNotHappenException();
131: }
132:
133: if ($thatClassNames === [] || $strictTypes) {
134: return AcceptsResult::createNo();
135: }
136:
137: $reflectionProvider = ReflectionProviderStaticAccessor::getInstance();
138: if (!$reflectionProvider->hasClass($thatClassNames[0])) {
139: return AcceptsResult::createNo();
140: }
141:
142: $typeClass = $reflectionProvider->getClass($thatClassNames[0]);
143: return AcceptsResult::createFromBoolean(
144: $typeClass->hasNativeMethod('__toString'),
145: );
146: }
147:
148: public function toNumber(): Type
149: {
150: return new ErrorType();
151: }
152:
153: public function toBitwiseNotType(): Type
154: {
155: return new StringType();
156: }
157:
158: public function toAbsoluteNumber(): Type
159: {
160: return new ErrorType();
161: }
162:
163: public function toInteger(): Type
164: {
165: return new IntegerType();
166: }
167:
168: public function toFloat(): Type
169: {
170: return new FloatType();
171: }
172:
173: public function toString(): Type
174: {
175: return $this;
176: }
177:
178: public function toArray(): Type
179: {
180: return new ConstantArrayType(
181: [new ConstantIntegerType(0)],
182: [$this],
183: [1],
184: isList: TrinaryLogic::createYes(),
185: );
186: }
187:
188: public function toArrayKey(): Type
189: {
190: $level = ReportUnsafeArrayStringKeyCastingToggle::getLevel();
191: if ($level !== ReportUnsafeArrayStringKeyCastingToggle::PREVENT) {
192: return $this;
193: }
194:
195: $isDecimalIntString = $this->isDecimalIntegerString();
196: if ($isDecimalIntString->no()) {
197: return $this;
198: } elseif ($isDecimalIntString->yes()) {
199: return new IntegerType();
200: }
201:
202: return new UnionType([
203: new IntegerType(),
204: TypeCombinator::intersect($this, new AccessoryDecimalIntegerStringType(inverse: true)),
205: ]);
206: }
207:
208: public function toCoercedArgumentType(bool $strictTypes): Type
209: {
210: if (!$strictTypes) {
211: if ($this->isNumericString()->no()) {
212: return TypeCombinator::union($this, $this->toBoolean());
213: }
214: return TypeCombinator::union($this->toInteger(), $this->toFloat(), $this, $this->toBoolean());
215: }
216:
217: return $this;
218: }
219:
220: public function isNull(): TrinaryLogic
221: {
222: return TrinaryLogic::createNo();
223: }
224:
225: public function isTrue(): TrinaryLogic
226: {
227: return TrinaryLogic::createNo();
228: }
229:
230: public function isFalse(): TrinaryLogic
231: {
232: return TrinaryLogic::createNo();
233: }
234:
235: public function isBoolean(): TrinaryLogic
236: {
237: return TrinaryLogic::createNo();
238: }
239:
240: public function isFloat(): TrinaryLogic
241: {
242: return TrinaryLogic::createNo();
243: }
244:
245: public function isInteger(): TrinaryLogic
246: {
247: return TrinaryLogic::createNo();
248: }
249:
250: public function isString(): TrinaryLogic
251: {
252: return TrinaryLogic::createYes();
253: }
254:
255: public function isNumericString(): TrinaryLogic
256: {
257: return TrinaryLogic::createMaybe();
258: }
259:
260: public function isDecimalIntegerString(): TrinaryLogic
261: {
262: return TrinaryLogic::createMaybe();
263: }
264:
265: public function isNonEmptyString(): TrinaryLogic
266: {
267: return TrinaryLogic::createMaybe();
268: }
269:
270: public function isNonFalsyString(): TrinaryLogic
271: {
272: return TrinaryLogic::createMaybe();
273: }
274:
275: public function isLiteralString(): TrinaryLogic
276: {
277: return TrinaryLogic::createMaybe();
278: }
279:
280: public function isLowercaseString(): TrinaryLogic
281: {
282: return TrinaryLogic::createMaybe();
283: }
284:
285: public function isUppercaseString(): TrinaryLogic
286: {
287: return TrinaryLogic::createMaybe();
288: }
289:
290: public function isClassString(): TrinaryLogic
291: {
292: return TrinaryLogic::createMaybe();
293: }
294:
295: public function getClassStringObjectType(): Type
296: {
297: return new ObjectWithoutClassType();
298: }
299:
300: public function getObjectTypeOrClassStringObjectType(): Type
301: {
302: return new ObjectWithoutClassType();
303: }
304:
305: public function isScalar(): TrinaryLogic
306: {
307: return TrinaryLogic::createYes();
308: }
309:
310: public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType
311: {
312: if ($type->isArray()->yes()) {
313: return new ConstantBooleanType(false);
314: }
315:
316: return new BooleanType();
317: }
318:
319: public function hasMethod(string $methodName): TrinaryLogic
320: {
321: if ($this->isClassString()->yes()) {
322: return TrinaryLogic::createMaybe();
323: }
324: return TrinaryLogic::createNo();
325: }
326:
327: public function tryRemove(Type $typeToRemove): ?Type
328: {
329: if ($typeToRemove instanceof ConstantStringType && $typeToRemove->getValue() === '') {
330: return TypeCombinator::intersect($this, new AccessoryNonEmptyStringType());
331: }
332:
333: if ($typeToRemove instanceof AccessoryNonEmptyStringType) {
334: return new ConstantStringType('');
335: }
336:
337: return null;
338: }
339:
340: public function getFiniteTypes(): array
341: {
342: return [];
343: }
344:
345: public function exponentiate(Type $exponent): Type
346: {
347: return ExponentiateHelper::exponentiate($this, $exponent);
348: }
349:
350: public function toPhpDocNode(): TypeNode
351: {
352: return new IdentifierTypeNode('string');
353: }
354:
355: public function hasTemplateOrLateResolvableType(): bool
356: {
357: return false;
358: }
359:
360: }
361: