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 toAbsoluteNumber(): Type
151: {
152: return new ErrorType();
153: }
154:
155: public function toInteger(): Type
156: {
157: return new IntegerType();
158: }
159:
160: public function toFloat(): Type
161: {
162: return new FloatType();
163: }
164:
165: public function toString(): Type
166: {
167: return $this;
168: }
169:
170: public function toArray(): Type
171: {
172: return new ConstantArrayType(
173: [new ConstantIntegerType(0)],
174: [$this],
175: [1],
176: isList: TrinaryLogic::createYes(),
177: );
178: }
179:
180: public function toArrayKey(): Type
181: {
182: $level = ReportUnsafeArrayStringKeyCastingToggle::getLevel();
183: if ($level !== ReportUnsafeArrayStringKeyCastingToggle::PREVENT) {
184: return $this;
185: }
186:
187: $isDecimalIntString = $this->isDecimalIntegerString();
188: if ($isDecimalIntString->no()) {
189: return $this;
190: } elseif ($isDecimalIntString->yes()) {
191: return new IntegerType();
192: }
193:
194: return new UnionType([
195: new IntegerType(),
196: TypeCombinator::intersect($this, new AccessoryDecimalIntegerStringType(inverse: true)),
197: ]);
198: }
199:
200: public function toCoercedArgumentType(bool $strictTypes): Type
201: {
202: if (!$strictTypes) {
203: if ($this->isNumericString()->no()) {
204: return TypeCombinator::union($this, $this->toBoolean());
205: }
206: return TypeCombinator::union($this->toInteger(), $this->toFloat(), $this, $this->toBoolean());
207: }
208:
209: return $this;
210: }
211:
212: public function isNull(): TrinaryLogic
213: {
214: return TrinaryLogic::createNo();
215: }
216:
217: public function isTrue(): TrinaryLogic
218: {
219: return TrinaryLogic::createNo();
220: }
221:
222: public function isFalse(): TrinaryLogic
223: {
224: return TrinaryLogic::createNo();
225: }
226:
227: public function isBoolean(): TrinaryLogic
228: {
229: return TrinaryLogic::createNo();
230: }
231:
232: public function isFloat(): TrinaryLogic
233: {
234: return TrinaryLogic::createNo();
235: }
236:
237: public function isInteger(): TrinaryLogic
238: {
239: return TrinaryLogic::createNo();
240: }
241:
242: public function isString(): TrinaryLogic
243: {
244: return TrinaryLogic::createYes();
245: }
246:
247: public function isNumericString(): TrinaryLogic
248: {
249: return TrinaryLogic::createMaybe();
250: }
251:
252: public function isDecimalIntegerString(): TrinaryLogic
253: {
254: return TrinaryLogic::createMaybe();
255: }
256:
257: public function isNonEmptyString(): TrinaryLogic
258: {
259: return TrinaryLogic::createMaybe();
260: }
261:
262: public function isNonFalsyString(): TrinaryLogic
263: {
264: return TrinaryLogic::createMaybe();
265: }
266:
267: public function isLiteralString(): TrinaryLogic
268: {
269: return TrinaryLogic::createMaybe();
270: }
271:
272: public function isLowercaseString(): TrinaryLogic
273: {
274: return TrinaryLogic::createMaybe();
275: }
276:
277: public function isUppercaseString(): TrinaryLogic
278: {
279: return TrinaryLogic::createMaybe();
280: }
281:
282: public function isClassString(): TrinaryLogic
283: {
284: return TrinaryLogic::createMaybe();
285: }
286:
287: public function getClassStringObjectType(): Type
288: {
289: return new ObjectWithoutClassType();
290: }
291:
292: public function getObjectTypeOrClassStringObjectType(): Type
293: {
294: return new ObjectWithoutClassType();
295: }
296:
297: public function isScalar(): TrinaryLogic
298: {
299: return TrinaryLogic::createYes();
300: }
301:
302: public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType
303: {
304: if ($type->isArray()->yes()) {
305: return new ConstantBooleanType(false);
306: }
307:
308: return new BooleanType();
309: }
310:
311: public function hasMethod(string $methodName): TrinaryLogic
312: {
313: if ($this->isClassString()->yes()) {
314: return TrinaryLogic::createMaybe();
315: }
316: return TrinaryLogic::createNo();
317: }
318:
319: public function tryRemove(Type $typeToRemove): ?Type
320: {
321: if ($typeToRemove instanceof ConstantStringType && $typeToRemove->getValue() === '') {
322: return TypeCombinator::intersect($this, new AccessoryNonEmptyStringType());
323: }
324:
325: if ($typeToRemove instanceof AccessoryNonEmptyStringType) {
326: return new ConstantStringType('');
327: }
328:
329: return null;
330: }
331:
332: public function getFiniteTypes(): array
333: {
334: return [];
335: }
336:
337: public function exponentiate(Type $exponent): Type
338: {
339: return ExponentiateHelper::exponentiate($this, $exponent);
340: }
341:
342: public function toPhpDocNode(): TypeNode
343: {
344: return new IdentifierTypeNode('string');
345: }
346:
347: public function hasTemplateOrLateResolvableType(): bool
348: {
349: return false;
350: }
351:
352: }
353: