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