1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Type;
4:
5: use PHPStan\Php\PhpVersion;
6: use PHPStan\PhpDocParser\Ast\Type\TypeNode;
7: use PHPStan\Reflection\Callables\CallableParametersAcceptor;
8: use PHPStan\Reflection\ClassConstantReflection;
9: use PHPStan\Reflection\ClassMemberAccessAnswerer;
10: use PHPStan\Reflection\ClassReflection;
11: use PHPStan\Reflection\ExtendedMethodReflection;
12: use PHPStan\Reflection\ExtendedPropertyReflection;
13: use PHPStan\Reflection\ReflectionProvider;
14: use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection;
15: use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection;
16: use PHPStan\TrinaryLogic;
17: use PHPStan\Type\Constant\ConstantArrayType;
18: use PHPStan\Type\Constant\ConstantStringType;
19: use PHPStan\Type\Enum\EnumCaseObjectType;
20: use PHPStan\Type\Generic\TemplateTypeMap;
21: use PHPStan\Type\Generic\TemplateTypeReference;
22: use PHPStan\Type\Generic\TemplateTypeVariance;
23:
24: /**
25: * Represents a PHPStan type in the type system.
26: *
27: * This is the central interface of PHPStan's type system. Every type that PHPStan
28: * can reason about implements this interface — from simple scalars like StringType
29: * to complex generics like GenericObjectType.
30: *
31: * Each Type knows what it accepts, what is a supertype of it, what properties/methods/constants
32: * it has, what operations it supports, and how to describe itself for error messages.
33: *
34: * Important: Never use `instanceof` to check types. For example, `$type instanceof StringType`
35: * will miss union types, intersection types with accessory types, and other composite forms.
36: * Always use the `is*()` methods or `isSuperTypeOf()` instead:
37: *
38: * // Wrong:
39: * if ($type instanceof StringType) { ... }
40: *
41: * // Correct:
42: * if ($type->isString()->yes()) { ... }
43: *
44: * @api
45: * @api-do-not-implement
46: * @see https://phpstan.org/developing-extensions/type-system
47: */
48: interface Type
49: {
50:
51: /**
52: * Returns all class names referenced anywhere in this type, recursively
53: * (including generic arguments, callable signatures, etc.).
54: *
55: * @see Type::getObjectClassNames() for only direct object type class names
56: *
57: * @return list<non-empty-string>
58: */
59: public function getReferencedClasses(): array;
60:
61: /**
62: * Returns class names of the object types this type directly represents.
63: * Unlike getReferencedClasses(), excludes classes in generic arguments, etc.
64: *
65: * @return list<non-empty-string>
66: */
67: public function getObjectClassNames(): array;
68:
69: /** @return list<ClassReflection> */
70: public function getObjectClassReflections(): array;
71:
72: /**
73: * Return class-string<Foo> for object type Foo.
74: */
75: public function getClassStringType(): Type;
76:
77: /**
78: * Returns the object type for a class-string or literal class name string.
79: * For non-class-string types, returns ErrorType.
80: */
81: public function getClassStringObjectType(): Type;
82:
83: /**
84: * Like getClassStringObjectType(), but also returns object types as-is.
85: * Used for `$classOrObject::method()` where the left side can be either.
86: */
87: public function getObjectTypeOrClassStringObjectType(): Type;
88:
89: public function isObject(): TrinaryLogic;
90:
91: public function isEnum(): TrinaryLogic;
92:
93: /** @return list<ArrayType|ConstantArrayType> */
94: public function getArrays(): array;
95:
96: /**
97: * Only ConstantArrayType instances (array shapes with known keys).
98: *
99: * @return list<ConstantArrayType>
100: */
101: public function getConstantArrays(): array;
102:
103: /** @return list<ConstantStringType> */
104: public function getConstantStrings(): array;
105:
106: /**
107: * Unlike isSuperTypeOf(), accepts() takes into account PHP's implicit type coercion.
108: * With $strictTypes = false, int is accepted by float, and Stringable objects are
109: * accepted by string.
110: */
111: public function accepts(Type $type, bool $strictTypes): AcceptsResult;
112:
113: /**
114: * "Does every value of $type belong to $this type?"
115: *
116: * Preferable to instanceof checks because it correctly handles
117: * union types, intersection types, and all other composite types.
118: */
119: public function isSuperTypeOf(Type $type): IsSuperTypeOfResult;
120:
121: public function equals(Type $type): bool;
122:
123: public function describe(VerbosityLevel $level): string;
124:
125: public function canAccessProperties(): TrinaryLogic;
126:
127: /** @deprecated Use hasInstanceProperty or hasStaticProperty instead */
128: public function hasProperty(string $propertyName): TrinaryLogic;
129:
130: /** @deprecated Use getInstanceProperty or getStaticProperty instead */
131: public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection;
132:
133: /** @deprecated Use getUnresolvedInstancePropertyPrototype or getUnresolvedStaticPropertyPrototype instead */
134: public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection;
135:
136: public function hasInstanceProperty(string $propertyName): TrinaryLogic;
137:
138: public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection;
139:
140: /**
141: * Unlike getInstanceProperty(), this defers template type resolution.
142: * Use getInstanceProperty() in most rule implementations.
143: */
144: public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection;
145:
146: public function hasStaticProperty(string $propertyName): TrinaryLogic;
147:
148: public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection;
149:
150: public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection;
151:
152: public function canCallMethods(): TrinaryLogic;
153:
154: public function hasMethod(string $methodName): TrinaryLogic;
155:
156: public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): ExtendedMethodReflection;
157:
158: /**
159: * Unlike getMethod(), this defers template type and static type resolution.
160: * Use getMethod() in most rule implementations.
161: */
162: public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection;
163:
164: public function canAccessConstants(): TrinaryLogic;
165:
166: public function hasConstant(string $constantName): TrinaryLogic;
167:
168: public function getConstant(string $constantName): ClassConstantReflection;
169:
170: public function isIterable(): TrinaryLogic;
171:
172: public function isIterableAtLeastOnce(): TrinaryLogic;
173:
174: /**
175: * Returns the count of elements as a Type (typically IntegerRangeType).
176: */
177: public function getArraySize(): Type;
178:
179: /**
180: * Works for both arrays and Traversable objects.
181: */
182: public function getIterableKeyType(): Type;
183:
184: /** @deprecated use getIterableKeyType */
185: public function getFirstIterableKeyType(): Type;
186:
187: /** @deprecated use getIterableKeyType */
188: public function getLastIterableKeyType(): Type;
189:
190: public function getIterableValueType(): Type;
191:
192: /** @deprecated use getIterableValueType */
193: public function getFirstIterableValueType(): Type;
194:
195: /** @deprecated use getIterableValueType */
196: public function getLastIterableValueType(): Type;
197:
198: public function isArray(): TrinaryLogic;
199:
200: public function isConstantArray(): TrinaryLogic;
201:
202: /**
203: * An oversized array is a constant array shape that grew too large to track
204: * precisely and was degraded to a generic array type.
205: */
206: public function isOversizedArray(): TrinaryLogic;
207:
208: /**
209: * A list is an array with sequential integer keys starting from 0 with no gaps.
210: */
211: public function isList(): TrinaryLogic;
212:
213: public function isOffsetAccessible(): TrinaryLogic;
214:
215: /**
216: * Whether accessing a non-existent offset is safe (won't cause errors).
217: * Unlike isOffsetAccessible() which checks if offset access is supported at all.
218: */
219: public function isOffsetAccessLegal(): TrinaryLogic;
220:
221: public function hasOffsetValueType(Type $offsetType): TrinaryLogic;
222:
223: public function getOffsetValueType(Type $offsetType): Type;
224:
225: /**
226: * May add a new key. When $offsetType is null, appends (like $a[] = $value).
227: *
228: * @see Type::setExistingOffsetValueType() for modifying an existing key without widening
229: */
230: public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type;
231:
232: /**
233: * Unlike setOffsetValueType(), assumes the key already exists.
234: * Preserves the array shape and list type.
235: */
236: public function setExistingOffsetValueType(Type $offsetType, Type $valueType): Type;
237:
238: public function unsetOffset(Type $offsetType): Type;
239:
240: /** Models array_keys($array, $searchValue, $strict). */
241: public function getKeysArrayFiltered(Type $filterValueType, TrinaryLogic $strict): Type;
242:
243: /** Models array_keys($array). */
244: public function getKeysArray(): Type;
245:
246: /** Models array_values($array). */
247: public function getValuesArray(): Type;
248:
249: /** Models array_chunk($array, $length, $preserveKeys). */
250: public function chunkArray(Type $lengthType, TrinaryLogic $preserveKeys): Type;
251:
252: /** Models array_fill_keys($keys, $value). */
253: public function fillKeysArray(Type $valueType): Type;
254:
255: /** Models array_flip($array). */
256: public function flipArray(): Type;
257:
258: /** Models array_intersect_key($array, ...$otherArrays). */
259: public function intersectKeyArray(Type $otherArraysType): Type;
260:
261: /** Models array_pop() effect on the array. */
262: public function popArray(): Type;
263:
264: /** Models array_reverse($array, $preserveKeys). */
265: public function reverseArray(TrinaryLogic $preserveKeys): Type;
266:
267: /** Models array_search($needle, $array, $strict). */
268: public function searchArray(Type $needleType, ?TrinaryLogic $strict = null): Type;
269:
270: /** Models array_shift() effect on the array. */
271: public function shiftArray(): Type;
272:
273: /**
274: * Models shuffle() effect on the array. Result is always a list.
275: *
276: * It's also used to model array after `sort` / `rsort` / `usort` calls.
277: */
278: public function shuffleArray(): Type;
279:
280: /** Models array_slice($array, $offset, $length, $preserveKeys). */
281: public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $preserveKeys): Type;
282:
283: /** Models array_splice() effect on the array (the modified array, not the removed portion). */
284: public function spliceArray(Type $offsetType, Type $lengthType, Type $replacementType): Type;
285:
286: /**
287: * Downgrades the list-ness of the array from `Yes` to `Maybe` (e.g. for
288: * `asort`/`uksort`/etc. which preserve keys but break list ordering).
289: * Other shape information (keys, values, accessories like NonEmpty) is
290: * preserved.
291: */
292: public function makeListMaybe(): Type;
293:
294: /**
295: * Models "same keys, every value transformed" (e.g. `array_walk`,
296: * `array_map($cb, $a)`, `preg_replace*` over an array subject). Keys
297: * and accessories like list-ness / non-emptiness are preserved.
298: *
299: * @param callable(Type): Type $cb
300: */
301: public function mapValueType(callable $cb): Type;
302:
303: /**
304: * Replaces the iterable key type via `$cb($currentKeyType)`. For
305: * `ArrayType` rewrites the key type wholesale; for `ConstantArrayType`
306: * the explicit keys (which are already precise constants) are preserved
307: * — pass-through, matching the prior `TypeTraverser`-based callers.
308: * Used to widen / narrow the key type after a foreach narrowed `$key`
309: * via `is_int($key)` / `is_string($key)` checks.
310: *
311: * @param callable(Type): Type $cb
312: */
313: public function mapKeyType(callable $cb): Type;
314:
315: /**
316: * Marks every explicit key in a `ConstantArrayType` as optional (the
317: * shape can have any subset of the original keys). For non-`CAT` arrays
318: * this is a no-op — they already model arbitrary subsets. Used by
319: * `preg_replace*` over array subjects, where the callback can drop
320: * entries.
321: */
322: public function makeAllArrayKeysOptional(): Type;
323:
324: /**
325: * Models `array_change_key_case($a, $case)`. String keys are case-folded
326: * (constant ones to a specific value, general ones via accessories);
327: * non-string keys, values, accessories and list-ness are preserved.
328: * `$case` matches PHP's `CASE_LOWER` / `CASE_UPPER`; `null` means the
329: * case is non-constant and the result is the union of both folds.
330: */
331: public function changeKeyCaseArray(?int $case): Type;
332:
333: /**
334: * Models `array_filter($a)` (no callback): drops entries whose value is
335: * definitely falsey, marks possibly-falsey entries optional, keeps
336: * definitely-truthy entries unchanged. Keys are preserved; list-ness
337: * is downgraded since gaps may appear.
338: */
339: public function filterArrayRemovingFalsey(): Type;
340:
341: /** @return list<EnumCaseObjectType> */
342: public function getEnumCases(): array;
343:
344: /**
345: * Returns the single enum case this type represents, or null if not exactly one case.
346: */
347: public function getEnumCaseObject(): ?EnumCaseObjectType;
348:
349: /**
350: * Returns a list of finite values this type can take.
351: *
352: * Examples:
353: *
354: * - for bool: [true, false]
355: * - for int<0, 3>: [0, 1, 2, 3]
356: * - for enums: list of enum cases
357: * - for scalars: the scalar itself
358: *
359: * For infinite types it returns an empty array.
360: *
361: * @return list<Type>
362: */
363: public function getFiniteTypes(): array;
364:
365: /** Models the ** operator. */
366: public function exponentiate(Type $exponent): Type;
367:
368: public function isCallable(): TrinaryLogic;
369:
370: /** @return list<CallableParametersAcceptor> */
371: public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array;
372:
373: public function isCloneable(): TrinaryLogic;
374:
375: /** Models the (bool) cast. */
376: public function toBoolean(): BooleanType;
377:
378: /** Models numeric coercion for arithmetic operators. */
379: public function toNumber(): Type;
380:
381: /** Models the bitwise-not (`~$x`) operator. Returns `ErrorType` for types where `~` is undefined. */
382: public function toBitwiseNotType(): Type;
383:
384: /**
385: * Models `get_class($x)`'s return type per leaf: definite objects yield
386: * their `class-string` projection, definite non-objects yield `false`,
387: * and possibly-objects yield the union of both.
388: */
389: public function toGetClassResultType(): Type;
390:
391: /**
392: * Models the type of `$x::class`. For known final classes the literal
393: * class name is returned; for everything else an
394: * `IntersectionType[ClassString<X>, AccessoryLiteralStringType]`.
395: * `NullType` passes through (mirrors PHP's nullsafe `::class` semantics).
396: * `ReflectionProvider` is needed for the final-class lookup.
397: */
398: public function toClassConstantType(ReflectionProvider $reflectionProvider): Type;
399:
400: /**
401: * Projects a class-name-or-object `Type` (the right-hand side of
402: * `$x instanceof <expr>`) to the `ObjectType` it should be compared
403: * against. Constant class strings collapse to their `ObjectType`
404: * exactly; everything kept symbolically (object class names,
405: * `class-string<X>`) carries an uncertainty flag so the caller can
406: * fall back to `BooleanType` instead of a definite yes/no.
407: */
408: public function toObjectTypeForInstanceofCheck(): ClassNameToObjectTypeResult;
409:
410: /**
411: * Projects a class-name-or-object `Type` (the second argument of
412: * `is_a($x, $class, $allow_string)`) to the `ObjectType` to narrow
413: * `$x` against. When `$allowString` is true, the `is_a()` result also
414: * keeps the original class-string accepted alongside the object.
415: * `$allowSameClass` controls whether matching the input's own class
416: * collapses to `NeverType` for final classes (the call site's
417: * "always-true" suppression).
418: */
419: public function toObjectTypeForIsACheck(Type $objectOrClassType, bool $allowString, bool $allowSameClass): ClassNameToObjectTypeResult;
420:
421: /** Models the (int) cast. */
422: public function toInteger(): Type;
423:
424: /** Models the (float) cast. */
425: public function toFloat(): Type;
426:
427: /** Models the (string) cast. */
428: public function toString(): Type;
429:
430: /** Models the (array) cast. */
431: public function toArray(): Type;
432:
433: /**
434: * Models PHP's implicit array key coercion: floats truncated to int,
435: * booleans become 0/1, null becomes '', numeric strings become int.
436: */
437: public function toArrayKey(): Type;
438:
439: /**
440: * Returns how this type might change when passed to a typed parameter
441: * or assigned to a typed property.
442: *
443: * With $strictTypes = true: int widens to int|float (since int is accepted
444: * by float parameters in strict mode).
445: * With $strictTypes = false: additional coercions apply, e.g. Stringable
446: * objects are accepted by string parameters.
447: *
448: * Used internally to determine what types a value might be coerced to
449: * when checking parameter acceptance.
450: */
451: public function toCoercedArgumentType(bool $strictTypes): self;
452:
453: public function isSmallerThan(Type $otherType, PhpVersion $phpVersion): TrinaryLogic;
454:
455: public function isSmallerThanOrEqual(Type $otherType, PhpVersion $phpVersion): TrinaryLogic;
456:
457: /**
458: * Is Type of a known constant value? Includes literal strings, integers, floats, true, false, null, and array shapes.
459: *
460: * Unlike isConstantScalarValue(), this also returns yes for constant array types (array shapes
461: * with known keys and values). Use this when you need to detect any constant value including arrays.
462: */
463: public function isConstantValue(): TrinaryLogic;
464:
465: /**
466: * Is Type of a known constant scalar value? Includes literal strings, integers, floats, true, false, and null.
467: *
468: * Unlike isConstantValue(), this does NOT return yes for array shapes.
469: * Use this when you specifically need scalar constants only.
470: */
471: public function isConstantScalarValue(): TrinaryLogic;
472:
473: /** @return list<ConstantScalarType> */
474: public function getConstantScalarTypes(): array;
475:
476: /** @return list<int|float|string|bool|null> */
477: public function getConstantScalarValues(): array;
478:
479: public function isNull(): TrinaryLogic;
480:
481: public function isTrue(): TrinaryLogic;
482:
483: public function isFalse(): TrinaryLogic;
484:
485: public function isBoolean(): TrinaryLogic;
486:
487: public function isFloat(): TrinaryLogic;
488:
489: public function isInteger(): TrinaryLogic;
490:
491: public function isString(): TrinaryLogic;
492:
493: public function isNumericString(): TrinaryLogic;
494:
495: /**
496: * When isDecimalIntegerString() returns yes(), the type
497: * is guaranteed to be cast to an integer in an array key.
498: * Examples of constant values covered by this type: "0", "1", "1234", "-1"
499: *
500: * When isDecimalIntegerString() returns no(), the type represents strings containing non-decimal integers and other text.
501: * These are guaranteed to stay as string in an array key.
502: * Examples of constant values covered by this type: "+1", "00", "18E+3", "1.2", "1,3", "foo"
503: */
504: public function isDecimalIntegerString(): TrinaryLogic;
505:
506: public function isNonEmptyString(): TrinaryLogic;
507:
508: /**
509: * Non-falsy string is a non-empty string that is also not '0'.
510: * Stricter subset of non-empty-string.
511: */
512: public function isNonFalsyString(): TrinaryLogic;
513:
514: /**
515: * A literal-string is a string composed entirely from string literals
516: * in the source code (not from user input). Used for SQL injection prevention.
517: */
518: public function isLiteralString(): TrinaryLogic;
519:
520: public function isLowercaseString(): TrinaryLogic;
521:
522: public function isUppercaseString(): TrinaryLogic;
523:
524: public function isClassString(): TrinaryLogic;
525:
526: public function isVoid(): TrinaryLogic;
527:
528: public function isScalar(): TrinaryLogic;
529:
530: public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType;
531:
532: /**
533: * Type narrowing methods for comparison operators.
534: * For example, for ConstantIntegerType(5), getSmallerType() returns int<min, 4>.
535: */
536: public function getSmallerType(PhpVersion $phpVersion): Type;
537:
538: public function getSmallerOrEqualType(PhpVersion $phpVersion): Type;
539:
540: public function getGreaterType(PhpVersion $phpVersion): Type;
541:
542: public function getGreaterOrEqualType(PhpVersion $phpVersion): Type;
543:
544: /**
545: * Returns actual template type for a given object.
546: *
547: * Example:
548: *
549: * @-template T
550: * class Foo {}
551: *
552: * // $fooType is Foo<int>
553: * $t = $fooType->getTemplateType(Foo::class, 'T');
554: * $t->isInteger(); // yes
555: *
556: * Returns ErrorType in case of a missing type.
557: *
558: * @param class-string $ancestorClassName
559: */
560: public function getTemplateType(string $ancestorClassName, string $templateTypeName): Type;
561:
562: /**
563: * Infers the real types of TemplateTypes found in $this, based on
564: * the received Type. E.g. if $this is array<T> and $receivedType
565: * is array<int>, infers T = int.
566: */
567: public function inferTemplateTypes(Type $receivedType): TemplateTypeMap;
568:
569: /**
570: * Returns the template types referenced by this Type, recursively.
571: *
572: * The return value is a list of TemplateTypeReferences, who contain the
573: * referenced template type as well as the variance position in which it was
574: * found.
575: *
576: * For example, calling this on array<Foo<T>,Bar> (with T a template type)
577: * will return one TemplateTypeReference for the type T.
578: *
579: * @param TemplateTypeVariance $positionVariance The variance position in
580: * which the receiver type was
581: * found.
582: *
583: * @return list<TemplateTypeReference>
584: */
585: public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array;
586:
587: /** Models abs(). */
588: public function toAbsoluteNumber(): Type;
589:
590: /**
591: * Returns a new instance with all inner types mapped through $cb.
592: * Returns the same instance if inner types did not change.
593: *
594: * Not used directly — use TypeTraverser::map() instead.
595: *
596: * @param callable(Type):Type $cb
597: */
598: public function traverse(callable $cb): Type;
599:
600: /**
601: * Like traverse(), but walks two types simultaneously.
602: *
603: * Not used directly — use SimultaneousTypeTraverser::map() instead.
604: *
605: * @param callable(Type $left, Type $right): Type $cb
606: */
607: public function traverseSimultaneously(Type $right, callable $cb): Type;
608:
609: public function toPhpDocNode(): TypeNode;
610:
611: /** @see TypeCombinator::remove() */
612: public function tryRemove(Type $typeToRemove): ?Type;
613:
614: /**
615: * Removes constant value information. E.g. 'foo' -> string, 1 -> int.
616: * Used when types become too complex to track precisely (e.g. loop iterations).
617: */
618: public function generalize(GeneralizePrecision $precision): Type;
619:
620: /**
621: * Performance optimization to skip template resolution when no templates are present.
622: */
623: public function hasTemplateOrLateResolvableType(): bool;
624:
625: }
626: