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