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