From 9f4ec57b6fd7c58827e9f6b4fbed2bc09562cb03 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 10 Feb 2026 18:00:04 +0000 Subject: [PATCH 1/9] Add comprehensive PHPDoc documentation to Type interface Document every method on the Type interface with descriptions of purpose, behavior, parameters, return values, and usage guidance. Key additions: - Interface-level doc explaining the central role of Type and the critical rule to never use instanceof for type checks - Core methods: isSuperTypeOf, accepts, equals, describe with examples - Class identification: getReferencedClasses vs getObjectClassNames vs getObjectClassReflections, getClassStringObjectType - Member access: canAccessProperties/canCallMethods/canAccessConstants and has*/get*/getUnresolved* patterns - Array operations: all 13 array transformation methods (chunk, flip, pop, shift, slice, splice, reverse, search, shuffle, etc.) - Offset access: isOffsetAccessible vs isOffsetAccessLegal, setOffsetValueType vs setExistingOffsetValueType - Type checking: all is* methods for scalars, strings, and arrays - Type conversion: all to* methods (toBoolean through toCoercedArgumentType) - Comparison: looseCompare, isSmallerThan, getSmallerType family - Templates: inferTemplateTypes, getReferencedTemplateTypes, traverse - Other: generalize, tryRemove, getFiniteTypes, hasTemplateOrLateResolvableType https://claude.ai/code/session_01Htkqstd1mz1dbevHT6Y437 --- src/Type/Type.php | 820 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 804 insertions(+), 16 deletions(-) diff --git a/src/Type/Type.php b/src/Type/Type.php index 58d37a52a4..0e14cecf0b 100644 --- a/src/Type/Type.php +++ b/src/Type/Type.php @@ -21,6 +21,25 @@ use PHPStan\Type\Generic\TemplateTypeVariance; /** + * Represents a PHPStan type in the type system. + * + * This is the central interface of PHPStan's type system. Every type that PHPStan + * can reason about implements this interface — from simple scalars like StringType + * to complex generics like GenericObjectType. + * + * Each Type knows what it accepts, what is a supertype of it, what properties/methods/constants + * it has, what operations it supports, and how to describe itself for error messages. + * + * Important: Never use `instanceof` to check types. For example, `$type instanceof StringType` + * will miss union types, intersection types with accessory types, and other composite forms. + * Always use the `is*()` methods or `isSuperTypeOf()` instead: + * + * // Wrong: + * if ($type instanceof StringType) { ... } + * + * // Correct: + * if ($type->isString()->yes()) { ... } + * * @api * @see https://phpstan.org/developing-extensions/type-system */ @@ -28,50 +47,200 @@ interface Type { /** + * Returns all class names referenced anywhere in this type, recursively. + * + * Includes class names from object types, generic type arguments, callable + * signatures, conditional type branches, and any other nested positions. + * + * Used for dependency tracking and PHPDoc validation to ensure all referenced + * classes exist. + * + * @see Type::getObjectClassNames() for only the direct object type class names + * * @return list */ public function getReferencedClasses(): array; - /** @return list */ + /** + * Returns class names of the object types represented by this type. + * + * Unlike getReferencedClasses(), this only returns class names of actual + * object types — not classes referenced in generic arguments, callable + * signatures, or other nested positions. + * + * For a union type like Foo|Bar, returns ['Foo', 'Bar']. + * For a non-object type like string, returns []. + * + * @return list + */ public function getObjectClassNames(): array; /** + * Returns ClassReflection instances for the object types represented by this type. + * + * Like getObjectClassNames() but returns full ClassReflection objects, + * giving access to methods, properties, constants, parent classes, interfaces, + * generics, PHPDocs, and attributes. + * * @return list */ public function getObjectClassReflections(): array; /** - * Returns object type Foo for class-string and 'Foo' (if Foo is a valid class). + * Returns the object type for a class-string type or a literal class name string. + * + * For class-string, returns the object type Foo. + * For a literal string 'Foo' where Foo is a valid class, returns the object type Foo. + * For non-class-string types, returns ErrorType. + * + * Used in contexts like `new $className()` where a class-string is instantiated. */ public function getClassStringObjectType(): Type; /** - * Returns object type Foo for class-string, 'Foo' (if Foo is a valid class), - * and object type Foo. + * Returns the object type for class-string types, literal class name strings, + * and object types themselves. + * + * Like getClassStringObjectType(), but also returns object types as-is. + * For class-string, returns the object type Foo. + * For a literal string 'Foo' (if valid class), returns the object type Foo. + * For an object type Foo, returns the object type Foo. + * + * Used in contexts like static method/property access where the left side + * can be either a class-string or an object: `$classOrObject::method()`. */ public function getObjectTypeOrClassStringObjectType(): Type; + /** + * Returns whether this type is an object type. + * + * For ObjectType and its subtypes, returns yes. + * For union types, returns yes if all members are objects, + * maybe if some are, no if none are. + * For non-object types (scalars, arrays, etc.), returns no. + */ public function isObject(): TrinaryLogic; + /** + * Returns whether this type is an enum type. + * + * Returns yes for enum types and enum case types. + * Returns maybe for generic object types that might be enums. + * Returns no for non-enum types. + */ public function isEnum(): TrinaryLogic; - /** @return list */ + /** + * Returns the array type instances contained in this type. + * + * For a union type like array|array, returns both array types. + * For non-array types, returns an empty array. + * + * Used when you need to iterate over each array component separately, + * such as when computing array operation results. + * + * @see Type::getConstantArrays() for only array shapes with known structure + * + * @return list + */ public function getArrays(): array; - /** @return list */ + /** + * Returns the constant array type instances (array shapes) contained in this type. + * + * Unlike getArrays(), only returns ConstantArrayType instances — arrays with + * known keys and value types like array{name: string, age: int}. + * Generic array types like array are excluded. + * + * @return list + */ public function getConstantArrays(): array; - /** @return list */ + /** + * Returns the constant string type instances contained in this type. + * + * For a union type like 'foo'|'bar', returns both constant string types. + * For a generic string type, returns an empty array. + * + * Used when you need the actual literal string values, for example + * when evaluating string functions on known values. + * + * @return list + */ public function getConstantStrings(): array; + /** + * Checks whether this type accepts the given type for assignment or parameter passing. + * + * This is the method used by rules to validate that a value can be passed to a + * parameter, assigned to a typed property, or returned from a typed function. + * + * Unlike isSuperTypeOf(), accepts() takes into account PHP's implicit type coercion. + * With $strictTypes = false, int is accepted by float, and Stringable objects are + * accepted by string. With $strictTypes = true, the behavior is closer to isSuperTypeOf(). + * + * Returns AcceptsResult with TrinaryLogic (yes/maybe/no) and optional reasons + * explaining why the type was not accepted, for better error messages. + */ public function accepts(Type $type, bool $strictTypes): AcceptsResult; + /** + * Checks whether this type is a supertype of the given type. + * + * This is the fundamental type relationship method. It answers the question: + * "Does every value of $type belong to $this type?" + * + * Examples: + * (new StringType())->isSuperTypeOf(new ConstantStringType('foo')) // yes + * (new StringType())->isSuperTypeOf(new IntegerType()) // no + * (new StringType())->isSuperTypeOf(new MixedType()) // maybe + * + * Returns IsSuperTypeOfResult with TrinaryLogic (yes/maybe/no) and optional + * reasons. Use ->yes(), ->maybe(), ->no() to check the result. + * + * This method is preferable to instanceof checks because it correctly handles + * union types, intersection types, and all other composite types. + */ public function isSuperTypeOf(Type $type): IsSuperTypeOfResult; + /** + * Checks whether two types are structurally equal. + * + * Returns true only if the types are exactly the same — not merely compatible. + * Unlike isSuperTypeOf(), this is a strict binary check (true/false). + * + * For example, int|float is NOT equal to int (but int|float is a supertype of int). + * + * Used for cache validation, result deduplication, and checking if a type changed + * across analysis iterations. + */ public function equals(Type $type): bool; + /** + * Returns a human-readable string representation of this type. + * + * The verbosity level controls how much detail is included: + * - VerbosityLevel::typeOnly(): Simple type name (e.g. "string", "Foo") + * - VerbosityLevel::value(): Includes constant values (e.g. "'hello'", "1|2|3") + * - VerbosityLevel::precise(): Full details including accessory types (e.g. "non-empty-list") + * - VerbosityLevel::cache(): Most detailed, for internal caching purposes + * + * Used in error messages, debugging output, and test assertions. + * Use VerbosityLevel::getRecommendedLevelByType() to choose the appropriate level + * for error messages based on the types being compared. + */ public function describe(VerbosityLevel $level): string; + /** + * Returns whether property access ($obj->prop) is possible on this type. + * + * Returns yes for object types that support property access. + * Returns maybe for mixed types. + * Returns no for scalars, arrays, and other non-object types. + * + * This is a general capability check. Use hasInstanceProperty() or hasStaticProperty() + * to check for a specific property. + */ public function canAccessProperties(): TrinaryLogic; /** @deprecated Use hasInstanceProperty or hasStaticProperty instead */ @@ -83,38 +252,160 @@ public function getProperty(string $propertyName, ClassMemberAccessAnswerer $sco /** @deprecated Use getUnresolvedInstancePropertyPrototype or getUnresolvedStaticPropertyPrototype instead */ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection; + /** + * Returns whether a specific instance property exists on this type. + * + * Queries reflection to find the property definition. Returns yes if the + * property definitely exists, no if it definitely doesn't, or maybe if + * it might exist (e.g. on a mixed type). + */ public function hasInstanceProperty(string $propertyName): TrinaryLogic; + /** + * Returns the reflection for a specific instance property. + * + * The ClassMemberAccessAnswerer provides scope context for visibility checks. + * Call hasInstanceProperty() first to verify the property exists. + */ public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection; + /** + * Returns the unresolved property prototype for a specific instance property. + * + * Unlike getInstanceProperty(), this returns an intermediate representation + * that allows deferring template type resolution and applying transformations + * based on the called-on type (e.g. resolving static/self to the actual type). + * + * Use getInstanceProperty() in most rule implementations. + * Use this method in framework code that needs to apply type transformations. + */ public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection; + /** + * Returns whether a specific static property exists on this type. + */ public function hasStaticProperty(string $propertyName): TrinaryLogic; + /** + * Returns the reflection for a specific static property. + * + * The ClassMemberAccessAnswerer provides scope context for visibility checks. + * Call hasStaticProperty() first to verify the property exists. + */ public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection; + /** + * Returns the unresolved property prototype for a specific static property. + * + * @see Type::getUnresolvedInstancePropertyPrototype() for explanation of resolved vs unresolved + */ public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection; + /** + * Returns whether method calls ($obj->method()) are possible on this type. + * + * Returns yes for object types. + * Returns maybe for mixed types. + * Returns no for scalars, arrays, and other types that don't support method calls. + * + * This is a general capability check. Use hasMethod() to check for a specific method. + */ public function canCallMethods(): TrinaryLogic; + /** + * Returns whether a specific method exists on this type. + * + * Queries reflection for the method definition. Returns yes if the method + * definitely exists, no if it definitely doesn't, or maybe if it might + * exist (e.g. on a mixed type, or via __call). + */ public function hasMethod(string $methodName): TrinaryLogic; + /** + * Returns the reflection for a specific method. + * + * The ClassMemberAccessAnswerer provides scope context for visibility checks. + * Call hasMethod() first to verify the method exists. + */ public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): ExtendedMethodReflection; + /** + * Returns the unresolved method prototype for a specific method. + * + * Unlike getMethod(), this returns an intermediate representation that allows + * deferring template type resolution and applying transformations based on + * the called-on type (e.g. resolving static return types). + * + * Use getMethod() in most rule implementations. + * Use this method in framework code that needs to apply type transformations. + */ public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection; + /** + * Returns whether class constant access (Foo::CONST) is possible on this type. + * + * Returns yes for object/class types. + * Returns maybe for mixed types. + * Returns no for scalars, arrays, and other types that don't support constant access. + * + * This is a general capability check. Use hasConstant() to check for a specific constant. + */ public function canAccessConstants(): TrinaryLogic; + /** + * Returns whether a specific class constant exists on this type. + */ public function hasConstant(string $constantName): TrinaryLogic; + /** + * Returns the reflection for a specific class constant. + * + * Call hasConstant() first to verify the constant exists. + */ public function getConstant(string $constantName): ClassConstantReflection; + /** + * Returns whether this type can be iterated over (in a foreach loop). + * + * Iterable types include arrays, objects implementing Traversable + * (Iterator or IteratorAggregate), and the iterable pseudo-type. + * + * Returns yes for arrays and Traversable objects. + * Returns maybe for mixed types. + * Returns no for scalars and non-traversable objects. + */ public function isIterable(): TrinaryLogic; + /** + * Returns whether this type is iterable and guaranteed to be non-empty. + * + * Unlike isIterable(), this also asserts the iterable has at least one element. + * + * Returns yes for non-empty arrays and non-empty iterables. + * Returns no for empty arrays or types that might be empty. + */ public function isIterableAtLeastOnce(): TrinaryLogic; + /** + * Returns the count of elements as a Type, typically an IntegerRangeType. + * + * For constant arrays, returns the precise count (possibly a range + * accounting for optional keys). + * For generic arrays, returns int<0, max>. + * For non-empty arrays, returns int<1, max>. + * + * Used by count() return type extensions and array size comparisons. + */ public function getArraySize(): Type; + /** + * Returns the key type of this iterable. + * + * Works for both arrays and Traversable objects. + * For array, returns string. + * For Iterator, returns int. + * For non-iterable types, returns ErrorType. + */ public function getIterableKeyType(): Type; /** @deprecated use getIterableKeyType */ @@ -123,6 +414,14 @@ public function getFirstIterableKeyType(): Type; /** @deprecated use getIterableKeyType */ public function getLastIterableKeyType(): Type; + /** + * Returns the value type of this iterable. + * + * Works for both arrays and Traversable objects. + * For array, returns int. + * For Iterator, returns Foo. + * For non-iterable types, returns ErrorType. + */ public function getIterableValueType(): Type; /** @deprecated use getIterableValueType */ @@ -131,65 +430,280 @@ public function getFirstIterableValueType(): Type; /** @deprecated use getIterableValueType */ public function getLastIterableValueType(): Type; + /** + * Returns whether this type is an array. + * + * Returns yes for all array types (generic arrays, constant arrays, lists). + * Returns no for non-array types including objects implementing ArrayAccess. + */ public function isArray(): TrinaryLogic; + /** + * Returns whether this type is a constant array (array shape). + * + * Constant arrays have known keys and value types, like array{name: string, age: int}. + * Generic arrays like array are NOT constant arrays. + * + * Returns yes for ConstantArrayType instances. + * Returns no for generic array types and non-array types. + */ public function isConstantArray(): TrinaryLogic; + /** + * Returns whether this type is an oversized array. + * + * An oversized array is a constant array shape that grew too large to track + * precisely and was degraded to a generic array type with an OversizedArrayType + * accessory. This prevents performance issues from tracking thousands of keys. + * + * Returns yes for OversizedArrayType. + * Returns maybe for generic arrays (which could be oversized). + * Returns no for constant arrays (known size, not oversized). + */ public function isOversizedArray(): TrinaryLogic; + /** + * Returns whether this type is a list (sequential integer-keyed array). + * + * A list is an array with sequential integer keys starting from 0 with no gaps. + * For example, array{0: 'a', 1: 'b', 2: 'c'} is a list. + * array{0: 'a', 2: 'c'} is NOT a list (gap at key 1). + * array{name: string} is NOT a list (string key). + * + * Returns yes for types known to be lists. + * Returns maybe for generic arrays that might be lists. + * Returns no for arrays known not to be lists and non-array types. + */ public function isList(): TrinaryLogic; + /** + * Returns whether this type supports array offset access ($a[$key]). + * + * Returns yes for arrays, strings, and objects implementing ArrayAccess. + * Returns maybe for mixed types. + * Returns no for scalars (other than string) and non-ArrayAccess objects. + */ public function isOffsetAccessible(): TrinaryLogic; + /** + * Returns whether accessing an undefined offset on this type is legal. + * + * Unlike isOffsetAccessible() which checks if offset access is supported at all, + * this checks whether accessing a non-existent offset is safe (won't cause errors). + * + * Returns yes for arrays and strings (return null/empty string for missing offsets). + * Used by rules to decide whether to report undefined offset access errors. + */ public function isOffsetAccessLegal(): TrinaryLogic; + /** + * Returns whether the given offset exists in this type. + * + * For constant arrays, checks whether the specific key exists. + * For generic arrays, returns maybe (the key could exist). + * For objects implementing ArrayAccess, checks parameter compatibility. + * + * Returns yes if the offset definitely exists, no if it definitely doesn't, + * or maybe if it might exist. + */ public function hasOffsetValueType(Type $offsetType): TrinaryLogic; + /** + * Returns the type of the value at the given offset. + * + * For constant arrays, returns the value type for the specific key. + * For generic arrays, returns the generic value type. + * For strings, returns a single-character string type. + * + * Check hasOffsetValueType() first to determine whether the offset exists. + */ public function getOffsetValueType(Type $offsetType): Type; + /** + * Returns a new type with the offset set to the given value type. + * + * This may add a new key to the array. It can change the array structure + * (e.g. break list type if the key is not sequential). + * + * When $offsetType is null, the value is appended (like $a[] = $value). + * When $unionValues is true, the new value type is unioned with any existing + * value type at that offset. + * + * @see Type::setExistingOffsetValueType() for modifying an existing key without widening + */ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type; + /** + * Returns a new type with an existing offset's value type changed. + * + * Unlike setOffsetValueType(), this assumes the key already exists. + * It preserves the array shape and list type — it does not add new keys + * or widen the array. + * + * Used when modifying a known array element, like $a[$existingKey] = $newValue. + */ public function setExistingOffsetValueType(Type $offsetType, Type $valueType): Type; + /** + * Returns a new type with the given offset removed. + * + * For constant arrays, removes the specific key. + * For generic arrays, returns the same type (cannot determine which key was removed). + * + * Models the behavior of unset($a[$key]). + */ public function unsetOffset(Type $offsetType): Type; + /** + * Returns the keys of this array as a list type, filtered to only include + * keys whose values match the given filter type. + * + * Models the behavior of array_keys($array, $searchValue, $strict). + * The $strict parameter controls whether loose (==) or strict (===) comparison + * is used to match values. + */ public function getKeysArrayFiltered(Type $filterValueType, TrinaryLogic $strict): Type; + /** + * Returns the keys of this array as a list type. + * + * Models the behavior of array_keys($array). + * Returns a list — always a list with integer keys starting from 0. + */ public function getKeysArray(): Type; + /** + * Returns the values of this array as a reindexed list type. + * + * Models the behavior of array_values($array). + * Returns a list — always a list with integer keys starting from 0. + */ public function getValuesArray(): Type; + /** + * Returns the type resulting from splitting this array into chunks. + * + * Models the behavior of array_chunk($array, $length, $preserveKeys). + * Returns a list of arrays, each containing up to $lengthType elements. + * When $preserveKeys is yes, original keys are preserved within each chunk. + * When $preserveKeys is no, each chunk is reindexed as a list. + */ public function chunkArray(Type $lengthType, TrinaryLogic $preserveKeys): Type; + /** + * Returns the type resulting from using this array's values as keys + * filled with the given value type. + * + * Models the behavior of array_fill_keys($keys, $value) where $keys + * is this array's values. + */ public function fillKeysArray(Type $valueType): Type; + /** + * Returns the type resulting from swapping keys and values. + * + * Models the behavior of array_flip($array). + * Original keys become values and original values become keys + * (values are converted to valid array keys via toArrayKey()). + */ public function flipArray(): Type; + /** + * Returns the type resulting from keeping only keys that exist in the other arrays. + * + * Models the behavior of array_intersect_key($array, ...$otherArrays). + * Retains entries from this array whose keys also exist in $otherArraysType. + */ public function intersectKeyArray(Type $otherArraysType): Type; + /** + * Returns the type resulting from removing the last element. + * + * Models the effect of array_pop() on the array. + * For constant arrays, removes the last entry. + * For generic arrays, returns the same type. + */ public function popArray(): Type; + /** + * Returns the type resulting from reversing element order. + * + * Models the behavior of array_reverse($array, $preserveKeys). + * When $preserveKeys is yes, original keys are preserved. + * When $preserveKeys is no, integer keys are reindexed. + */ public function reverseArray(TrinaryLogic $preserveKeys): Type; + /** + * Returns the type of the key found when searching for a value. + * + * Models the behavior of array_search($needle, $array, $strict). + * Returns a union of matching key types, or false if no match is found. + * When $strict is yes, uses strict comparison (===). + * When $strict is no or null, uses loose comparison (==). + */ public function searchArray(Type $needleType, ?TrinaryLogic $strict = null): Type; + /** + * Returns the type resulting from removing the first element. + * + * Models the effect of array_shift() on the array. + * For constant arrays, removes the first entry and reindexes integer keys. + * For generic arrays, returns the same type. + */ public function shiftArray(): Type; + /** + * Returns the type resulting from randomizing element order. + * + * Models the effect of shuffle() on the array. + * The result is always a list (integer keys starting from 0). + * Constant array type information is degraded since order is unknown. + */ public function shuffleArray(): Type; + /** + * Returns the type resulting from extracting a portion of the array. + * + * Models the behavior of array_slice($array, $offset, $length, $preserveKeys). + * Extracts elements starting at $offsetType with optional $lengthType limit. + * When $preserveKeys is yes, original keys are kept. + * When $preserveKeys is no, integer keys are reindexed. + */ public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $preserveKeys): Type; + /** + * Returns the type resulting from removing and replacing a portion of the array. + * + * Models the effect of array_splice() on the array (the modified array, not the removed portion). + * Removes elements starting at $offsetType for $lengthType entries, then inserts + * $replacementType elements in their place. + */ public function spliceArray(Type $offsetType, Type $lengthType, Type $replacementType): Type; /** + * Returns all enum cases represented by this type. + * + * For a specific enum case type, returns that single case. + * For a full enum type, returns all defined cases. + * For a union of enum cases, returns all cases in the union. + * For non-enum types, returns an empty array. + * * @return list */ public function getEnumCases(): array; + /** + * Returns the single enum case this type represents, or null. + * + * Unlike getEnumCases() which returns all cases, this returns a single + * EnumCaseObjectType only when the type represents exactly one enum case. + * Returns null for non-enum types, full enum types, or unions of multiple cases. + */ public function getEnumCaseObject(): ?EnumCaseObjectType; /** - * Returns a list of finite values. + * Returns a list of finite values this type can take. * * Examples: * @@ -200,108 +714,335 @@ public function getEnumCaseObject(): ?EnumCaseObjectType; * * For infinite types it returns an empty array. * + * Used to determine if a check covers all possible values + * (e.g. in switch exhaustiveness analysis). + * * @return list */ public function getFiniteTypes(): array; + /** + * Returns the type resulting from raising this type to the power of $exponent. + * + * Models the ** operator. For integer and float types, returns the appropriate + * numeric result type. For non-numeric types, returns ErrorType. + */ public function exponentiate(Type $exponent): Type; + /** + * Returns whether this type can be called as a function/method. + * + * Returns yes for Closure, callable types, callable strings, callable arrays, + * and objects with __invoke(). + * Returns maybe for mixed types and generic strings. + * Returns no for non-callable types. + */ public function isCallable(): TrinaryLogic; /** + * Returns the parameter acceptors (signatures) for this callable type. + * + * Each CallableParametersAcceptor describes one possible signature of the callable, + * including parameters and return type. Multiple entries indicate overloaded signatures. + * + * Call isCallable() first to verify this type is callable. + * * @return list */ public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array; + /** + * Returns whether values of this type can be cloned. + * + * Returns yes for object types (unless clone is restricted). + * Returns no for non-object types. + */ public function isCloneable(): TrinaryLogic; + /** + * Returns the type resulting from casting to bool. + * + * Models the (bool) cast and boolean coercion in conditions. + * Empty arrays, 0, 0.0, '', '0', null, and false are falsy. + * Everything else is truthy. + */ public function toBoolean(): BooleanType; + /** + * Returns the type resulting from numeric coercion (int or float). + * + * Models the implicit conversion that occurs with arithmetic operators. + * Numeric strings become int or float, booleans become 0 or 1. + * Arrays and non-numeric types return ErrorType. + */ public function toNumber(): Type; + /** + * Returns the type resulting from casting to int. + * + * Models the (int) cast. Floats are truncated, strings are parsed, + * booleans become 0 or 1, null becomes 0. + * Arrays return ErrorType. + */ public function toInteger(): Type; + /** + * Returns the type resulting from casting to float. + * + * Models the (float) cast. Integers become floats, strings are parsed, + * booleans become 0.0 or 1.0, null becomes 0.0. + * Arrays return ErrorType. + */ public function toFloat(): Type; + /** + * Returns the type resulting from casting to string. + * + * Models the (string) cast. Integers, floats, and booleans become their + * string representations. Objects with __toString() return their string type. + * Arrays and objects without __toString() return ErrorType. + */ public function toString(): Type; + /** + * Returns the type resulting from casting to array. + * + * Models the (array) cast. Arrays return themselves, scalars are wrapped + * in a single-element array, and objects are converted to their property arrays. + */ public function toArray(): Type; + /** + * Returns the type when used as an array key. + * + * Models PHP's implicit array key coercion: floats are truncated to int, + * booleans become 0/1, null becomes '', and strings that look like integers + * are converted to int. Objects and arrays return ErrorType since they + * cannot be used as array keys. + */ public function toArrayKey(): Type; /** - * Tells how a type might change when passed to an argument + * Returns how this type might change when passed to a typed parameter * or assigned to a typed property. * - * Example: int is accepted by int|float with strict_types = 1 - * Stringable is accepted by string|Stringable even without strict_types. + * With $strictTypes = true: int widens to int|float (since int is accepted + * by float parameters in strict mode). + * With $strictTypes = false: additional coercions apply, e.g. Stringable + * objects are accepted by string parameters. + * + * Used internally to determine what types a value might be coerced to + * when checking parameter acceptance. */ public function toCoercedArgumentType(bool $strictTypes): self; + /** + * Returns whether this type is definitely smaller than the given type + * using PHP's < operator semantics. + * + * Takes PhpVersion into account because comparison behavior varies across + * PHP versions (e.g. comparing objects to other types). + */ public function isSmallerThan(Type $otherType, PhpVersion $phpVersion): TrinaryLogic; + /** + * Returns whether this type is definitely smaller than or equal to the given type + * using PHP's <= operator semantics. + */ public function isSmallerThanOrEqual(Type $otherType, PhpVersion $phpVersion): TrinaryLogic; /** * Is Type of a known constant value? Includes literal strings, integers, floats, true, false, null, and array shapes. + * + * Unlike isConstantScalarValue(), this also returns yes for constant array types (array shapes + * with known keys and values). Use this when you need to detect any constant value including arrays. */ public function isConstantValue(): TrinaryLogic; /** * Is Type of a known constant scalar value? Includes literal strings, integers, floats, true, false, and null. + * + * Unlike isConstantValue(), this does NOT return yes for array shapes. + * Use this when you specifically need scalar constants only. */ public function isConstantScalarValue(): TrinaryLogic; /** + * Returns the constant scalar type instances contained in this type. + * + * For a union like 1|2|'foo', returns [ConstantIntegerType(1), ConstantIntegerType(2), ConstantStringType('foo')]. + * For non-constant or infinite types, returns an empty array. + * * @return list */ public function getConstantScalarTypes(): array; /** + * Returns the actual PHP values of constant scalar types. + * + * For a union like 1|2|'foo', returns [1, 2, 'foo']. + * For non-constant or infinite types, returns an empty array. + * * @return list */ public function getConstantScalarValues(): array; + /** + * Returns whether this type is the null type. + */ public function isNull(): TrinaryLogic; + /** + * Returns whether this type is the true type. + */ public function isTrue(): TrinaryLogic; + /** + * Returns whether this type is the false type. + */ public function isFalse(): TrinaryLogic; + /** + * Returns whether this type is a boolean type (true, false, or bool). + */ public function isBoolean(): TrinaryLogic; + /** + * Returns whether this type is a float type. + */ public function isFloat(): TrinaryLogic; + /** + * Returns whether this type is an integer type. + */ public function isInteger(): TrinaryLogic; + /** + * Returns whether this type is a string type. + * + * Returns yes for all string types including constant strings, numeric strings, + * class-string, non-empty-string, literal-string, etc. + */ public function isString(): TrinaryLogic; + /** + * Returns whether this type is a numeric string type. + * + * A numeric string is a string that PHP considers valid for arithmetic, + * like '123', '1.5', or '0x1A'. Returns yes for AccessoryNumericStringType + * and constant strings that are numeric. + */ public function isNumericString(): TrinaryLogic; + /** + * Returns whether this type is a non-empty string type. + * + * Returns yes for strings guaranteed to have length >= 1, + * including non-falsy strings, class-strings, and non-empty constant strings. + * Returns no for '' (empty string constant) and generic string types. + */ public function isNonEmptyString(): TrinaryLogic; + /** + * Returns whether this type is a non-falsy string type. + * + * A non-falsy string is a non-empty string that is also not '0'. + * This is a stricter subset of non-empty-string. + * Returns yes for AccessoryNonFalsyStringType and qualifying constant strings. + */ public function isNonFalsyString(): TrinaryLogic; + /** + * Returns whether this type is a literal string type. + * + * A literal-string is a string whose value was composed entirely from + * string literals in the source code (not from user input). Used for + * SQL injection prevention — literal strings are safe for query building. + */ public function isLiteralString(): TrinaryLogic; + /** + * Returns whether this type is a lowercase string type. + * + * Returns yes for strings known to be entirely lowercase, such as the result + * of strtolower() or constant strings where strtolower($value) === $value. + */ public function isLowercaseString(): TrinaryLogic; + /** + * Returns whether this type is an uppercase string type. + * + * Returns yes for strings known to be entirely uppercase, such as the result + * of strtoupper() or constant strings where strtoupper($value) === $value. + */ public function isUppercaseString(): TrinaryLogic; + /** + * Returns whether this type is a class-string type. + * + * A class-string is a string that contains a valid fully-qualified class name. + * Returns yes for class-string, class-string, and literal strings that + * are known class names. Returns maybe for generic strings. + */ public function isClassString(): TrinaryLogic; + /** + * Returns whether this type is the void type. + * + * Void is a return-type-only type that indicates a function returns no value. + * It cannot be used in union types or as a parameter type. + */ public function isVoid(): TrinaryLogic; + /** + * Returns whether this type is a scalar type. + * + * Scalar types are int, float, string, and bool. + * Returns yes for all scalar types including their constant subtypes. + * Returns no for arrays, objects, null, void, and resource. + */ public function isScalar(): TrinaryLogic; + /** + * Returns the result of a loose comparison (==) between this type and the given type. + * + * Models PHP's type juggling comparison rules. Returns a BooleanType that + * may be true, false, or bool (when the result is uncertain). + * Takes PhpVersion into account because loose comparison behavior varies + * across PHP versions (e.g. 0 == "foo" changed in PHP 8.0). + */ public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType; + /** + * Returns a type representing all values that are smaller than this type. + * + * Used for type narrowing after < comparisons. + * For example, for ConstantIntegerType(5), returns int. + */ public function getSmallerType(PhpVersion $phpVersion): Type; + /** + * Returns a type representing all values that are smaller than or equal to this type. + * + * Used for type narrowing after <= comparisons. + * For example, for ConstantIntegerType(5), returns int. + */ public function getSmallerOrEqualType(PhpVersion $phpVersion): Type; + /** + * Returns a type representing all values that are greater than this type. + * + * Used for type narrowing after > comparisons. + * For example, for ConstantIntegerType(5), returns int<6, max>. + */ public function getGreaterType(PhpVersion $phpVersion): Type; + /** + * Returns a type representing all values that are greater than or equal to this type. + * + * Used for type narrowing after >= comparisons. + * For example, for ConstantIntegerType(5), returns int<5, max>. + */ public function getGreaterOrEqualType(PhpVersion $phpVersion): Type; /** @@ -323,15 +1064,18 @@ public function getGreaterOrEqualType(PhpVersion $phpVersion): Type; public function getTemplateType(string $ancestorClassName, string $templateTypeName): Type; /** - * Infers template types + * Infers template types. * * Infers the real Type of the TemplateTypes found in $this, based on - * the received Type. + * the received Type. For example, if $this is array and $receivedType + * is array, it infers T = int. + * + * Returns a TemplateTypeMap mapping template type names to their inferred types. */ public function inferTemplateTypes(Type $receivedType): TemplateTypeMap; /** - * Returns the template types referenced by this Type, recursively + * Returns the template types referenced by this Type, recursively. * * The return value is a list of TemplateTypeReferences, who contain the * referenced template type as well as the variance position in which it was @@ -348,36 +1092,80 @@ public function inferTemplateTypes(Type $receivedType): TemplateTypeMap; */ public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array; + /** + * Returns the type resulting from taking the absolute value. + * + * Models the abs() function. For negative constant integers/floats, returns the + * positive counterpart. For integer ranges, adjusts bounds accordingly. + * For non-numeric types, returns ErrorType. + */ public function toAbsoluteNumber(): Type; /** - * Traverses inner types + * Traverses inner types. * * Returns a new instance with all inner types mapped through $cb. Might * return the same instance if inner types did not change. * + * Used to resolve template types, transform nested types, or collect + * information about type structure. For example, replacing TemplateType + * placeholders with concrete types in a generic instantiation. + * * @param callable(Type):Type $cb */ public function traverse(callable $cb): Type; /** - * Traverses inner types while keeping the same context in another type. + * Traverses inner types while keeping the same structure in another type. + * + * Like traverse(), but walks two types simultaneously, passing matching + * pairs of inner types from $this and $right to the callback. Used when + * two types with the same structure need to be compared or merged element-wise. * * @param callable(Type $left, Type $right): Type $cb */ public function traverseSimultaneously(Type $right, callable $cb): Type; + /** + * Converts this Type to its PHPDoc AST node representation. + * + * Used to serialize types back to PHPDoc format, for example when generating + * PHPDoc annotations or converting types for display in documentation tools. + */ public function toPhpDocNode(): TypeNode; /** * Return the difference with another type, or null if it cannot be represented. * + * For example, int|string minus string returns int. + * Returns null when the subtraction cannot be cleanly represented as a Type. + * * @see TypeCombinator::remove() */ public function tryRemove(Type $typeToRemove): ?Type; + /** + * Generalizes this type by removing constant value information. + * + * Converts specific/literal types to their more general equivalents: + * - GeneralizePrecision::lessSpecific(): Full generalization, e.g. 'foo' -> string, 1 -> int + * - GeneralizePrecision::moreSpecific(): Partial generalization, preserves some detail (used in loop analysis) + * - GeneralizePrecision::templateArgument(): For template argument generalization + * + * Used when types become too complex to track precisely, such as after + * repeated loop iterations where constant arrays grow unboundedly. + */ public function generalize(GeneralizePrecision $precision): Type; + /** + * Returns whether this type contains any template types or late-resolvable types. + * + * Template types are generic type parameters (T, TValue, etc.) waiting to be resolved. + * Late-resolvable types are types that cannot be fully determined during initial analysis. + * + * Used as a performance optimization to skip template resolution logic when + * no templates are present. + */ public function hasTemplateOrLateResolvableType(): bool; } From 69422bae9e694eefd73632398e0fd169596f45f7 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 10 Feb 2026 18:08:28 +0000 Subject: [PATCH 2/9] Add comprehensive PHPDoc documentation to Scope and parent interfaces Document every method on the Scope interface, ClassMemberAccessAnswerer interface, and NamespaceAnswerer interface with descriptions of purpose, behavior, parameters, return values, and usage guidance. Scope interface: - Interface-level doc explaining Scope as the analysis state at an AST position, listing all extension types that receive it as a parameter - File/context methods: getFile vs getFileDescription (trait handling), isDeclareStrictTypes, isInTrait/getTraitReflection - Function context: getFunction, getFunctionName, getParentScope - Variable tracking: hasVariableType, getVariableType, canAnyVariableExist (extract() handling), getDefinedVariables vs getMaybeDefinedVariables - Type resolution: getType (the core method), getNativeType (PHPDoc-free), getKeepVoidType (preserves void), getScopeType vs getType (deferred evaluation), getScopeNativeType - Name resolution: resolveName (self/static/parent handling), resolveTypeByName (preserves LSB via StaticType/ThisType) - Reflection helpers: getInstancePropertyReflection, getStaticPropertyReflection, getMethodReflection, getConstantReflection - Scope filtering: filterByTruthyValue/filterByFalseyValue for type narrowing in conditional branches - Context queries: isInClassExists, isInFunctionExists, isInClosureBind, isInAnonymousFunction, isInExpressionAssign, isUndefinedExpressionAllowed, isInFirstLevelStatement, getFunctionCallStack ClassMemberAccessAnswerer interface: - Interface-level doc explaining its role as visibility checker passed to Type methods for enforcing public/protected/private access - canReadProperty vs canWriteProperty (PHP 8.4 asymmetric visibility) - canCallMethod, canAccessConstant NamespaceAnswerer interface: - Interface-level doc explaining namespace resolution context https://claude.ai/code/session_01Htkqstd1mz1dbevHT6Y437 --- src/Analyser/Scope.php | 378 ++++++++++++++++++- src/Reflection/ClassMemberAccessAnswerer.php | 59 ++- src/Reflection/NamespaceAnswerer.php | 11 +- 3 files changed, 433 insertions(+), 15 deletions(-) diff --git a/src/Analyser/Scope.php b/src/Analyser/Scope.php index 0e2bac3224..7c3e174657 100644 --- a/src/Analyser/Scope.php +++ b/src/Analyser/Scope.php @@ -22,10 +22,31 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeWithClassName; -/** @api */ +/** + * Represents the state of the analyser at a specific position in the AST. + * + * The Scope tracks everything PHPStan knows at a given point in code: variable types, + * the current class/function/method context, whether strict_types is enabled, and more. + * It is the primary interface through which rules and extensions query information + * about the analysed code. + * + * The Scope is passed as a parameter to: + * - Custom rules (2nd parameter of processNode()) + * - Dynamic return type extensions (last parameter of getTypeFrom*Call()) + * - Dynamic throw type extensions + * - Type-specifying extensions (3rd parameter of specifyTypes()) + * + * The Scope is immutable from the extension's perspective. Each AST node gets + * its own Scope reflecting the analysis state at that point. For example, after + * an `if ($x instanceof Foo)` check, the Scope inside the if-branch knows that + * $x is of type Foo. + * + * @api + */ interface Scope extends ClassMemberAccessAnswerer, NamespaceAnswerer { + /** @var list PHP superglobal variable names that are always available */ public const SUPERGLOBAL_VARIABLES = [ 'GLOBALS', '_SERVER', @@ -38,133 +59,464 @@ interface Scope extends ClassMemberAccessAnswerer, NamespaceAnswerer '_ENV', ]; + /** + * Returns the absolute path of the file being analysed. + * + * When analysing a trait, this returns the file where the trait is used (the class file), + * not the trait file itself. Use getFileDescription() to get the trait file path + * with class context information. + */ public function getFile(): string; + /** + * Returns a human-readable file description for error messages. + * + * For regular files, this is the same as getFile(). + * For traits, this returns the trait file path with the using class context, + * e.g. "TraitFile.php (in context of class MyClass)". + */ public function getFileDescription(): string; + /** + * Returns whether the current file has declare(strict_types=1). + * + * When true, PHP enforces strict type checking for function/method arguments + * and return values — no implicit type coercion is performed. This affects + * how Type::accepts() behaves (e.g. int is not accepted by float in strict mode). + */ public function isDeclareStrictTypes(): bool; /** + * Returns whether the current analysis context is inside a trait. + * + * When true, getTraitReflection() is guaranteed to return non-null. + * Used by rules that need trait-specific behavior, such as skipping + * certain checks that don't apply in trait context. + * * @phpstan-assert-if-true !null $this->getTraitReflection() */ public function isInTrait(): bool; + /** + * Returns the ClassReflection of the trait being analysed, or null. + * + * Only non-null when isInTrait() is true. The returned reflection + * represents the trait itself, not the class using the trait. + * Use getClassReflection() (from ClassMemberAccessAnswerer) to get the + * class that uses the trait. + */ public function getTraitReflection(): ?ClassReflection; + /** + * Returns the reflection of the current function or method, or null. + * + * Returns null when outside of any function/method (e.g. at the top level + * of a file, or in a class but outside a method). For closures and arrow + * functions, returns their reflection. + */ public function getFunction(): ?PhpFunctionFromParserNodeReflection; + /** + * Returns the name of the current function or method, or null. + * + * For methods, returns the method name (not the fully qualified name). + * For closures and arrow functions, returns null. + * For top-level code, returns null. + */ public function getFunctionName(): ?string; + /** + * Returns the parent scope, or null if this is the top-level scope. + * + * The parent scope is the scope that encloses the current one. For example, + * when inside a closure, the parent scope is the scope of the function + * that contains the closure. Used for variable resolution in closures + * and arrow functions. + */ public function getParentScope(): ?self; + /** + * Returns whether a variable with the given name exists in the current scope. + * + * Returns TrinaryLogic::Yes if the variable is definitely defined, + * TrinaryLogic::Maybe if it might be defined (e.g. defined in one branch of an if), + * and TrinaryLogic::No if it is not defined. + */ public function hasVariableType(string $variableName): TrinaryLogic; + /** + * Returns the type of a variable in the current scope. + * + * If the variable is not defined, returns ErrorType. + * Check hasVariableType() first if you need to distinguish between + * undefined variables and variables with unknown types. + */ public function getVariableType(string $variableName): Type; + /** + * Returns whether any variable can potentially exist in this scope. + * + * Returns true at the top level of a file (outside functions/closures) + * or after an extract() call — contexts where arbitrary variables may exist. + * Returns false inside functions, methods, and closures (unless extract() + * was called), where the set of available variables is known. + * + * Used by the DefinedVariableRule to suppress "undefined variable" errors + * when the full variable set is not known. + */ public function canAnyVariableExist(): bool; /** + * Returns the names of all variables that are definitely defined in this scope. + * + * Only includes variables with TrinaryLogic::Yes certainty. + * * @return array */ public function getDefinedVariables(): array; /** + * Returns the names of variables that might be defined in this scope. + * + * Only includes variables with TrinaryLogic::Maybe certainty — variables + * that are defined in some code paths but not others (e.g. defined inside + * an if-branch but not in the else-branch). + * * @return array */ public function getMaybeDefinedVariables(): array; + /** + * Returns whether a global constant with the given name exists. + * + * Checks both PHP built-in constants and user-defined constants. + * The Name node is resolved according to the current namespace. + */ public function hasConstant(Name $name): bool; - /** @deprecated Use getInstancePropertyReflection or getStaticPropertyReflection instead */ + /** + * @deprecated Use getInstancePropertyReflection or getStaticPropertyReflection instead + */ public function getPropertyReflection(Type $typeWithProperty, string $propertyName): ?ExtendedPropertyReflection; + /** + * Returns the reflection for an instance property on the given type, or null. + * + * Resolves the property through the type system, handling union types, + * intersection types, and visibility checks. Returns null if the property + * doesn't exist or is not accessible from the current scope. + */ public function getInstancePropertyReflection(Type $typeWithProperty, string $propertyName): ?ExtendedPropertyReflection; + /** + * Returns the reflection for a static property on the given type, or null. + * + * Like getInstancePropertyReflection() but for static properties (Foo::$bar). + * Returns null if the property doesn't exist or is not accessible. + */ public function getStaticPropertyReflection(Type $typeWithProperty, string $propertyName): ?ExtendedPropertyReflection; + /** + * Returns the reflection for a method on the given type, or null. + * + * Resolves the method through the type system, handling union types, + * intersection types, and visibility checks. Returns null if the method + * doesn't exist or is not accessible from the current scope. + */ public function getMethodReflection(Type $typeWithMethod, string $methodName): ?ExtendedMethodReflection; + /** + * Returns the reflection for a class constant on the given type, or null. + * + * Resolves the constant through the type system. Returns null if the + * constant doesn't exist or is not accessible from the current scope. + */ public function getConstantReflection(Type $typeWithConstant, string $constantName): ?ClassConstantReflection; + /** + * Returns the explicitly configured type for a global constant, if any. + * + * Checks the PHPStan configuration for user-specified constant type overrides + * (via the `constants` configuration option). Falls back to the given $constantType + * if no override is configured. + */ public function getConstantExplicitTypeFromConfig(string $constantName, Type $constantType): Type; + /** + * Returns the key type of an iterable type. + * + * Unlike calling $iteratee->getIterableKeyType() directly, this method + * goes through the Scope to properly resolve template types and handle + * scope-specific type refinements. + */ public function getIterableKeyType(Type $iteratee): Type; + /** + * Returns the value type of an iterable type. + * + * Unlike calling $iteratee->getIterableValueType() directly, this method + * goes through the Scope to properly resolve template types and handle + * scope-specific type refinements. + */ public function getIterableValueType(Type $iteratee): Type; /** + * Returns whether the current analysis context is inside an anonymous function + * (closure or arrow function). + * + * When true, both getAnonymousFunctionReflection() and + * getAnonymousFunctionReturnType() are guaranteed to return non-null. + * * @phpstan-assert-if-true !null $this->getAnonymousFunctionReflection() * @phpstan-assert-if-true !null $this->getAnonymousFunctionReturnType() */ public function isInAnonymousFunction(): bool; + /** + * Returns the ClosureType reflection of the current anonymous function, or null. + * + * Only non-null when isInAnonymousFunction() is true. The ClosureType + * contains the closure's parameter types, return type, and template types. + */ public function getAnonymousFunctionReflection(): ?ClosureType; + /** + * Returns the declared return type of the current anonymous function, or null. + * + * Only non-null when isInAnonymousFunction() is true. Used by return type + * rules to validate that the closure returns the correct type. + */ public function getAnonymousFunctionReturnType(): ?Type; + /** + * Returns the type of a PHP expression at this point in the analysis. + * + * This is the most important method on Scope. It evaluates the type of any + * expression AST node, taking into account all type information available at + * the current analysis position — variable assignments, type narrowing from + * conditions, PHPDoc annotations, and more. + * + * The returned type reflects PHPDoc-enhanced types. Use getNativeType() to get + * the type based only on PHP's native type system (typehints, assignments). + * + * Note: This method may defer evaluation until the expression's analysis is + * complete (see getScopeType() for cases where immediate evaluation is needed). + */ public function getType(Expr $node): Type; + /** + * Returns the native PHP type of an expression, ignoring PHPDoc annotations. + * + * Unlike getType() which includes PHPDoc-enhanced type information (like + * generic types, more specific return types from @return tags, etc.), this + * method returns only what PHP's native type system knows. + * + * Used when you need to distinguish between what PHP enforces at runtime + * vs. what PHPDoc promises at the documentation level. + */ public function getNativeType(Expr $expr): Type; + /** + * Like getType(), but preserves the void type for function/method calls. + * + * Normally, getType() replaces void return types with null (since void + * functions effectively return null). This method keeps the void type, + * which is needed by return type rules that must distinguish between + * "returns null" and "returns void". + */ public function getKeepVoidType(Expr $node): Type; /** - * The `getType()` method along with FNSR enabled - * waits for the Expr analysis to be completed - * in order to evaluate the type at the right place in the code. + * Returns the type of an expression using the current scope state directly. * - * This prevents tricky bugs when reasoning about code like - * `doFoo($a = 1, $a)`. + * Unlike getType(), which may defer evaluation until the expression's + * full analysis is complete (to handle cases like `doFoo($a = 1, $a)` + * where argument evaluation order matters), this method uses the scope's + * current state immediately. * - * Sometimes this is counter-productive because we actually want - * to use the current Scope object contents to resolve the Expr type. - * - * In these cases use `getScopeType()`. + * Use this when you intentionally want the type as it exists in the + * current scope snapshot, not the final resolved type. */ public function getScopeType(Expr $expr): Type; + /** + * Like getScopeType(), but returns the native PHP type only. + * + * Combines the immediate-evaluation behavior of getScopeType() with + * the PHPDoc-ignoring behavior of getNativeType(). + */ public function getScopeNativeType(Expr $expr): Type; + /** + * Resolves a Name AST node to a fully qualified class name string. + * + * Handles special names: `self` and `static` resolve to the current class, + * `parent` resolves to the parent class. Other names are returned as-is + * (they should already be fully qualified by the PHP parser's name resolver). + * + * Inside a Closure::bind() context, `self`/`static` resolve to the bound class. + */ public function resolveName(Name $name): string; + /** + * Resolves a Name AST node to a TypeWithClassName. + * + * Unlike resolveName() which returns a plain string, this returns a proper + * Type object that preserves late-static-binding information: + * - `static` returns a StaticType (preserves LSB in subclasses) + * - `self` returns a ThisType when inside the same class hierarchy + * - Other names return an ObjectType + */ public function resolveTypeByName(Name $name): TypeWithClassName; /** + * Returns the PHPStan Type representing a given PHP value. + * + * Converts runtime PHP values to their corresponding constant types: + * integers become ConstantIntegerType, strings become ConstantStringType, + * arrays become ConstantArrayType (if small enough), etc. + * * @param mixed $value */ public function getTypeFromValue($value): Type; + /** + * Returns whether an expression has a tracked type in this scope. + * + * Returns TrinaryLogic::Yes if the expression's type is definitely known, + * TrinaryLogic::Maybe if it might be known, and TrinaryLogic::No if there + * is no type information for it. + * + * This checks the scope's expression type map without computing the type + * (unlike getType() which always computes a type). + */ public function hasExpressionType(Expr $node): TrinaryLogic; + /** + * Returns whether the given class name is being checked inside a + * class_exists(), interface_exists(), or trait_exists() call. + * + * When true, rules should suppress "class not found" errors because + * the code is explicitly checking for the class's existence. + */ public function isInClassExists(string $className): bool; + /** + * Returns whether the given function name is being checked inside a + * function_exists() call. + * + * When true, rules should suppress "function not found" errors because + * the code is explicitly checking for the function's existence. + */ public function isInFunctionExists(string $functionName): bool; + /** + * Returns whether the current analysis context is inside a Closure::bind() + * or Closure::bindTo() call. + * + * When true, the closure's $this and self/static may refer to a different + * class than the one where the closure was defined. + */ public function isInClosureBind(): bool; - /** @return list */ + /** + * Returns the stack of function/method calls that are currently being analysed. + * + * When analysing arguments of a function call, this returns the chain of + * enclosing calls. Used by extensions that need to know the calling context, + * such as type-specifying extensions for functions like class_exists(). + * + * @return list + */ public function getFunctionCallStack(): array; - /** @return list */ + /** + * Like getFunctionCallStack(), but also includes the parameter being passed to. + * + * Each entry is a tuple of the function/method reflection and the parameter + * reflection for the argument position being analysed (or null if unknown). + * + * @return list + */ public function getFunctionCallStackWithParameters(): array; + /** + * Returns whether a function parameter has a default value of null. + * + * Checks the parameter's default value AST node to determine if + * `= null` was specified. Used by function definition checks. + */ public function isParameterValueNullable(Param $parameter): bool; /** + * Resolves a type AST node (from a parameter/return type declaration) to a Type. + * + * Handles named types, identifier types (int, string, etc.), union types, + * intersection types, and nullable types. The $isNullable flag adds null + * to the type, and $isVariadic wraps the type in an array. + * * @param Node\Name|Node\Identifier|Node\ComplexType|null $type */ public function getFunctionType($type, bool $isNullable, bool $isVariadic): Type; + /** + * Returns whether the given expression is currently being assigned to. + * + * Returns true during the analysis of the right-hand side of an assignment + * to this expression. For example, when analysing `$a = expr`, this returns + * true for the $a variable during the analysis of `expr`. + * + * Used to prevent infinite recursion when resolving types during assignment. + */ public function isInExpressionAssign(Expr $expr): bool; + /** + * Returns whether accessing the given expression in an undefined state is allowed. + * + * Returns true when the expression is on the left-hand side of an assignment + * or in similar contexts where it's valid for the expression to be undefined + * (e.g. `$a['key'] = value` where $a['key'] doesn't need to exist yet). + */ public function isUndefinedExpressionAllowed(Expr $expr): bool; + /** + * Returns a new Scope with types narrowed by assuming the expression is truthy. + * + * Given an expression like `$x instanceof Foo`, returns a scope where + * $x is known to be of type Foo. This is the scope used inside the + * if-branch of `if ($x instanceof Foo)`. + * + * Uses the TypeSpecifier internally to determine type narrowing. + */ public function filterByTruthyValue(Expr $expr): self; + /** + * Returns a new Scope with types narrowed by assuming the expression is falsy. + * + * The opposite of filterByTruthyValue(). Given `$x instanceof Foo`, returns + * a scope where $x is known NOT to be of type Foo. This is the scope used + * in the else-branch of `if ($x instanceof Foo)`. + */ public function filterByFalseyValue(Expr $expr): self; + /** + * Returns whether the current statement is a "first-level" statement. + * + * A first-level statement is one that is directly inside a function/method + * body, not nested inside control structures like if/else, loops, or + * try/catch. Used to determine whether certain checks should be more + * or less strict. + */ public function isInFirstLevelStatement(): bool; + /** + * Returns the PHP version(s) being analysed against. + * + * Returns a PhpVersions object that can represent a range of PHP versions + * (when the exact version is not known). Use its methods like + * supportsEnums(), supportsReadonlyProperties(), etc. to check for + * version-specific features. + */ public function getPhpVersion(): PhpVersions; /** @internal */ diff --git a/src/Reflection/ClassMemberAccessAnswerer.php b/src/Reflection/ClassMemberAccessAnswerer.php index 9eeb979821..ca69e6ab4e 100644 --- a/src/Reflection/ClassMemberAccessAnswerer.php +++ b/src/Reflection/ClassMemberAccessAnswerer.php @@ -2,15 +2,40 @@ namespace PHPStan\Reflection; -/** @api */ +/** + * Answers questions about visibility and access rights for class members + * (properties, methods, constants) from the current analysis context. + * + * This interface is the Scope's role as an access control checker. It is + * passed as a parameter to Type methods like getMethod(), getProperty(), + * getConstant(), etc., so the type system can enforce visibility rules + * (public/protected/private) based on where the access occurs. + * + * The primary implementation is MutatingScope. A secondary implementation, + * OutOfClassScope, is used when accessing members from outside any class. + * + * @api + */ interface ClassMemberAccessAnswerer { /** + * Returns whether the current analysis context is inside a class. + * + * When true, getClassReflection() is guaranteed to return non-null. + * Used to determine if protected/private members are accessible. + * * @phpstan-assert-if-true !null $this->getClassReflection() */ public function isInClass(): bool; + /** + * Returns the ClassReflection of the class the current code is in, + * or null if not inside a class. + * + * Used together with property/method/constant reflections to determine + * whether the current context has access to protected or private members. + */ public function getClassReflection(): ?ClassReflection; /** @@ -18,12 +43,44 @@ public function getClassReflection(): ?ClassReflection; */ public function canAccessProperty(PropertyReflection $propertyReflection): bool; + /** + * Returns whether the current context can read the given property. + * + * Checks visibility rules: public properties are always readable, + * protected properties are readable from the same class or subclasses, + * and private properties are only readable from the declaring class. + * + * Also accounts for PHP 8.4 asymmetric visibility where a property + * may have different read and write visibility. + */ public function canReadProperty(ExtendedPropertyReflection $propertyReflection): bool; + /** + * Returns whether the current context can write to the given property. + * + * Like canReadProperty(), but checks write visibility instead. + * With PHP 8.4 asymmetric visibility, a property like + * `public private(set) string $name` is publicly readable but only + * privately writable. + */ public function canWriteProperty(ExtendedPropertyReflection $propertyReflection): bool; + /** + * Returns whether the current context can call the given method. + * + * Checks visibility rules: public methods are always callable, + * protected methods are callable from the same class or subclasses, + * and private methods are only callable from the declaring class. + */ public function canCallMethod(MethodReflection $methodReflection): bool; + /** + * Returns whether the current context can access the given class constant. + * + * Checks visibility rules: public constants are always accessible, + * protected constants are accessible from the same class or subclasses, + * and private constants are only accessible from the declaring class. + */ public function canAccessConstant(ClassConstantReflection $constantReflection): bool; } diff --git a/src/Reflection/NamespaceAnswerer.php b/src/Reflection/NamespaceAnswerer.php index 4e908a6d8e..a4e46cef05 100644 --- a/src/Reflection/NamespaceAnswerer.php +++ b/src/Reflection/NamespaceAnswerer.php @@ -2,11 +2,20 @@ namespace PHPStan\Reflection; -/** @api */ +/** + * Provides the current namespace context. + * + * Used by the type resolver and PHPDoc parser to resolve relative class names + * against the current namespace and use statements. + * + * @api + */ interface NamespaceAnswerer { /** + * Returns the current namespace, or null if in the global namespace. + * * @return non-empty-string|null */ public function getNamespace(): ?string; From fe10c5bc347417ea2766cc9dfcad07c06250423c Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 10 Feb 2026 19:12:57 +0000 Subject: [PATCH 3/9] Add comprehensive PHPDoc documentation to all types reachable from Type and Scope interfaces Document all interfaces and classes reachable through parameter types and return types of the previously documented Type and Scope interfaces. This covers 31 files across the type system, reflection layer, and supporting infrastructure: - Core result types: TrinaryLogic, AcceptsResult, IsSuperTypeOfResult - Type system: VerbosityLevel, GeneralizePrecision, ConstantScalarType, TypeWithClassName - Reflection: ClassMemberReflection, PropertyReflection, ExtendedPropertyReflection, MethodReflection, ExtendedMethodReflection, FunctionReflection, ParameterReflection, ClassConstantReflection, ConstantReflection, ParametersAcceptor, ExtendedParametersAcceptor, PassedByReference, Assertions, AttributeReflection - Callables: CallableParametersAcceptor, SimpleThrowPoint, SimpleImpurePoint - Generics: TemplateTypeMap, TemplateTypeReference, TemplateTypeVariance - Prototype resolution: UnresolvedPropertyPrototypeReflection, UnresolvedMethodPrototypeReflection - PHP version: PhpVersion, PhpVersions https://claude.ai/code/session_01Htkqstd1mz1dbevHT6Y437 --- src/Php/PhpVersion.php | 24 +++- src/Php/PhpVersions.php | 20 ++++ src/Reflection/Assertions.php | 32 +++++ src/Reflection/AttributeReflection.php | 15 ++- .../Callables/CallableParametersAcceptor.php | 38 +++++- .../Callables/SimpleImpurePoint.php | 30 ++++- src/Reflection/Callables/SimpleThrowPoint.php | 25 ++++ src/Reflection/ClassConstantReflection.php | 33 +++++- src/Reflection/ClassMemberReflection.php | 27 ++++- src/Reflection/ConstantReflection.php | 18 ++- src/Reflection/ExtendedMethodReflection.php | 68 ++++++++--- src/Reflection/ExtendedParametersAcceptor.php | 27 ++++- src/Reflection/ExtendedPropertyReflection.php | 62 ++++++++-- src/Reflection/FunctionReflection.php | 64 ++++++++-- src/Reflection/MethodReflection.php | 44 ++++++- src/Reflection/ParameterReflection.php | 29 ++++- src/Reflection/ParametersAcceptor.php | 38 +++++- src/Reflection/PassedByReference.php | 36 ++++++ src/Reflection/PropertyReflection.php | 39 +++++- .../UnresolvedMethodPrototypeReflection.php | 31 +++++ .../UnresolvedPropertyPrototypeReflection.php | 33 ++++++ src/TrinaryLogic.php | 111 ++++++++++++++++++ src/Type/AcceptsResult.php | 48 +++++++- src/Type/ConstantScalarType.php | 24 +++- src/Type/GeneralizePrecision.php | 38 +++++- src/Type/Generic/TemplateTypeMap.php | 51 +++++++- src/Type/Generic/TemplateTypeReference.php | 21 ++++ src/Type/Generic/TemplateTypeVariance.php | 38 ++++++ src/Type/IsSuperTypeOfResult.php | 59 +++++++++- src/Type/TypeWithClassName.php | 31 ++++- src/Type/VerbosityLevel.php | 72 +++++++++++- 31 files changed, 1163 insertions(+), 63 deletions(-) diff --git a/src/Php/PhpVersion.php b/src/Php/PhpVersion.php index 6a555dff73..dc4c1c0463 100644 --- a/src/Php/PhpVersion.php +++ b/src/Php/PhpVersion.php @@ -6,21 +6,43 @@ use function floor; /** + * Represents a specific PHP version for version-dependent analysis behavior. + * + * PHPStan uses this to gate behavior based on PHP version — e.g. whether enums are + * supported (8.1+), whether property hooks exist (8.4+), whether non-standard casts + * are deprecated (8.5+), etc. + * + * The version is stored as PHP_VERSION_ID format (e.g. 80100 for PHP 8.1.0). + * The source indicates where the version came from: runtime, phpstan.neon config, + * or composer.json platform config. + * + * This class provides numerous `supports*()` and `deprecates*()` methods that rules + * and extensions use to conditionally apply version-specific analysis. Extension + * developers can access it via `Scope::getPhpVersion()` (which returns PhpVersions, + * a range-aware wrapper) or by injecting PhpVersion directly. + * * @api */ #[AutowiredService(factory: '@PHPStan\Php\PhpVersionFactory::create')] final class PhpVersion { + /** Version was detected from the running PHP runtime. */ public const SOURCE_RUNTIME = 1; + + /** Version was set in phpstan.neon configuration. */ public const SOURCE_CONFIG = 2; + + /** Version was read from config.platform.php in composer.json. */ public const SOURCE_COMPOSER_PLATFORM_PHP = 3; + + /** Version source is not known. */ public const SOURCE_UNKNOWN = 4; /** * @api * - * @param self::SOURCE_* $source + * @param self::SOURCE_* $source Where this version number came from */ public function __construct(private int $versionId, private int $source = self::SOURCE_UNKNOWN) { diff --git a/src/Php/PhpVersions.php b/src/Php/PhpVersions.php index 993ee23acf..8a61abd56d 100644 --- a/src/Php/PhpVersions.php +++ b/src/Php/PhpVersions.php @@ -7,17 +7,37 @@ use PHPStan\Type\Type; /** + * Range-aware PHP version check that handles version uncertainty. + * + * Unlike PhpVersion (which represents a single known version), PhpVersions wraps + * a Type representing the possible PHP versions. When the exact version is known, + * queries return Yes/No. When a range of versions is possible (e.g. `int<80000, 80400>`), + * queries return Maybe. + * + * This is the return type of Scope::getPhpVersion(). Rules and extensions use it + * to query version-dependent features: + * + * $scope->getPhpVersion()->supportsNamedArguments() // TrinaryLogic + * + * The underlying type is an integer (range) type representing PHP_VERSION_ID values. + * * @api */ final class PhpVersions { + /** + * @param Type $phpVersions An integer type representing the possible PHP_VERSION_ID values + */ public function __construct( private Type $phpVersions, ) { } + /** + * Returns the underlying type representing the PHP version range. + */ public function getType(): Type { return $this->phpVersions; diff --git a/src/Reflection/Assertions.php b/src/Reflection/Assertions.php index a1f7ebfa6d..4d8f8de77f 100644 --- a/src/Reflection/Assertions.php +++ b/src/Reflection/Assertions.php @@ -11,6 +11,19 @@ use function count; /** + * Collection of @phpstan-assert annotations on a function or method. + * + * PHPStan supports type assertions via PHPDoc annotations: + * - `@phpstan-assert Type $param` — narrows the parameter type unconditionally + * - `@phpstan-assert-if-true Type $param` — narrows when the method returns true + * - `@phpstan-assert-if-false Type $param` — narrows when the method returns false + * + * This class collects all such assertions and provides methods to retrieve them + * by condition type. It also handles negation: an `@phpstan-assert-if-true` assertion + * is automatically negated and included in the `getAssertsIfFalse()` result. + * + * Returned by ExtendedMethodReflection::getAsserts() and FunctionReflection::getAsserts(). + * * @api */ final class Assertions @@ -26,6 +39,8 @@ private function __construct(private array $asserts) } /** + * Returns all assert tags regardless of condition. + * * @return AssertTag[] */ public function getAll(): array @@ -34,6 +49,10 @@ public function getAll(): array } /** + * Returns unconditional assertions (@phpstan-assert). + * + * These narrow parameter types regardless of the method's return value. + * * @return AssertTag[] */ public function getAsserts(): array @@ -42,6 +61,10 @@ public function getAsserts(): array } /** + * Returns assertions that apply when the method returns true. + * + * Includes `@phpstan-assert-if-true` tags and negated `@phpstan-assert-if-false` tags. + * * @return AssertTag[] */ public function getAssertsIfTrue(): array @@ -56,6 +79,10 @@ public function getAssertsIfTrue(): array } /** + * Returns assertions that apply when the method returns false. + * + * Includes `@phpstan-assert-if-false` tags and negated `@phpstan-assert-if-true` tags. + * * @return AssertTag[] */ public function getAssertsIfFalse(): array @@ -70,6 +97,11 @@ public function getAssertsIfFalse(): array } /** + * Transforms all assertion types using the given callback. + * + * Used when resolving template types — the assertion types need to be + * substituted with concrete type arguments. + * * @param callable(Type): Type $callable */ public function mapTypes(callable $callable): self diff --git a/src/Reflection/AttributeReflection.php b/src/Reflection/AttributeReflection.php index 74d6874223..768732d565 100644 --- a/src/Reflection/AttributeReflection.php +++ b/src/Reflection/AttributeReflection.php @@ -5,24 +5,37 @@ use PHPStan\Type\Type; /** + * Reflection for a PHP attribute (PHP 8.0+). + * + * Represents a single attribute applied to a class, method, property, function, + * parameter, or constant. Provides the attribute's class name and the types of + * its constructor arguments. + * + * Returned by the getAttributes() method on ExtendedMethodReflection, + * ExtendedPropertyReflection, FunctionReflection, ClassConstantReflection, etc. + * * @api */ final class AttributeReflection { /** - * @param array $argumentTypes + * @param string $name The fully qualified attribute class name + * @param array $argumentTypes Argument types keyed by parameter name */ public function __construct(private string $name, private array $argumentTypes) { } + /** Returns the fully qualified attribute class name. */ public function getName(): string { return $this->name; } /** + * Returns the types of the attribute's constructor arguments, keyed by parameter name. + * * @return array */ public function getArgumentTypes(): array diff --git a/src/Reflection/Callables/CallableParametersAcceptor.php b/src/Reflection/Callables/CallableParametersAcceptor.php index 11a22e40cf..1150dae562 100644 --- a/src/Reflection/Callables/CallableParametersAcceptor.php +++ b/src/Reflection/Callables/CallableParametersAcceptor.php @@ -7,39 +7,71 @@ use PHPStan\TrinaryLogic; /** + * A ParametersAcceptor for callable types (closures, first-class callables). + * + * Extends ParametersAcceptor with information about side effects, exceptions, + * and other runtime behavior of callable values. This is what PHPStan knows + * about a closure or callable when it's passed as a parameter or stored in a variable. + * + * Implemented by ClosureType and used as the return type of + * Type::getCallableParametersAcceptors(). + * + * Provides: + * - Throw points (what exceptions the callable may throw) + * - Impure points (what side effects the callable may have) + * - Purity information + * - Variables captured from outer scope (used variables) + * - Expressions that are invalidated by calling this callable + * * @api */ interface CallableParametersAcceptor extends ParametersAcceptor { /** + * Returns the points where this callable may throw exceptions. + * * @return SimpleThrowPoint[] */ public function getThrowPoints(): array; + /** Whether this callable is known to be pure (no side effects). */ public function isPure(): TrinaryLogic; + /** Whether this callable accepts named arguments. */ public function acceptsNamedArguments(): TrinaryLogic; /** + * Returns the points where this callable may have side effects. + * * @return SimpleImpurePoint[] */ public function getImpurePoints(): array; /** + * Returns expressions that become invalid after this callable is invoked. + * + * Used to track when calling a closure invalidates cached type information + * for variables it captures by reference. + * * @return InvalidateExprNode[] */ public function getInvalidateExpressions(): array; /** + * Returns the names of outer-scope variables captured by this callable. + * + * Relevant for `use ($var)` in closures. + * * @return string[] */ public function getUsedVariables(): array; /** - * Has the #[\NoDiscard] attribute - on PHP 8.5+ if the function's return - * value is unused at runtime a warning is emitted, PHPStan will emit the - * warning during analysis and on older PHP versions too + * Whether this callable has the #[\NoDiscard] attribute. + * + * On PHP 8.5+ if the return value is unused at runtime, a warning is emitted. + * PHPStan reports this during analysis regardless of PHP version. */ public function mustUseReturnValue(): TrinaryLogic; diff --git a/src/Reflection/Callables/SimpleImpurePoint.php b/src/Reflection/Callables/SimpleImpurePoint.php index 8d6ed95b4c..07dc807628 100644 --- a/src/Reflection/Callables/SimpleImpurePoint.php +++ b/src/Reflection/Callables/SimpleImpurePoint.php @@ -12,6 +12,17 @@ use function sprintf; /** + * Represents a point where a callable may have side effects (impure behavior). + * + * Used by CallableParametersAcceptor::getImpurePoints() to describe what side effects + * a closure or callable value may have. Each impure point has an identifier (e.g. + * "functionCall", "methodCall"), a human-readable description, and a certainty flag. + * + * PHPStan uses impure points to: + * - Detect calls to impure functions inside @phpstan-pure contexts + * - Report unused return values of pure functions (expr.resultUnused) + * - Determine whether expressions have side effects + * * @phpstan-import-type ImpurePointIdentifier from ImpurePoint */ final class SimpleImpurePoint @@ -25,7 +36,9 @@ final class SimpleImpurePoint ]; /** - * @param ImpurePointIdentifier $identifier + * @param ImpurePointIdentifier $identifier Category of the side effect + * @param string $description Human-readable description of the impure action + * @param bool $certain Whether the side effect is certain (true) or possible (false) */ public function __construct( private string $identifier, @@ -36,6 +49,12 @@ public function __construct( } /** + * Creates a SimpleImpurePoint from a function/method and its selected variant. + * + * Returns null if the function is known to be pure (no side effects). + * Handles special cases like print_r() where a parameter can flip the + * function between impure (prints to output) and pure (returns string). + * * @param Arg[] $args */ public static function createFromVariant(FunctionReflection|ExtendedMethodReflection $function, ?ParametersAcceptor $variant, ?Scope $scope = null, array $args = []): ?self @@ -104,6 +123,8 @@ public static function createFromVariant(FunctionReflection|ExtendedMethodReflec } /** + * Returns the category identifier for this side effect (e.g. "functionCall", "methodCall"). + * * @return ImpurePointIdentifier */ public function getIdentifier(): string @@ -111,11 +132,18 @@ public function getIdentifier(): string return $this->identifier; } + /** Returns a human-readable description of the impure action. */ public function getDescription(): string { return $this->description; } + /** + * Whether the side effect is certain (vs. merely possible). + * + * Certain when the function is known to be impure (e.g. void return, or + * explicitly marked @phpstan-impure). Uncertain when purity is unknown. + */ public function isCertain(): bool { return $this->certain; diff --git a/src/Reflection/Callables/SimpleThrowPoint.php b/src/Reflection/Callables/SimpleThrowPoint.php index 5cde43a155..37933801dc 100644 --- a/src/Reflection/Callables/SimpleThrowPoint.php +++ b/src/Reflection/Callables/SimpleThrowPoint.php @@ -6,6 +6,17 @@ use PHPStan\Type\Type; use Throwable; +/** + * Represents a point where a callable may throw an exception. + * + * Used by CallableParametersAcceptor::getThrowPoints() to describe what exceptions + * a closure or callable value may throw. This is a simplified version of the full + * ThrowPoint used in the analyser — it carries just the exception type, whether the + * throw was explicitly declared (@throws), and whether it could be any Throwable. + * + * Explicit throw points come from @throws annotations. Implicit throw points represent + * the possibility that any function call could throw. + */ final class SimpleThrowPoint { @@ -17,26 +28,40 @@ private function __construct( { } + /** + * Creates a throw point from an explicit @throws annotation. + * + * @param Type $type The exception type declared in @throws + * @param bool $canContainAnyThrowable Whether the type could match any Throwable + */ public static function createExplicit(Type $type, bool $canContainAnyThrowable): self { return new self($type, true, $canContainAnyThrowable); } + /** + * Creates an implicit throw point that could throw any Throwable. + * + * Used when no @throws annotation is present and the callable is not marked pure. + */ public static function createImplicit(): self { return new self(new ObjectType(Throwable::class), false, true); } + /** Returns the type of exceptions that may be thrown. */ public function getType(): Type { return $this->type; } + /** Whether this throw point comes from an explicit @throws annotation. */ public function isExplicit(): bool { return $this->explicit; } + /** Whether the exception type is broad enough to include any Throwable. */ public function canContainAnyThrowable(): bool { return $this->canContainAnyThrowable; diff --git a/src/Reflection/ClassConstantReflection.php b/src/Reflection/ClassConstantReflection.php index dc30920809..7151e8481e 100644 --- a/src/Reflection/ClassConstantReflection.php +++ b/src/Reflection/ClassConstantReflection.php @@ -6,22 +6,53 @@ use PHPStan\PhpDoc\ResolvedPhpDocBlock; use PHPStan\Type\Type; -/** @api */ +/** + * Reflection for a class constant. + * + * Combines ClassMemberReflection (declaring class, visibility) with + * ConstantReflection (name, value type, deprecation) and adds class-constant-specific + * features: the value expression AST, final modifier, and separate PHPDoc/native types. + * + * PHP 8.3+ supports native type declarations on class constants, so this interface + * provides both PHPDoc and native type accessors (similar to property reflection). + * + * This is the return type of Type::getConstant() and Scope::getConstantReflection(). + * + * @api + */ interface ClassConstantReflection extends ClassMemberReflection, ConstantReflection { + /** + * Returns the AST expression for this constant's value. + * + * This is the raw expression from the parser, useful for rules that + * need to inspect the constant's definition. + */ public function getValueExpr(): Expr; + /** Whether this constant is declared final (PHP 8.1+). */ public function isFinal(): bool; + /** Whether this constant has a PHPDoc @var type. */ public function hasPhpDocType(): bool; + /** + * Returns the PHPDoc @var type for this constant, or null if none. + */ public function getPhpDocType(): ?Type; + /** Whether this constant has a native PHP type declaration (PHP 8.3+). */ public function hasNativeType(): bool; + /** + * Returns the native PHP type declaration, or null if none. + */ public function getNativeType(): ?Type; + /** + * Returns the resolved PHPDoc block for this constant, or null if none exists. + */ public function getResolvedPhpDoc(): ?ResolvedPhpDocBlock; } diff --git a/src/Reflection/ClassMemberReflection.php b/src/Reflection/ClassMemberReflection.php index da2274a063..8d83edc23c 100644 --- a/src/Reflection/ClassMemberReflection.php +++ b/src/Reflection/ClassMemberReflection.php @@ -2,18 +2,43 @@ namespace PHPStan\Reflection; -/** @api */ +/** + * Base interface for all class members: properties, methods, and constants. + * + * Provides common metadata shared by all class members — their declaring class, + * visibility (public/private/protected), static-ness, and raw PHPDoc comment. + * + * This is the parent interface for PropertyReflection, MethodReflection, and + * (via ConstantReflection) ClassConstantReflection. Extension developers typically + * work with the more specific child interfaces. + * + * @api + */ interface ClassMemberReflection { + /** + * Returns the class where this member is declared. + * + * For inherited members, this returns the original declaring class, + * not the class where the member was accessed. + */ public function getDeclaringClass(): ClassReflection; + /** Whether this member is declared static. */ public function isStatic(): bool; + /** Whether this member has private visibility. */ public function isPrivate(): bool; + /** Whether this member has public visibility. */ public function isPublic(): bool; + /** + * Returns the raw PHPDoc comment for this member, or null if none exists. + * + * This is the unparsed comment string including the /** delimiters. + */ public function getDocComment(): ?string; } diff --git a/src/Reflection/ConstantReflection.php b/src/Reflection/ConstantReflection.php index 9679c72542..64cf783419 100644 --- a/src/Reflection/ConstantReflection.php +++ b/src/Reflection/ConstantReflection.php @@ -5,23 +5,39 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\Type; -/** @api */ +/** + * Reflection for a constant (class constant or global constant). + * + * Provides the constant's name, resolved value type, deprecation status, and + * metadata. This is the base interface — ClassConstantReflection extends it + * with class-specific features (declaring class, value expression, native type). + * + * @api + */ interface ConstantReflection { + /** Returns the constant name. */ public function getName(): string; + /** Returns the type of this constant's value. */ public function getValueType(): Type; + /** Whether this constant is marked as deprecated. */ public function isDeprecated(): TrinaryLogic; + /** Returns the deprecation message, or null if not deprecated. */ public function getDeprecatedDescription(): ?string; + /** Whether this constant is marked as @internal. */ public function isInternal(): TrinaryLogic; + /** Returns the file path where this constant is defined, or null for built-ins. */ public function getFileName(): ?string; /** + * Returns PHP attributes on this constant. + * * @return list */ public function getAttributes(): array; diff --git a/src/Reflection/ExtendedMethodReflection.php b/src/Reflection/ExtendedMethodReflection.php index a13cd47c1f..97e4581500 100644 --- a/src/Reflection/ExtendedMethodReflection.php +++ b/src/Reflection/ExtendedMethodReflection.php @@ -7,16 +7,23 @@ use PHPStan\Type\Type; /** - * The purpose of this interface is to be able to - * answer more questions about methods - * without breaking backward compatibility - * with existing MethodsClassReflectionExtension. + * Extended method reflection with additional metadata beyond MethodReflection. * - * Developers are meant to only implement MethodReflection - * and its methods in their code. + * This interface exists to allow PHPStan to add new method query methods in minor + * versions without breaking existing MethodsClassReflectionExtension implementations. + * Extension developers should implement MethodReflection, not this interface — PHPStan + * wraps MethodReflection implementations to provide ExtendedMethodReflection. * - * New methods on ExtendedMethodReflection will be added - * in minor versions. + * Provides access to: + * - Extended parameter signatures (ExtendedParametersAcceptor with PHPDoc/native types) + * - Named argument variants (different signatures when using named arguments) + * - Type assertions (@phpstan-assert annotations) + * - Self-out types (@phpstan-self-out for fluent interfaces) + * - Purity information (@phpstan-pure/@phpstan-impure) + * - PHP attributes (including #[\NoDiscard]) + * - Resolved PHPDoc block + * + * This is the return type of Type::getMethod() and Scope::getMethodReflection(). * * @api */ @@ -24,55 +31,86 @@ interface ExtendedMethodReflection extends MethodReflection { /** + * Returns extended parameter/return type signatures with PHPDoc and native types. + * * @return list */ public function getVariants(): array; /** + * Shortcut for methods with exactly one variant. + * * @internal */ public function getOnlyVariant(): ExtendedParametersAcceptor; /** + * Returns alternative signatures used when the method is called with named arguments. + * + * Some built-in functions have different behavior with named arguments. + * Returns null if the named argument variants are the same as regular variants. + * * @return list|null */ public function getNamedArgumentsVariants(): ?array; + /** Whether this method accepts named arguments (PHP 8.0+). */ public function acceptsNamedArguments(): TrinaryLogic; + /** + * Returns type assertions declared via @phpstan-assert annotations. + * + * These narrow parameter or property types after the method call, + * similar to how is_string() narrows to string. + */ public function getAsserts(): Assertions; + /** + * Returns the @phpstan-self-out type, if declared. + * + * Used for fluent interfaces where calling a method changes the generic + * type parameters of $this (e.g. a builder pattern). + */ public function getSelfOutType(): ?Type; + /** Whether this method returns by reference (&). */ public function returnsByReference(): TrinaryLogic; + /** Whether this method has the `final` keyword explicitly. */ public function isFinalByKeyword(): TrinaryLogic; + /** Whether this method is abstract. */ public function isAbstract(): TrinaryLogic|bool; + /** Whether this method is a PHP built-in (not defined in userland code). */ public function isBuiltin(): TrinaryLogic|bool; /** - * This indicates whether the method has phpstan-pure - * or phpstan-impure annotation above it. + * Whether this method has a @phpstan-pure or @phpstan-impure annotation. * - * In most cases asking hasSideEffects() is much more practical - * as it also accounts for void return type (method being always impure). + * In most cases hasSideEffects() is more practical as it also accounts + * for void return type (methods returning void are always impure). */ public function isPure(): TrinaryLogic; /** + * Returns PHP attributes on this method. + * * @return list */ public function getAttributes(): array; /** - * Has the #[\NoDiscard] attribute - on PHP 8.5+ if the function's return - * value is unused at runtime a warning is emitted, PHPStan will emit the - * warning during analysis and on older PHP versions too + * Whether this method has the #[\NoDiscard] attribute. + * + * On PHP 8.5+ if the return value is unused at runtime, a warning is emitted. + * PHPStan reports this during analysis regardless of PHP version. */ public function mustUseReturnValue(): TrinaryLogic; + /** + * Returns the resolved PHPDoc block for this method, or null if none exists. + */ public function getResolvedPhpDoc(): ?ResolvedPhpDocBlock; } diff --git a/src/Reflection/ExtendedParametersAcceptor.php b/src/Reflection/ExtendedParametersAcceptor.php index 77fb213b49..76f0fe0a07 100644 --- a/src/Reflection/ExtendedParametersAcceptor.php +++ b/src/Reflection/ExtendedParametersAcceptor.php @@ -5,19 +5,44 @@ use PHPStan\Type\Generic\TemplateTypeVarianceMap; use PHPStan\Type\Type; -/** @api */ +/** + * Extended function/method signature with separate PHPDoc and native types. + * + * Extends ParametersAcceptor with: + * - Extended parameter reflections (separate PHPDoc/native types per parameter) + * - Separate PHPDoc and native return types (vs the combined return type from ParametersAcceptor) + * - Call-site variance map for template type parameters + * + * This is the return type of FunctionReflection::getVariants() and + * ExtendedMethodReflection::getVariants(). + * + * @api + */ interface ExtendedParametersAcceptor extends ParametersAcceptor { /** + * Returns extended parameter reflections with separate PHPDoc/native types. + * * @return list */ public function getParameters(): array; + /** + * Returns the PHPDoc @return type, separate from the native type. + */ public function getPhpDocReturnType(): Type; + /** + * Returns the native PHP return type declaration. + */ public function getNativeReturnType(): Type; + /** + * Returns the variance map for template types at the call site. + * + * Used for @template-covariant and other call-site variance specifications. + */ public function getCallSiteVarianceMap(): TemplateTypeVarianceMap; } diff --git a/src/Reflection/ExtendedPropertyReflection.php b/src/Reflection/ExtendedPropertyReflection.php index 16d0f895ea..e275cdda5d 100644 --- a/src/Reflection/ExtendedPropertyReflection.php +++ b/src/Reflection/ExtendedPropertyReflection.php @@ -6,16 +6,22 @@ use PHPStan\Type\Type; /** - * The purpose of this interface is to be able to - * answer more questions about properties - * without breaking backward compatibility - * with existing PropertiesClassReflectionExtension. + * Extended property reflection with additional metadata beyond PropertyReflection. * - * Developers are meant to only implement PropertyReflection - * and its methods in their code. + * This interface exists to allow PHPStan to add new property query methods in minor + * versions without breaking existing PropertiesClassReflectionExtension implementations. + * Extension developers should implement PropertyReflection, not this interface — PHPStan + * wraps PropertyReflection implementations to provide ExtendedPropertyReflection. * - * New methods on ExtendedPropertyReflection will be added - * in minor versions. + * Provides access to: + * - Separate PHPDoc type vs native type (for resolving the effective type) + * - Property hooks (PHP 8.4+) — get/set hooks with their own method reflections + * - Asymmetric visibility (PHP 8.4+) — different read/write visibility + * - Abstract/final/virtual modifiers + * - PHP attributes + * + * This is the return type of Type::getProperty(), Type::getInstanceProperty(), + * and Type::getStaticProperty(). * * @api */ @@ -26,48 +32,82 @@ interface ExtendedPropertyReflection extends PropertyReflection public const HOOK_SET = 'set'; + /** Returns the property name. */ public function getName(): string; + /** Whether this property has a PHPDoc @var type. */ public function hasPhpDocType(): bool; + /** + * Returns the PHPDoc @var type for this property. + * + * If no PHPDoc type exists, returns MixedType. + */ public function getPhpDocType(): Type; + /** Whether this property has a native PHP type declaration. */ public function hasNativeType(): bool; + /** + * Returns the native PHP type declaration for this property. + * + * If no native type exists, returns MixedType. + */ public function getNativeType(): Type; + /** Whether this property is abstract (requires implementation in child class). */ public function isAbstract(): TrinaryLogic; + /** Whether this property has the `final` keyword explicitly. */ public function isFinalByKeyword(): TrinaryLogic; + /** Whether this property is effectively final (by keyword or other means). */ public function isFinal(): TrinaryLogic; + /** + * Whether this is a virtual property (has hooks but no backing store). + * + * Virtual properties exist only through their get/set hooks and don't + * occupy memory in the object. Introduced in PHP 8.4. + */ public function isVirtual(): TrinaryLogic; /** + * Whether this property has the given hook type ('get' or 'set'). + * * @param self::HOOK_* $hookType */ public function hasHook(string $hookType): bool; /** + * Returns the method reflection for the given hook type. + * + * Property hooks (PHP 8.4+) are internally represented as methods. + * * @param self::HOOK_* $hookType */ public function getHook(string $hookType): ExtendedMethodReflection; + /** Whether this property has protected(set) asymmetric visibility. */ public function isProtectedSet(): bool; + /** Whether this property has private(set) asymmetric visibility. */ public function isPrivateSet(): bool; /** + * Returns PHP attributes on this property. + * * @return list */ public function getAttributes(): array; /** - * If property has been declared in code then this returns `no()` + * Whether this is a "dummy" property that may not actually exist. * - * Returns `yes()` if the property represents possibly-defined property - * in non-final classes, on mixed, on object etc. + * Returns no() for properties declared in code. + * Returns yes() for properties that represent possibly-defined properties + * on non-final classes, mixed, object, etc. — these are placeholders + * PHPStan creates when it cannot prove a property doesn't exist. */ public function isDummy(): TrinaryLogic; diff --git a/src/Reflection/FunctionReflection.php b/src/Reflection/FunctionReflection.php index b99209c628..5be9fbee46 100644 --- a/src/Reflection/FunctionReflection.php +++ b/src/Reflection/FunctionReflection.php @@ -5,67 +5,115 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\Type; -/** @api */ +/** + * Reflection for a standalone function (not a class method). + * + * Represents both built-in PHP functions and user-defined functions. Like methods, + * functions can have multiple "variants" (overloaded signatures) — particularly + * common for built-in functions where the return type depends on argument types. + * + * Extension developers encounter this interface when implementing + * DynamicFunctionReturnTypeExtension or FunctionTypeSpecifyingExtension. + * + * Functions referenced in Scope::getFunctionCallStack() may be either + * FunctionReflection or MethodReflection. + * + * @api + */ interface FunctionReflection { + /** Returns the fully qualified function name. */ public function getName(): string; + /** Returns the file path where this function is defined, or null for built-ins. */ public function getFileName(): ?string; /** + * Returns the function's parameter/return type signatures (one or more variants). + * * @return list */ public function getVariants(): array; /** + * Shortcut for functions with exactly one variant. + * * @internal */ public function getOnlyVariant(): ExtendedParametersAcceptor; /** + * Returns alternative signatures used when the function is called with named arguments. + * + * Returns null if the named argument variants are the same as regular variants. + * * @return list|null */ public function getNamedArgumentsVariants(): ?array; + /** Whether this function accepts named arguments (PHP 8.0+). */ public function acceptsNamedArguments(): TrinaryLogic; + /** Whether this function is marked as deprecated. */ public function isDeprecated(): TrinaryLogic; + /** Returns the deprecation message, or null if not deprecated. */ public function getDeprecatedDescription(): ?string; + /** Whether this function is marked as @internal. */ public function isInternal(): TrinaryLogic; + /** + * Returns the type of exceptions this function throws, or null if unknown. + * + * Comes from @throws PHPDoc tag. + */ public function getThrowType(): ?Type; + /** + * Whether this function has side effects (is impure). + * + * @see MethodReflection::hasSideEffects() for semantics. + */ public function hasSideEffects(): TrinaryLogic; + /** Whether this is a PHP built-in function (not defined in userland code). */ public function isBuiltin(): bool; + /** + * Returns type assertions declared via @phpstan-assert annotations. + */ public function getAsserts(): Assertions; + /** + * Returns the raw PHPDoc comment, or null if none exists. + */ public function getDocComment(): ?string; + /** Whether this function returns by reference (&). */ public function returnsByReference(): TrinaryLogic; /** - * This indicates whether the function has phpstan-pure - * or phpstan-impure annotation above it. + * Whether this function has a @phpstan-pure or @phpstan-impure annotation. * - * In most cases asking hasSideEffects() is much more practical - * as it also accounts for void return type (method being always impure). + * In most cases hasSideEffects() is more practical as it also accounts + * for void return type (functions returning void are always impure). */ public function isPure(): TrinaryLogic; /** + * Returns PHP attributes on this function. + * * @return list */ public function getAttributes(): array; /** - * Has the #[\NoDiscard] attribute - on PHP 8.5+ if the function's return - * value is unused at runtime a warning is emitted, PHPStan will emit the - * warning during analysis and on older PHP versions too + * Whether this function has the #[\NoDiscard] attribute. + * + * On PHP 8.5+ if the return value is unused at runtime, a warning is emitted. + * PHPStan reports this during analysis regardless of PHP version. */ public function mustUseReturnValue(): TrinaryLogic; diff --git a/src/Reflection/MethodReflection.php b/src/Reflection/MethodReflection.php index 529a5011dd..790c9396e9 100644 --- a/src/Reflection/MethodReflection.php +++ b/src/Reflection/MethodReflection.php @@ -5,29 +5,71 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\Type; -/** @api */ +/** + * Reflection for a class method. + * + * This is the interface extension developers should implement when creating custom + * MethodsClassReflectionExtension implementations for magic methods (__call, etc.). + * + * Methods can have multiple "variants" (overloaded signatures) — for example, + * built-in functions like `array_map` have different signatures depending on + * the number of arguments. Each variant is a ParametersAcceptor. + * + * For additional method metadata (assertions, purity, named arguments, attributes), + * see ExtendedMethodReflection which extends this interface. + * + * @api + */ interface MethodReflection extends ClassMemberReflection { + /** Returns the method name. */ public function getName(): string; + /** + * Returns the prototype (original declaration) of this method. + * + * For methods that override a parent method, this returns the parent's + * method reflection. For methods with no parent, returns itself. + */ public function getPrototype(): ClassMemberReflection; /** + * Returns the method's parameter/return type signatures (one or more variants). + * + * Most methods have a single variant. Built-in PHP functions with overloaded + * signatures (e.g. different return types based on argument count) have multiple. + * * @return list */ public function getVariants(): array; + /** Whether this method is marked as deprecated. */ public function isDeprecated(): TrinaryLogic; + /** Returns the deprecation message, or null if not deprecated. */ public function getDeprecatedDescription(): ?string; + /** Whether this method is final. */ public function isFinal(): TrinaryLogic; + /** Whether this method is marked as @internal. */ public function isInternal(): TrinaryLogic; + /** + * Returns the type of exceptions this method throws, or null if unknown. + * + * Comes from @throws PHPDoc tag. + */ public function getThrowType(): ?Type; + /** + * Whether this method has side effects (is impure). + * + * Returns Yes for methods known to be impure, No for pure methods, + * and Maybe when purity cannot be determined. Void methods are always + * considered impure since they must do something to be useful. + */ public function hasSideEffects(): TrinaryLogic; } diff --git a/src/Reflection/ParameterReflection.php b/src/Reflection/ParameterReflection.php index 8efbfa7c06..24c2031fc0 100644 --- a/src/Reflection/ParameterReflection.php +++ b/src/Reflection/ParameterReflection.php @@ -4,20 +4,47 @@ use PHPStan\Type\Type; -/** @api */ +/** + * Reflection for a function/method parameter. + * + * Represents a single parameter in a function or method signature. Each parameter + * has a name, type, and metadata about optionality, variadicity, and pass-by-reference. + * + * The type returned by getType() is the combined PHPDoc + native type. + * For separate PHPDoc and native types, see ExtendedParameterReflection. + * + * Part of a ParametersAcceptor which describes a complete function signature. + * + * @api + */ interface ParameterReflection { + /** Returns the parameter name (without the $ prefix). */ public function getName(): string; + /** + * Whether this parameter is optional (has a default value or is variadic). + */ public function isOptional(): bool; + /** + * Returns the parameter's type (combined PHPDoc + native type). + */ public function getType(): Type; + /** + * Returns how this parameter is passed: by value, by reference (reads existing), + * or by reference (creates new variable). + */ public function passedByReference(): PassedByReference; + /** Whether this parameter is variadic (...$param). */ public function isVariadic(): bool; + /** + * Returns the type of the default value, or null if the parameter has no default. + */ public function getDefaultValue(): ?Type; } diff --git a/src/Reflection/ParametersAcceptor.php b/src/Reflection/ParametersAcceptor.php index b5fa5f1a2d..8457bbc1dd 100644 --- a/src/Reflection/ParametersAcceptor.php +++ b/src/Reflection/ParametersAcceptor.php @@ -5,27 +5,63 @@ use PHPStan\Type\Generic\TemplateTypeMap; use PHPStan\Type\Type; -/** @api */ +/** + * Describes one signature variant of a function or method. + * + * A function/method may have multiple ParametersAcceptor variants — for example, + * the built-in `array_map` function has different signatures depending on argument count. + * Each variant describes the template type parameters, positional parameters, variadicity, + * and return type. + * + * This is the base interface. ExtendedParametersAcceptor adds separate PHPDoc/native + * return types and extended parameter reflection. CallableParametersAcceptor adds + * throw points, impure points, and purity information. + * + * Use ParametersAcceptorSelector to choose the best variant for a given call site. + * + * @api + */ interface ParametersAcceptor { + /** + * Functions that access variadic arguments implicitly. + * Used by PHPStan to detect implicit variadic behavior. + */ public const VARIADIC_FUNCTIONS = [ 'func_get_args', 'func_get_arg', 'func_num_args', ]; + /** + * Returns the template type parameters declared on this signature. + * + * Maps template names to their bound types (e.g. @template T of object). + */ public function getTemplateTypeMap(): TemplateTypeMap; + /** + * Returns the template type map with types resolved from the call site. + * + * After template type inference at a call site, this map contains the + * concrete types inferred for each template parameter. + */ public function getResolvedTemplateTypeMap(): TemplateTypeMap; /** + * Returns the list of parameters in this signature. + * * @return list */ public function getParameters(): array; + /** Whether this signature accepts additional arguments (is variadic). */ public function isVariadic(): bool; + /** + * Returns the return type of this signature (combined PHPDoc + native type). + */ public function getReturnType(): Type; } diff --git a/src/Reflection/PassedByReference.php b/src/Reflection/PassedByReference.php index 804d049b43..76883313a4 100644 --- a/src/Reflection/PassedByReference.php +++ b/src/Reflection/PassedByReference.php @@ -5,6 +5,22 @@ use function array_key_exists; /** + * Describes how a function/method parameter is passed: by value or by reference. + * + * Three modes: + * - **No**: Passed by value — the argument expression is evaluated and its value is copied. + * - **ReadsArgument**: Passed by reference, but the function reads the existing variable. + * The variable must already exist. Example: `sort(&$array)`. + * - **CreatesNewVariable**: Passed by reference, and the function may create the variable + * if it doesn't exist. Example: `preg_match($pattern, $subject, &$matches)` where + * `$matches` doesn't need to be defined beforehand. + * + * This distinction matters for PHPStan's scope analysis — when a function takes a + * parameter by reference with "creates new variable" semantics, PHPStan knows the + * variable will exist after the call even if it wasn't defined before. + * + * Used as the return type of ParameterReflection::passedByReference(). + * * @api */ final class PassedByReference @@ -30,26 +46,40 @@ private static function create(int $value): self return self::$registry[$value]; } + /** Parameter is passed by value. */ public static function createNo(): self { return self::create(self::NO); } + /** + * Parameter is passed by reference and may create the variable. + * + * The variable doesn't need to exist before the call — the function + * will create/initialize it. + */ public static function createCreatesNewVariable(): self { return self::create(self::CREATES_NEW_VARIABLE); } + /** + * Parameter is passed by reference and reads the existing variable. + * + * The variable should already exist before the call. + */ public static function createReadsArgument(): self { return self::create(self::READS_ARGUMENT); } + /** Returns true if the parameter is passed by value (not by reference). */ public function no(): bool { return $this->value === self::NO; } + /** Returns true if the parameter is passed by reference (either mode). */ public function yes(): bool { return !$this->no(); @@ -60,11 +90,17 @@ public function equals(self $other): bool return $this->value === $other->value; } + /** Returns true if this is the "creates new variable" by-reference mode. */ public function createsNewVariable(): bool { return $this->value === self::CREATES_NEW_VARIABLE; } + /** + * Combines two PassedByReference values, returning the stronger one. + * + * CreatesNewVariable > ReadsArgument > No. + */ public function combine(self $other): self { if ($this->value > $other->value) { diff --git a/src/Reflection/PropertyReflection.php b/src/Reflection/PropertyReflection.php index cc22cc5610..9b94c4ca2c 100644 --- a/src/Reflection/PropertyReflection.php +++ b/src/Reflection/PropertyReflection.php @@ -5,24 +5,61 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\Type; -/** @api */ +/** + * Reflection for a class property. + * + * This is the interface extension developers should implement when creating + * custom PropertiesClassReflectionExtension implementations for magic properties. + * + * Properties have separate readable and writable types to support: + * - Asymmetric types (PHP 8.4+ property hooks with different get/set types) + * - Read-only properties (readable but not writable) + * - Write-only properties (writable but not readable, rare) + * + * For additional property metadata (native types, PHPDoc types, hooks, attributes), + * see ExtendedPropertyReflection which extends this interface. + * + * @api + */ interface PropertyReflection extends ClassMemberReflection { + /** + * Returns the type seen when reading from this property. + * + * This is the combined PHPDoc + native type that PHPStan uses for analysis. + */ public function getReadableType(): Type; + /** + * Returns the type accepted when writing to this property. + * + * May differ from the readable type for properties with asymmetric visibility + * or property hooks with different get/set types. + */ public function getWritableType(): Type; + /** + * Whether the property's type can change after assignment. + * + * Returns false for typed properties (which always retain their declared type) + * and true for untyped properties (which take on the type of whatever is assigned). + */ public function canChangeTypeAfterAssignment(): bool; + /** Whether this property can be read from. */ public function isReadable(): bool; + /** Whether this property can be written to. */ public function isWritable(): bool; + /** Whether this property is marked as deprecated. */ public function isDeprecated(): TrinaryLogic; + /** Returns the deprecation message, or null if not deprecated. */ public function getDeprecatedDescription(): ?string; + /** Whether this property is marked as @internal. */ public function isInternal(): TrinaryLogic; } diff --git a/src/Reflection/Type/UnresolvedMethodPrototypeReflection.php b/src/Reflection/Type/UnresolvedMethodPrototypeReflection.php index 4665670cb4..fef79c9ebf 100644 --- a/src/Reflection/Type/UnresolvedMethodPrototypeReflection.php +++ b/src/Reflection/Type/UnresolvedMethodPrototypeReflection.php @@ -5,15 +5,46 @@ use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Type\Type; +/** + * Lazy method reflection that defers template type resolution. + * + * When calling a method on a generic type, the method's parameter and return types + * need to be transformed by substituting template type parameters with their concrete + * arguments. This interface allows that resolution to be deferred and configured: + * + * - getNakedMethod() returns the method as declared (before template substitution) + * - getTransformedMethod() returns the method with templates resolved + * - doNotResolveTemplateTypeMapToBounds() prevents falling back to template bounds + * when concrete types are unknown (used during type inference) + * - withCalledOnType() sets the type the method is being called on + * + * This is the return type of Type::getUnresolvedMethodPrototype(). + */ interface UnresolvedMethodPrototypeReflection { + /** + * Returns a new instance that keeps template types unresolved instead of + * falling back to their bounds. Used during type inference. + */ public function doNotResolveTemplateTypeMapToBounds(): self; + /** + * Returns the method reflection without any template type substitution. + */ public function getNakedMethod(): ExtendedMethodReflection; + /** + * Returns the method reflection with template types substituted from the + * called-on type's generic arguments. + */ public function getTransformedMethod(): ExtendedMethodReflection; + /** + * Returns a new instance configured for the given called-on type. + * + * The called-on type provides the generic arguments used for template substitution. + */ public function withCalledOnType(Type $type): self; } diff --git a/src/Reflection/Type/UnresolvedPropertyPrototypeReflection.php b/src/Reflection/Type/UnresolvedPropertyPrototypeReflection.php index 441d4a36c3..456779a5a6 100644 --- a/src/Reflection/Type/UnresolvedPropertyPrototypeReflection.php +++ b/src/Reflection/Type/UnresolvedPropertyPrototypeReflection.php @@ -5,15 +5,48 @@ use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Type\Type; +/** + * Lazy property reflection that defers template type resolution. + * + * When accessing a property on a generic type, the property's types need to be + * transformed by substituting template type parameters with their concrete arguments. + * This interface allows that resolution to be deferred and configured: + * + * - getNakedProperty() returns the property as declared (before template substitution) + * - getTransformedProperty() returns the property with templates resolved + * - doNotResolveTemplateTypeMapToBounds() prevents falling back to template bounds + * when concrete types are unknown (used during type inference) + * - withFechedOnType() sets the type the property is being accessed on + * + * This is the return type of Type::getUnresolvedPropertyPrototype(), + * Type::getUnresolvedInstancePropertyPrototype(), and + * Type::getUnresolvedStaticPropertyPrototype(). + */ interface UnresolvedPropertyPrototypeReflection { + /** + * Returns a new instance that keeps template types unresolved instead of + * falling back to their bounds. Used during type inference. + */ public function doNotResolveTemplateTypeMapToBounds(): self; + /** + * Returns the property reflection without any template type substitution. + */ public function getNakedProperty(): ExtendedPropertyReflection; + /** + * Returns the property reflection with template types substituted from the + * fetched-on type's generic arguments. + */ public function getTransformedProperty(): ExtendedPropertyReflection; + /** + * Returns a new instance configured for the given fetched-on type. + * + * The fetched-on type provides the generic arguments used for template substitution. + */ public function withFechedOnType(Type $type): self; } diff --git a/src/TrinaryLogic.php b/src/TrinaryLogic.php index fe08c0aa69..e82885cbc2 100644 --- a/src/TrinaryLogic.php +++ b/src/TrinaryLogic.php @@ -9,6 +9,30 @@ use function min; /** + * Three-valued logic used throughout PHPStan's type system. + * + * Unlike boolean logic, TrinaryLogic has three states: Yes, No, and Maybe. + * This is essential for static analysis because type relationships aren't always + * certain. For example, a `mixed` type *might* be a string — that's `Maybe`. + * + * Many Type methods return TrinaryLogic instead of bool because the answer may + * depend on runtime values that can't be known statically. Extension developers + * encounter TrinaryLogic extensively when querying type properties: + * + * if ($type->isString()->yes()) { + * // Definitely a string + * } + * if ($type->isString()->maybe()) { + * // Could be a string (e.g. mixed) + * } + * if ($type->isString()->no()) { + * // Definitely not a string + * } + * + * TrinaryLogic supports logical operations (and, or, negate) that propagate + * uncertainty correctly. It is used as a flyweight — instances are cached and + * compared by identity. + * * @api * @see https://phpstan.org/developing-extensions/trinary-logic */ @@ -26,21 +50,44 @@ private function __construct(private int $value) { } + /** + * Creates a TrinaryLogic representing definite truth. + * + * Use when the answer is unconditionally true — e.g. `StringType::isString()` + * returns `TrinaryLogic::createYes()`. + */ public static function createYes(): self { return self::$registry[self::YES] ??= new self(self::YES); } + /** + * Creates a TrinaryLogic representing definite falsehood. + * + * Use when the answer is unconditionally false — e.g. `IntegerType::isString()` + * returns `TrinaryLogic::createNo()`. + */ public static function createNo(): self { return self::$registry[self::NO] ??= new self(self::NO); } + /** + * Creates a TrinaryLogic representing uncertainty. + * + * Use when the answer cannot be determined statically — e.g. `MixedType::isString()` + * returns `TrinaryLogic::createMaybe()` because mixed could be a string at runtime. + */ public static function createMaybe(): self { return self::$registry[self::MAYBE] ??= new self(self::MAYBE); } + /** + * Converts a boolean to TrinaryLogic (true → Yes, false → No). + * + * Useful when the answer is definitively known but comes from a boolean expression. + */ public static function createFromBoolean(bool $value): self { $yesNo = $value ? self::YES : self::NO; @@ -54,6 +101,8 @@ private static function create(int $value): self } /** + * Returns true if this represents definite truth (Yes). + * * @phpstan-assert-if-true =false $this->no() * @phpstan-assert-if-true =false $this->maybe() */ @@ -63,6 +112,8 @@ public function yes(): bool } /** + * Returns true if this represents uncertainty (Maybe). + * * @phpstan-assert-if-true =false $this->no() * @phpstan-assert-if-true =false $this->yes() */ @@ -72,6 +123,8 @@ public function maybe(): bool } /** + * Returns true if this represents definite falsehood (No). + * * @phpstan-assert-if-true =false $this->maybe() * @phpstan-assert-if-true =false $this->yes() */ @@ -80,6 +133,12 @@ public function no(): bool return $this->value === self::NO; } + /** + * Converts this TrinaryLogic to a BooleanType. + * + * Yes → ConstantBooleanType(true), No → ConstantBooleanType(false), + * Maybe → BooleanType (either true or false). + */ public function toBooleanType(): BooleanType { if ($this->value === self::MAYBE) { @@ -89,6 +148,11 @@ public function toBooleanType(): BooleanType return new ConstantBooleanType($this->value === self::YES); } + /** + * Logical AND — returns the minimum of all operands. + * + * Truth table: Yes ∧ Yes = Yes, Yes ∧ Maybe = Maybe, anything ∧ No = No. + */ public function and(self ...$operands): self { $min = $this->value; @@ -103,6 +167,11 @@ public function and(self ...$operands): self } /** + * Lazy logical AND that short-circuits on No. + * + * Evaluates callbacks only until a No result is found, then stops. + * More efficient than computing all results when early termination is likely. + * * @template T * @param T[] $objects * @param callable(T): self $callback @@ -129,6 +198,11 @@ public function lazyAnd( return $this->and(...$results); } + /** + * Logical OR — returns the maximum of all operands. + * + * Truth table: No ∨ No = No, No ∨ Maybe = Maybe, anything ∨ Yes = Yes. + */ public function or(self ...$operands): self { $max = $this->value; @@ -143,6 +217,10 @@ public function or(self ...$operands): self } /** + * Lazy logical OR that short-circuits on Yes. + * + * Evaluates callbacks only until a Yes result is found, then stops. + * * @template T * @param T[] $objects * @param callable(T): self $callback @@ -169,6 +247,12 @@ public function lazyOr( return $this->or(...$results); } + /** + * Returns Yes if all operands are identical, No if all are identical and No, Maybe otherwise. + * + * Used when combining results from multiple sources where they must all agree. + * If any two operands differ, the result is Maybe. + */ public static function extremeIdentity(self ...$operands): self { if ($operands === []) { @@ -181,6 +265,8 @@ public static function extremeIdentity(self ...$operands): self } /** + * Lazy version of extremeIdentity() that short-circuits when operands disagree. + * * @template T * @param T[] $objects * @param callable(T): self $callback @@ -211,6 +297,12 @@ public static function lazyExtremeIdentity( return $lastResult; } + /** + * Returns Yes if any operand is Yes, otherwise the minimum. + * + * Useful for combining results where a single Yes is sufficient to + * confirm, but No requires all operands to be No. + */ public static function maxMin(self ...$operands): self { if ($operands === []) { @@ -221,6 +313,8 @@ public static function maxMin(self ...$operands): self } /** + * Lazy version of maxMin() that short-circuits on Yes. + * * @template T * @param T[] $objects * @param callable(T): self $callback @@ -243,16 +337,30 @@ public static function lazyMaxMin( return self::maxMin(...$results); } + /** + * Logical negation — Yes becomes No, No becomes Yes, Maybe stays Maybe. + */ public function negate(): self { return self::create(-$this->value); } + /** + * Returns true if both TrinaryLogic values are the same state. + * + * Uses identity comparison since TrinaryLogic is a flyweight. + */ public function equals(self $other): bool { return $this === $other; } + /** + * Returns the stronger of the two values, or null if they are equal. + * + * Yes > Maybe > No. Used when determining which branch provides + * more information about a type. + */ public function compareTo(self $other): ?self { if ($this->value > $other->value) { @@ -264,6 +372,9 @@ public function compareTo(self $other): ?self return null; } + /** + * Returns a human-readable label: "Yes", "No", or "Maybe". + */ public function describe(): string { static $labels = [ diff --git a/src/Type/AcceptsResult.php b/src/Type/AcceptsResult.php index 36a3b598cf..0a28242f50 100644 --- a/src/Type/AcceptsResult.php +++ b/src/Type/AcceptsResult.php @@ -10,6 +10,20 @@ use function array_values; /** + * Result of a Type::accepts() check — whether one type accepts another. + * + * Wraps a TrinaryLogic result together with human-readable reasons explaining + * why the acceptance failed. These reasons are surfaced in PHPStan error messages + * to help developers understand type mismatches. + * + * For example, when checking if `int` accepts `string`, the result would be No + * with a reason like "string is not a subtype of int". + * + * The `accepts()` method is used to check assignability — whether a value of one + * type can be assigned to a variable/parameter of another type. This is stricter + * than `isSuperTypeOf()` because it accounts for PHPStan's rule level and + * generics variance. + * * @api */ final class AcceptsResult @@ -17,7 +31,7 @@ final class AcceptsResult /** * @api - * @param list $reasons + * @param list $reasons Human-readable explanations of why acceptance failed */ public function __construct( public readonly TrinaryLogic $result, @@ -27,6 +41,8 @@ public function __construct( } /** + * Returns true if the type is definitely accepted. + * * @phpstan-assert-if-true =false $this->no() * @phpstan-assert-if-true =false $this->maybe() */ @@ -36,6 +52,8 @@ public function yes(): bool } /** + * Returns true if acceptance is uncertain. + * * @phpstan-assert-if-true =false $this->no() * @phpstan-assert-if-true =false $this->yes() */ @@ -45,6 +63,8 @@ public function maybe(): bool } /** + * Returns true if the type is definitely not accepted. + * * @phpstan-assert-if-true =false $this->maybe() * @phpstan-assert-if-true =false $this->yes() */ @@ -53,12 +73,15 @@ public function no(): bool return $this->result->no(); } + /** Creates a definite acceptance result with no reasons. */ public static function createYes(): self { return new self(TrinaryLogic::createYes(), []); } /** + * Creates a definite rejection result with optional reasons. + * * @param list $reasons */ public static function createNo(array $reasons = []): self @@ -66,16 +89,21 @@ public static function createNo(array $reasons = []): self return new self(TrinaryLogic::createNo(), $reasons); } + /** Creates an uncertain acceptance result with no reasons. */ public static function createMaybe(): self { return new self(TrinaryLogic::createMaybe(), []); } + /** Converts a boolean to an AcceptsResult (true → Yes, false → No). */ public static function createFromBoolean(bool $value): self { return new self(TrinaryLogic::createFromBoolean($value), []); } + /** + * Logical AND — combines this result with another, merging reasons. + */ public function and(self $other): self { return new self( @@ -84,6 +112,9 @@ public function and(self $other): self ); } + /** + * Logical OR — combines this result with another, merging reasons. + */ public function or(self $other): self { return new self( @@ -93,6 +124,11 @@ public function or(self $other): self } /** + * Transforms all reason strings using the given callback. + * + * Useful for adding context to reasons, e.g. wrapping them in + * "Parameter $foo: ..." format. + * * @param callable(string): string $cb */ public function decorateReasons(callable $cb): self @@ -105,6 +141,11 @@ public function decorateReasons(callable $cb): self return new self($this->result, $reasons); } + /** + * Returns Yes if all operands agree, Maybe if any disagree. + * + * @see TrinaryLogic::extremeIdentity() + */ public static function extremeIdentity(self ...$operands): self { if ($operands === []) { @@ -122,6 +163,11 @@ public static function extremeIdentity(self ...$operands): self return new self($result, array_values(array_unique($reasons))); } + /** + * Returns Yes if any operand is Yes, otherwise the minimum. + * + * @see TrinaryLogic::maxMin() + */ public static function maxMin(self ...$operands): self { if ($operands === []) { diff --git a/src/Type/ConstantScalarType.php b/src/Type/ConstantScalarType.php index b84b381717..3ba36599ec 100644 --- a/src/Type/ConstantScalarType.php +++ b/src/Type/ConstantScalarType.php @@ -2,11 +2,33 @@ namespace PHPStan\Type; -/** @api */ +/** + * A type whose value is known at analysis time — a compile-time constant scalar. + * + * Implemented by ConstantIntegerType, ConstantFloatType, ConstantStringType, + * ConstantBooleanType, and NullType. These types represent specific known values + * rather than just their general category. + * + * For example, ConstantStringType('hello') represents the specific string 'hello', + * while StringType represents any string. + * + * PHPStan tracks constant values to enable precise analysis of: + * - Array shapes (constant string keys) + * - Switch/match exhaustiveness + * - String operations with known inputs + * - Arithmetic with known values + * + * Use Type::isConstantValue() to check if a type is constant without instanceof, + * and Type::getConstantScalarTypes() to extract constant types from unions. + * + * @api + */ interface ConstantScalarType extends Type { /** + * Returns the actual PHP value this type represents. + * * @return int|float|string|bool|null */ public function getValue(); diff --git a/src/Type/GeneralizePrecision.php b/src/Type/GeneralizePrecision.php index 3f4d3be629..c4c7db305d 100644 --- a/src/Type/GeneralizePrecision.php +++ b/src/Type/GeneralizePrecision.php @@ -2,6 +2,26 @@ namespace PHPStan\Type; +/** + * Controls how aggressively Type::generalize() widens a type. + * + * Generalization is the process of widening a specific type to a broader one. + * For example, generalizing ConstantStringType('hello') yields StringType. + * This is used when PHPStan needs to merge types across loop iterations or + * branches where tracking precise constant values is impractical. + * + * Three levels of precision: + * - **lessSpecific**: Aggressive generalization — constant values become their + * general type (e.g. 'hello' → string, array{foo: int} → array) + * - **moreSpecific**: Preserves more detail — e.g. non-empty-string stays + * non-empty-string instead of widening to string + * - **templateArgument**: Used when generalizing template type arguments, + * preserving template-specific structure + * + * Used as a parameter to Type::generalize(): + * + * $type->generalize(GeneralizePrecision::lessSpecific()) + */ final class GeneralizePrecision { @@ -22,19 +42,31 @@ private static function create(int $value): self return self::$registry[$value]; } - /** @api */ + /** + * Aggressive generalization — constant values become their general types. + * + * @api + */ public static function lessSpecific(): self { return self::create(self::LESS_SPECIFIC); } - /** @api */ + /** + * Preserves more detail during generalization. + * + * @api + */ public static function moreSpecific(): self { return self::create(self::MORE_SPECIFIC); } - /** @api */ + /** + * Used when generalizing template type arguments. + * + * @api + */ public static function templateArgument(): self { return self::create(self::TEMPLATE_ARGUMENT); diff --git a/src/Type/Generic/TemplateTypeMap.php b/src/Type/Generic/TemplateTypeMap.php index 49f3a08496..4b6b3b4d7f 100644 --- a/src/Type/Generic/TemplateTypeMap.php +++ b/src/Type/Generic/TemplateTypeMap.php @@ -11,6 +11,26 @@ use function count; /** + * Maps template type parameter names to their resolved types. + * + * This is the core data structure for PHPStan's generics support. When a class declares + * `@template T`, `@template U of object`, etc., the TemplateTypeMap tracks what concrete + * types T and U resolve to in a particular context. + * + * Two kinds of type bindings are tracked: + * - **types** (upper bounds): The concrete type inferred or declared for each template. + * For `@template T of Countable`, if T is inferred as `array`, types maps T → array. + * - **lowerBoundTypes**: Types inferred from contravariant positions (e.g. parameter types). + * Used during type inference to narrow template types from below. + * + * TemplateTypeMap supports set operations (union, intersect, benevolentUnion) that combine + * maps from different code paths, and resolveToBounds() which replaces unresolved template + * types with their declared bounds. + * + * Common usage: ParametersAcceptor::getTemplateTypeMap() returns the template declarations, + * and ParametersAcceptor::getResolvedTemplateTypeMap() returns inferred concrete types. + * Type::inferTemplateTypes() produces a TemplateTypeMap from a concrete type. + * * @api */ final class TemplateTypeMap @@ -22,13 +42,16 @@ final class TemplateTypeMap /** * @api - * @param array $types - * @param array $lowerBoundTypes + * @param array $types Concrete types for each template parameter (upper bounds) + * @param array $lowerBoundTypes Types inferred from contravariant positions */ public function __construct(private array $types, private array $lowerBoundTypes = []) { } + /** + * Moves all upper-bound types to lower-bound types, intersecting with existing lower bounds. + */ public function convertToLowerBoundTypes(): self { $lowerBoundTypes = $this->types; @@ -47,6 +70,7 @@ public function convertToLowerBoundTypes(): self return new self([], $lowerBoundTypes); } + /** Returns a shared empty TemplateTypeMap instance. */ public static function createEmpty(): self { $empty = self::$empty; @@ -71,7 +95,11 @@ public function count(): int return count($this->types + $this->lowerBoundTypes); } - /** @return array */ + /** + * Returns all template type bindings (upper-bound types with lower-bound fallbacks). + * + * @return array + */ public function getTypes(): array { $types = $this->types; @@ -115,6 +143,11 @@ public function unsetType(string $name): self return new self($types, $lowerBoundTypes); } + /** + * Unions this map with another — for each template, the types are unioned. + * + * Used when merging type information from different branches (e.g. if/else). + */ public function union(self $other): self { $result = $this->types; @@ -171,6 +204,11 @@ public function benevolentUnion(self $other): self return new self($result, $resultLowerBoundTypes); } + /** + * Intersects this map with another — for each template, the types are intersected. + * + * Used when combining constraints from multiple sources. + */ public function intersect(self $other): self { $result = $this->types; @@ -206,6 +244,13 @@ public function map(callable $cb): self return new self($types); } + /** + * Replaces any unresolved TemplateType values with their declared bounds. + * + * For `@template T of Countable`, if T is still unresolved, it becomes `Countable`. + * If a default type is specified (`@template T of Countable = array`), uses the default. + * Result is cached. + */ public function resolveToBounds(): self { if ($this->resolvedToBounds !== null) { diff --git a/src/Type/Generic/TemplateTypeReference.php b/src/Type/Generic/TemplateTypeReference.php index 0be67d5e08..e1ca98ff6f 100644 --- a/src/Type/Generic/TemplateTypeReference.php +++ b/src/Type/Generic/TemplateTypeReference.php @@ -2,6 +2,20 @@ namespace PHPStan\Type\Generic; +/** + * A reference to a template type together with its variance at the point of usage. + * + * When a type contains template type parameters (e.g. `array` or `Comparable`), + * this class pairs the TemplateType with its positional variance — whether T appears + * in a covariant position (return type), contravariant position (parameter type), + * invariant position, or bivariant position. + * + * Used by Type::getReferencedTemplateTypes() to report all template types within + * a type along with their variance context. This information is used for: + * - Template type inference (knowing the variance affects how types are inferred) + * - Variance validation (checking that @template-covariant types only appear in + * covariant positions) + */ final class TemplateTypeReference { @@ -9,11 +23,18 @@ public function __construct(private TemplateType $type, private TemplateTypeVari { } + /** Returns the template type being referenced. */ public function getType(): TemplateType { return $this->type; } + /** + * Returns the variance of the position where this template type appears. + * + * For example, in `function foo(): T`, T is in a covariant position. + * In `function foo(T $x)`, T is in a contravariant position. + */ public function getPositionVariance(): TemplateTypeVariance { return $this->positionVariance; diff --git a/src/Type/Generic/TemplateTypeVariance.php b/src/Type/Generic/TemplateTypeVariance.php index a630895bed..e032a25ba3 100644 --- a/src/Type/Generic/TemplateTypeVariance.php +++ b/src/Type/Generic/TemplateTypeVariance.php @@ -13,6 +13,24 @@ use function sprintf; /** + * Represents the variance of a template type parameter. + * + * Variance describes how subtyping of a generic type relates to subtyping of its + * type arguments. For a class `Box`: + * + * - **Invariant** (default): `Box` is NOT a subtype of `Box`, even though + * Cat extends Animal. The type argument must match exactly. Declared with `@template T`. + * - **Covariant**: `Box` IS a subtype of `Box`. Safe when T only appears + * in "output" positions (return types). Declared with `@template-covariant T`. + * - **Contravariant**: `Box` IS a subtype of `Box`. Safe when T only + * appears in "input" positions (parameter types). Declared with `@template-contravariant T`. + * - **Bivariant**: The type argument is ignored for subtyping purposes. Rarely used. + * - **Static**: Special variance for `static` return type in template context. + * + * Variance composition follows standard rules — e.g. covariant composed with + * contravariant yields contravariant. This is used when template types appear + * inside nested generic types. + * * @api */ final class TemplateTypeVariance @@ -37,26 +55,31 @@ private static function create(int $value): self return self::$registry[$value]; } + /** Type argument must match exactly. This is the default for @template T. */ public static function createInvariant(): self { return self::create(self::INVARIANT); } + /** Subtyping flows with the type argument: Cat <: Animal ⟹ Box <: Box. */ public static function createCovariant(): self { return self::create(self::COVARIANT); } + /** Subtyping flows against the type argument: Cat <: Animal ⟹ Box <: Box. */ public static function createContravariant(): self { return self::create(self::CONTRAVARIANT); } + /** Special variance for static return type in template context. */ public static function createStatic(): self { return self::create(self::STATIC); } + /** Type argument is ignored for subtyping — all types are compatible. */ public static function createBivariant(): self { return self::create(self::BIVARIANT); @@ -87,6 +110,13 @@ public function bivariant(): bool return $this->value === self::BIVARIANT; } + /** + * Composes two variances together for nested generic types. + * + * For example, if a type appears in a contravariant position inside a + * covariant container, the effective variance is contravariant. + * Composition rules follow standard type theory. + */ public function compose(self $other): self { if ($this->contravariant()) { @@ -126,6 +156,14 @@ public function compose(self $other): self return $other; } + /** + * Checks whether two types satisfy this variance constraint. + * + * For invariant: types must be equal. + * For covariant: $a must be a supertype of $b. + * For contravariant: $b must be a supertype of $a. + * For bivariant: always valid. + */ public function isValidVariance(TemplateType $templateType, Type $a, Type $b): IsSuperTypeOfResult { if ($b instanceof NeverType) { diff --git a/src/Type/IsSuperTypeOfResult.php b/src/Type/IsSuperTypeOfResult.php index 7fddea5c04..ca682028fe 100644 --- a/src/Type/IsSuperTypeOfResult.php +++ b/src/Type/IsSuperTypeOfResult.php @@ -10,6 +10,22 @@ use function array_values; /** + * Result of a Type::isSuperTypeOf() check — whether one type is a supertype of another. + * + * Wraps a TrinaryLogic result together with human-readable reasons explaining the + * relationship. This is the primary mechanism for comparing types in PHPStan's type system. + * + * `isSuperTypeOf()` answers: "Can all values of type B also be values of type A?" + * For example: + * - `(new StringType())->isSuperTypeOf(new ConstantStringType('hello'))` → Yes + * - `(new IntegerType())->isSuperTypeOf(new StringType())` → No + * - `(new StringType())->isSuperTypeOf(new MixedType())` → Maybe + * + * This is distinct from `accepts()` which also considers rule levels and PHPDoc context. + * Use `isSuperTypeOf()` for type-theoretic comparisons and `accepts()` for assignability checks. + * + * Can be converted to AcceptsResult via toAcceptsResult(). + * * @api */ final class IsSuperTypeOfResult @@ -17,7 +33,7 @@ final class IsSuperTypeOfResult /** * @api - * @param list $reasons + * @param list $reasons Human-readable explanations of the type relationship */ public function __construct( public readonly TrinaryLogic $result, @@ -27,6 +43,8 @@ public function __construct( } /** + * Returns true if this type is definitely a supertype. + * * @phpstan-assert-if-true =false $this->no() * @phpstan-assert-if-true =false $this->maybe() */ @@ -36,6 +54,8 @@ public function yes(): bool } /** + * Returns true if the supertype relationship is uncertain. + * * @phpstan-assert-if-true =false $this->no() * @phpstan-assert-if-true =false $this->yes() */ @@ -45,6 +65,8 @@ public function maybe(): bool } /** + * Returns true if this type is definitely not a supertype. + * * @phpstan-assert-if-true =false $this->maybe() * @phpstan-assert-if-true =false $this->yes() */ @@ -53,12 +75,15 @@ public function no(): bool return $this->result->no(); } + /** Creates a definite supertype result with no reasons. */ public static function createYes(): self { return new self(TrinaryLogic::createYes(), []); } /** + * Creates a definite non-supertype result with optional reasons. + * * @param list $reasons */ public static function createNo(array $reasons = []): self @@ -66,21 +91,31 @@ public static function createNo(array $reasons = []): self return new self(TrinaryLogic::createNo(), $reasons); } + /** Creates an uncertain supertype result with no reasons. */ public static function createMaybe(): self { return new self(TrinaryLogic::createMaybe(), []); } + /** Converts a boolean to an IsSuperTypeOfResult (true → Yes, false → No). */ public static function createFromBoolean(bool $value): self { return new self(TrinaryLogic::createFromBoolean($value), []); } + /** + * Converts this to an AcceptsResult, preserving the result and reasons. + * + * Used when an isSuperTypeOf() check is sufficient for an accepts() implementation. + */ public function toAcceptsResult(): AcceptsResult { return new AcceptsResult($this->result, $this->reasons); } + /** + * Logical AND — combines with other results, merging reasons. + */ public function and(self ...$others): self { $results = []; @@ -96,6 +131,9 @@ public function and(self ...$others): self ); } + /** + * Logical OR — combines with other results, merging reasons. + */ public function or(self ...$others): self { $results = []; @@ -112,6 +150,8 @@ public function or(self ...$others): self } /** + * Transforms all reason strings using the given callback. + * * @param callable(string): string $cb */ public function decorateReasons(callable $cb): self @@ -124,6 +164,11 @@ public function decorateReasons(callable $cb): self return new self($this->result, $reasons); } + /** + * Returns Yes if all operands agree, Maybe if any disagree. + * + * @see TrinaryLogic::extremeIdentity() + */ public static function extremeIdentity(self ...$operands): self { if ($operands === []) { @@ -135,6 +180,11 @@ public static function extremeIdentity(self ...$operands): self return new self($result, self::mergeReasons($operands)); } + /** + * Returns Yes if any operand is Yes, otherwise the minimum. + * + * @see TrinaryLogic::maxMin() + */ public static function maxMin(self ...$operands): self { if ($operands === []) { @@ -146,11 +196,18 @@ public static function maxMin(self ...$operands): self return new self($result, self::mergeReasons($operands)); } + /** + * Logical negation — Yes becomes No and vice versa, Maybe stays Maybe. + * Reasons are preserved. + */ public function negate(): self { return new self($this->result->negate(), $this->reasons); } + /** + * Returns a human-readable label: "Yes", "No", or "Maybe". + */ public function describe(): string { return $this->result->describe(); diff --git a/src/Type/TypeWithClassName.php b/src/Type/TypeWithClassName.php index c2688af355..e169b2020d 100644 --- a/src/Type/TypeWithClassName.php +++ b/src/Type/TypeWithClassName.php @@ -4,14 +4,43 @@ use PHPStan\Reflection\ClassReflection; -/** @api */ +/** + * A Type that represents an object with a known class name. + * + * Implemented by ObjectType, StaticType, ThisType, EnumCaseObjectType, ClosureType, + * and GenericObjectType. Provides access to the class name and its ClassReflection. + * + * This interface is used when code needs to work with any object type that has a + * specific class — for example, Scope::resolveTypeByName() returns TypeWithClassName + * because the resolved type always has a known class. + * + * Note: Do not use `instanceof TypeWithClassName` to check if a type is an object. + * Use `$type->getObjectClassNames()` or `$type->isObject()` instead, which correctly + * handles union types and intersection types. + * + * @api + */ interface TypeWithClassName extends Type { + /** + * Returns the fully qualified class name (without leading backslash). + */ public function getClassName(): string; + /** + * Walks the type's class hierarchy to find an ancestor matching the given class name. + * + * Returns a TypeWithClassName representing the type projected onto that ancestor, + * or null if the class is not in the hierarchy. Preserves generic type arguments + * when walking through the hierarchy. + */ public function getAncestorWithClassName(string $className): ?self; + /** + * Returns the ClassReflection for this type's class, or null if the class + * cannot be reflected (e.g. the class doesn't exist). + */ public function getClassReflection(): ?ClassReflection; } diff --git a/src/Type/VerbosityLevel.php b/src/Type/VerbosityLevel.php index 41ae162b7a..ca56ce5984 100644 --- a/src/Type/VerbosityLevel.php +++ b/src/Type/VerbosityLevel.php @@ -14,6 +14,29 @@ use PHPStan\Type\Generic\GenericStaticType; use PHPStan\Type\Generic\TemplateType; +/** + * Controls the verbosity of type descriptions in error messages. + * + * When PHPStan describes a type for an error message, it uses VerbosityLevel to + * decide how much detail to include. Higher levels include more detail like constant + * values and array shapes. + * + * The four levels (from least to most verbose): + * - **typeOnly**: Just the type name, e.g. "string", "array", "Foo" + * - **value**: Includes constant values, e.g. "'hello'", "array{foo: int}", "non-empty-string" + * - **precise**: Maximum detail including lowercase/uppercase string distinctions + * - **cache**: Internal level used for generating cache keys + * + * Used as a parameter to Type::describe() to control output detail: + * + * $type->describe(VerbosityLevel::typeOnly()) // "string" + * $type->describe(VerbosityLevel::value()) // "'hello'" + * $type->describe(VerbosityLevel::precise()) // "non-empty-lowercase-string" + * + * The getRecommendedLevelByType() factory method automatically chooses the right level + * for error messages based on what types are involved — it picks the minimum verbosity + * needed to distinguish the accepting type from the accepted type. + */ final class VerbosityLevel { @@ -47,25 +70,50 @@ public function getLevelValue(): int return $this->value; } - /** @api */ + /** + * Least verbose: only type names, no values or refinements. + * + * E.g. "string", "int", "array". + * + * @api + */ public static function typeOnly(): self { return self::create(self::TYPE_ONLY); } - /** @api */ + /** + * Includes constant values and basic refinements. + * + * E.g. "'hello'", "42", "array{foo: int, bar: string}", "non-empty-string". + * + * @api + */ public static function value(): self { return self::create(self::VALUE); } - /** @api */ + /** + * Maximum verbosity: includes all refinements like lowercase/uppercase. + * + * E.g. "non-empty-lowercase-string", "non-falsy-string". + * + * @api + */ public static function precise(): self { return self::create(self::PRECISE); } - /** @api */ + /** + * Internal level used to generate unique cache keys for types. + * + * Produces the most specific string possible to distinguish any two + * structurally different types. Not intended for user-facing messages. + * + * @api + */ public static function cache(): self { return self::create(self::CACHE); @@ -91,7 +139,15 @@ public function isCache(): bool return $this->value === self::CACHE; } - /** @api */ + /** + * Chooses the minimum verbosity level needed to distinguish the accepting and accepted types. + * + * Examines both types and picks a level that provides enough detail to make the + * error message clear. For example, if the types differ only in constant values, + * it picks value(). If they differ in lowercase/uppercase, it picks precise(). + * + * @api + */ public static function getRecommendedLevelByType(Type $acceptingType, ?Type $acceptedType = null): self { $moreVerbose = false; @@ -206,6 +262,12 @@ public static function getRecommendedLevelByType(Type $acceptingType, ?Type $acc } /** + * Dispatches to the appropriate callback based on the current level. + * + * Type implementations use this in their describe() method to provide + * different representations at each verbosity level. Falls back to less + * specific callbacks when more specific ones are not provided. + * * @param callable(): string $typeOnlyCallback * @param callable(): string $valueCallback * @param callable(): string|null $preciseCallback From b5463a32e880785b5a2b274d5e348013ac12e646 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 10 Feb 2026 19:28:24 +0000 Subject: [PATCH 4/9] Document Type interface method addition pattern in CLAUDE.md Based on git blame analysis of Type.php, document the recurring pattern of adding new methods to the Type interface as a bug fix strategy instead of scattering instanceof checks or utility calls across the codebase. https://claude.ai/code/session_01Htkqstd1mz1dbevHT6Y437 --- CLAUDE.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index d4e7bdc1e9..186a84d1d2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -210,6 +210,24 @@ Based on analysis of recent releases (2.1.30-2.1.38), these are the recurring pa A recurring cleanup theme: never use `$type instanceof StringType` or similar. This misses union types, intersection types with accessory types, and other composite forms. Always use `$type->isString()->yes()` or `(new StringType())->isSuperTypeOf($type)`. Multiple PRs have systematically replaced `instanceof *Type` checks throughout the codebase. +### Type system: add methods to the Type interface instead of one-offing conditions + +When a bug requires checking a type property across the codebase, the fix is often to add a new method to the `Type` interface rather than scattering `instanceof` checks or utility function calls throughout rules and extensions. This ensures every type implementation handles the query correctly (including union/intersection types which delegate to their inner types) and keeps the logic centralized. + +Historical analysis of `Type.php` via `git blame` shows that new methods are added for several recurring reasons: + +- **Replacing scattered `instanceof` checks (~30%)**: Methods like `isNull()`, `isTrue()`, `isFalse()`, `isString()`, `isInteger()`, `isFloat()`, `isBoolean()`, `isArray()`, `isScalar()`, `isObject()`, `isEnum()`, `getClassStringObjectType()`, `getObjectClassNames()`, `getObjectClassReflections()` were added to replace `$type instanceof ConstantBooleanType`, `$type instanceof StringType`, etc. Each type implements the method correctly — e.g., `UnionType::isNull()` returns `yes` only if all members are null, `maybe` if some are, `no` if none are. This is impossible to get right with a single `instanceof` check. + +- **Moving logic from TypeUtils/extensions into Type (~35%)**: Methods like `toArrayKey()`, `toBoolean()`, `toNumber()`, `toFloat()`, `toInteger()`, `toString()`, `toArray()`, `flipArray()`, `getKeysArray()`, `getValuesArray()`, `popArray()`, `shiftArray()`, `shuffleArray()`, `reverseSortArray()`, `getEnumCases()`, `isCallable()`, `getCallableParametersAcceptors()`, `isList()` moved scattered utility logic into polymorphic dispatch. When logic lives in a utility function it typically uses a chain of `if ($type instanceof X) ... elseif ($type instanceof Y) ...` which breaks when new type classes are added or misses edge cases in composite types. + +- **Supporting new type features (~15%)**: Methods like `isNonEmptyString()`, `isNonFalsyString()`, `isLiteralString()`, `isClassString()`, `isNonEmptyArray()`, `isIterableAtLeastOnce()` were added as PHPStan gained support for more refined types (accessory types in intersections). These enable rules to query refined properties without knowing how the refinement is represented internally. + +- **Bug fixes through better polymorphism (~10%)**: Some bugs are directly fixed by adding a new Type method. For example, `isOffsetAccessLegal()` fixed false positives about illegal offset access by letting each type declare whether `$x[...]` is valid. `setExistingOffsetValueType()` (distinct from `setOffsetValueType()`) fixed array list type preservation bugs. `toCoercedArgumentType()` fixed parameter type contravariance issues during type coercion. + +- **Richer return types (~5%)**: Methods that returned `TrinaryLogic` were changed to return `AcceptsResult` or `IsSuperTypeOfResult`, which carry human-readable reasons for why a type relationship holds or doesn't. This enabled better error messages without changing the call sites significantly. + +When considering a bug fix that involves checking "is this type a Foo?", first check whether an appropriate method already exists on `Type`. If not, consider whether adding one would be the right fix — especially if the check is needed in more than one place or involves logic that varies by type class. + ### MutatingScope: expression invalidation during scope merging When two scopes are merged (e.g. after if/else branches), `MutatingScope::generalizeWith()` must invalidate dependent expressions. If variable `$i` changes, then `$locations[$i]` must be invalidated too. Bugs arise when stale `ExpressionTypeHolder` entries survive scope merges. Fix pattern: in `MutatingScope`, when a root expression changes, skip/invalidate all deep expressions that depend on it. From 7ff1c19e82ceb9581fdc0bd82de6df4b93c1fb9d Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 10 Feb 2026 20:29:47 +0000 Subject: [PATCH 5/9] Add comprehensive PHPDoc documentation to CompoundType and AccessoryType interfaces CompoundType: Document the double-dispatch protocol for bidirectional type comparison, explaining why instanceof CompoundType is the correct pattern (unlike instanceof SomeSpecificType which is discouraged). Document all four methods with their decomposition semantics. AccessoryType: Document the marker interface's role as a refinement type that always lives inside an IntersectionType alongside a base type. Include a complete mapping table of all 13 implementations to their corresponding query methods on the Type interface. https://claude.ai/code/session_01Htkqstd1mz1dbevHT6Y437 --- src/Type/Accessory/AccessoryType.php | 48 ++++++++++++++++++++ src/Type/CompoundType.php | 68 +++++++++++++++++++++++++++- 2 files changed, 115 insertions(+), 1 deletion(-) diff --git a/src/Type/Accessory/AccessoryType.php b/src/Type/Accessory/AccessoryType.php index 734ee43759..3b0992e0e9 100644 --- a/src/Type/Accessory/AccessoryType.php +++ b/src/Type/Accessory/AccessoryType.php @@ -4,6 +4,54 @@ use PHPStan\Type\Type; +/** + * Marker interface for types that refine a base type with additional constraints. + * + * An AccessoryType never stands alone — it always exists inside an `IntersectionType` + * alongside a base type (like `StringType` or `ArrayType`). The base type provides + * the fundamental type identity, and the AccessoryType adds a narrowing guarantee. + * + * For example, `non-empty-string` is represented as: + * + * IntersectionType([StringType, AccessoryNonEmptyStringType]) + * + * And `non-empty-list` is represented as: + * + * IntersectionType([ArrayType(int, int), NonEmptyArrayType, AccessoryArrayListType]) + * + * Each AccessoryType implementation has a corresponding query method on the `Type` interface + * that returns `TrinaryLogic`. This lets any code query the refinement without knowing + * how it is represented internally: + * + * | AccessoryType class | Corresponding Type method | + * |----------------------------------|---------------------------------| + * | AccessoryNonEmptyStringType | `isNonEmptyString()` | + * | AccessoryNonFalsyStringType | `isNonFalsyString()` | + * | AccessoryLiteralStringType | `isLiteralString()` | + * | AccessoryNumericStringType | `isNumericString()` | + * | AccessoryLowercaseStringType | `isLowercaseString()` | + * | AccessoryUppercaseStringType | `isUppercaseString()` | + * | AccessoryArrayListType | `isList()` | + * | NonEmptyArrayType | `isIterableAtLeastOnce()` | + * | OversizedArrayType | `isOversizedArray()` | + * | HasMethodType | `hasMethod(string)` | + * | HasPropertyType | `hasProperty(string)` | + * | HasOffsetType | `hasOffsetValueType(Type)` | + * | HasOffsetValueType | `getOffsetValueType(Type)` | + * + * All implementations also implement `CompoundType`, so they participate in the + * double-dispatch protocol for type comparison — simple types delegate to + * `$type->isAcceptedBy()`/`$type->isSubTypeOf()` when they encounter an AccessoryType. + * + * The `instanceof AccessoryType` check is used in a few specific places: + * - `IntersectionType::describe()` — skips AccessoryTypes when building base type names + * (they are rendered as type-level qualifiers like `non-empty-` instead) + * - `IntersectionType::describeItself()` — separates base types from accessory types + * when composing the human-readable type description + * - `UnionTypeHelper::sortTypes()` — sorts AccessoryTypes after base types + * - `TypeCombinator` — handles AccessoryType intersection/union normalization + * - `MissingTypehintCheck` — skips AccessoryTypes in typehint analysis + */ interface AccessoryType extends Type { diff --git a/src/Type/CompoundType.php b/src/Type/CompoundType.php index f66e10c091..8f3f0f97cc 100644 --- a/src/Type/CompoundType.php +++ b/src/Type/CompoundType.php @@ -5,16 +5,82 @@ use PHPStan\Php\PhpVersion; use PHPStan\TrinaryLogic; -/** @api */ +/** + * Marker interface for types that require bidirectional type comparison. + * + * Simple types like `StringType` or `IntegerType` can answer `isSuperTypeOf()` + * and `accepts()` on their own — they check whether the incoming type fits. + * But compound types (unions, intersections, mixed, never, accessory types, + * integer ranges, callables, iterables, conditionals, etc.) need to be asked + * from the other direction, because they carry internal structure that the + * simple type on the other side knows nothing about. + * + * The protocol works like a double dispatch: + * + * 1. A simple type's `accepts()`/`isSuperTypeOf()` receives an argument. + * 2. It checks `if ($type instanceof CompoundType)`. + * 3. If true, it delegates to `$type->isAcceptedBy($this, …)` or `$type->isSubTypeOf($this)`. + * 4. The compound type then decomposes itself (e.g., iterates union members) + * and calls back to the simple type for each component. + * + * This avoids the simple type having to understand union/intersection/mixed/never + * semantics. For example, `StringType::accepts()` doesn't need to know how to + * check a `UnionType` — it just delegates to `UnionType::isAcceptedBy()`, + * which iterates its members and asks `StringType::accepts()` for each one. + * + * Unlike `instanceof SomeSpecificType` checks (which are discouraged in CLAUDE.md), + * `instanceof CompoundType` is the correct and intended pattern throughout the + * type system. It is part of the double-dispatch protocol, not a type query. + * + * Implementations include: + * - `UnionType` — `isSubTypeOf()` requires ALL members to be subtypes, `isAcceptedBy()` requires ALL to be accepted + * - `IntersectionType` — `isSubTypeOf()` requires at least ONE member to be a subtype (via `maxMin`) + * - `MixedType`, `NeverType` — terminal cases (mixed accepts everything, never is subtype of everything) + * - All `AccessoryType` implementations — refinement types that live inside intersections + * - `IntegerRangeType`, `CallableType`, `IterableType` — types with internal structure + * - `ConditionalType`, `KeyOfType`, `ValueOfType`, etc. — late-resolvable types + * + * @api + */ interface CompoundType extends Type { + /** + * Answers "is this compound type accepted by $acceptingType?" from the compound type's perspective. + * + * Called by simple types when they encounter a CompoundType argument in their `accepts()` method. + * The compound type decomposes itself and calls `$acceptingType->accepts()` for each component. + * + * For example, `UnionType(string|int)::isAcceptedBy(StringType)` asks StringType to accept + * `string` and `int` separately, then combines results with `extremeIdentity` (all must pass). + */ public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult; + /** + * Answers "is this compound type a subtype of $otherType?" from the compound type's perspective. + * + * Called by simple types when they encounter a CompoundType argument in their `isSuperTypeOf()` method. + * The compound type decomposes itself and calls `$otherType->isSuperTypeOf()` for each component. + * + * For example, `UnionType(string|int)::isSubTypeOf(MixedType)` asks MixedType whether it is + * a supertype of `string` and `int` separately, then combines with `extremeIdentity` (all must pass). + */ public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult; + /** + * Compares this compound type against $otherType using greater-than semantics. + * + * Used for comparison operators (`>`). Each compound type decomposes the comparison + * across its members (e.g., IntegerRangeType checks whether all values in the range + * are greater than the other type). + */ public function isGreaterThan(Type $otherType, PhpVersion $phpVersion): TrinaryLogic; + /** + * Compares this compound type against $otherType using greater-than-or-equal semantics. + * + * Used for comparison operators (`>=`). Same decomposition strategy as `isGreaterThan()`. + */ public function isGreaterThanOrEqual(Type $otherType, PhpVersion $phpVersion): TrinaryLogic; } From 4629288d6d2ae4f69e5387b549beca4f89220a62 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 10 Feb 2026 22:27:33 +0000 Subject: [PATCH 6/9] Remove obvious PHPDoc descriptions that are redundant with method names, tags, or common knowledge https://claude.ai/code/session_01Htkqstd1mz1dbevHT6Y437 --- src/Analyser/Scope.php | 210 +---- src/Php/PhpVersion.php | 23 +- src/Php/PhpVersions.php | 16 +- src/Reflection/Assertions.php | 27 +- src/Reflection/AttributeReflection.php | 8 +- .../Callables/CallableParametersAcceptor.php | 28 +- .../Callables/SimpleImpurePoint.php | 21 +- src/Reflection/Callables/SimpleThrowPoint.php | 14 - src/Reflection/ClassConstantReflection.php | 18 - src/Reflection/ClassMemberAccessAnswerer.php | 44 - src/Reflection/ClassMemberReflection.php | 10 - src/Reflection/ConstantReflection.php | 12 +- src/Reflection/ExtendedMethodReflection.php | 40 +- src/Reflection/ExtendedParametersAcceptor.php | 17 +- src/Reflection/ExtendedPropertyReflection.php | 45 +- src/Reflection/FunctionReflection.php | 47 +- src/Reflection/MethodReflection.php | 21 +- src/Reflection/NamespaceAnswerer.php | 6 +- src/Reflection/ParameterReflection.php | 15 - src/Reflection/ParametersAcceptor.php | 21 +- src/Reflection/PassedByReference.php | 21 +- src/Reflection/PropertyReflection.php | 14 - .../UnresolvedMethodPrototypeReflection.php | 12 - .../UnresolvedPropertyPrototypeReflection.php | 12 - src/TrinaryLogic.php | 82 +- src/Type/AcceptsResult.php | 42 +- src/Type/ConstantScalarType.php | 18 +- src/Type/GeneralizePrecision.php | 18 +- src/Type/Generic/TemplateTypeMap.php | 26 +- src/Type/Generic/TemplateTypeReference.php | 7 - src/Type/Generic/TemplateTypeVariance.php | 15 - src/Type/IsSuperTypeOfResult.php | 51 +- src/Type/Type.php | 801 ++---------------- src/Type/TypeWithClassName.php | 13 +- src/Type/VerbosityLevel.php | 41 +- 35 files changed, 140 insertions(+), 1676 deletions(-) diff --git a/src/Analyser/Scope.php b/src/Analyser/Scope.php index 7c3e174657..cab588c4c6 100644 --- a/src/Analyser/Scope.php +++ b/src/Analyser/Scope.php @@ -60,138 +60,56 @@ interface Scope extends ClassMemberAccessAnswerer, NamespaceAnswerer ]; /** - * Returns the absolute path of the file being analysed. - * - * When analysing a trait, this returns the file where the trait is used (the class file), - * not the trait file itself. Use getFileDescription() to get the trait file path - * with class context information. + * When analysing a trait, returns the file where the trait is used, + * not the trait file itself. Use getFileDescription() for the trait file path. */ public function getFile(): string; /** - * Returns a human-readable file description for error messages. - * - * For regular files, this is the same as getFile(). - * For traits, this returns the trait file path with the using class context, + * For traits, returns the trait file path with the using class context, * e.g. "TraitFile.php (in context of class MyClass)". */ public function getFileDescription(): string; - /** - * Returns whether the current file has declare(strict_types=1). - * - * When true, PHP enforces strict type checking for function/method arguments - * and return values — no implicit type coercion is performed. This affects - * how Type::accepts() behaves (e.g. int is not accepted by float in strict mode). - */ public function isDeclareStrictTypes(): bool; /** - * Returns whether the current analysis context is inside a trait. - * - * When true, getTraitReflection() is guaranteed to return non-null. - * Used by rules that need trait-specific behavior, such as skipping - * certain checks that don't apply in trait context. - * * @phpstan-assert-if-true !null $this->getTraitReflection() */ public function isInTrait(): bool; /** - * Returns the ClassReflection of the trait being analysed, or null. - * - * Only non-null when isInTrait() is true. The returned reflection - * represents the trait itself, not the class using the trait. - * Use getClassReflection() (from ClassMemberAccessAnswerer) to get the - * class that uses the trait. + * Returns the trait itself, not the class using the trait. + * Use getClassReflection() for the using class. */ public function getTraitReflection(): ?ClassReflection; - /** - * Returns the reflection of the current function or method, or null. - * - * Returns null when outside of any function/method (e.g. at the top level - * of a file, or in a class but outside a method). For closures and arrow - * functions, returns their reflection. - */ public function getFunction(): ?PhpFunctionFromParserNodeReflection; - /** - * Returns the name of the current function or method, or null. - * - * For methods, returns the method name (not the fully qualified name). - * For closures and arrow functions, returns null. - * For top-level code, returns null. - */ public function getFunctionName(): ?string; - /** - * Returns the parent scope, or null if this is the top-level scope. - * - * The parent scope is the scope that encloses the current one. For example, - * when inside a closure, the parent scope is the scope of the function - * that contains the closure. Used for variable resolution in closures - * and arrow functions. - */ public function getParentScope(): ?self; - /** - * Returns whether a variable with the given name exists in the current scope. - * - * Returns TrinaryLogic::Yes if the variable is definitely defined, - * TrinaryLogic::Maybe if it might be defined (e.g. defined in one branch of an if), - * and TrinaryLogic::No if it is not defined. - */ public function hasVariableType(string $variableName): TrinaryLogic; - /** - * Returns the type of a variable in the current scope. - * - * If the variable is not defined, returns ErrorType. - * Check hasVariableType() first if you need to distinguish between - * undefined variables and variables with unknown types. - */ public function getVariableType(string $variableName): Type; /** - * Returns whether any variable can potentially exist in this scope. - * - * Returns true at the top level of a file (outside functions/closures) - * or after an extract() call — contexts where arbitrary variables may exist. - * Returns false inside functions, methods, and closures (unless extract() - * was called), where the set of available variables is known. - * - * Used by the DefinedVariableRule to suppress "undefined variable" errors - * when the full variable set is not known. + * True at the top level of a file or after extract() — contexts where + * arbitrary variables may exist. */ public function canAnyVariableExist(): bool; - /** - * Returns the names of all variables that are definitely defined in this scope. - * - * Only includes variables with TrinaryLogic::Yes certainty. - * - * @return array - */ + /** @return array */ public function getDefinedVariables(): array; /** - * Returns the names of variables that might be defined in this scope. - * - * Only includes variables with TrinaryLogic::Maybe certainty — variables - * that are defined in some code paths but not others (e.g. defined inside - * an if-branch but not in the else-branch). + * Variables with TrinaryLogic::Maybe certainty — defined in some code paths but not others. * * @return array */ public function getMaybeDefinedVariables(): array; - /** - * Returns whether a global constant with the given name exists. - * - * Checks both PHP built-in constants and user-defined constants. - * The Name node is resolved according to the current namespace. - */ public function hasConstant(Name $name): bool; /** @@ -199,152 +117,52 @@ public function hasConstant(Name $name): bool; */ public function getPropertyReflection(Type $typeWithProperty, string $propertyName): ?ExtendedPropertyReflection; - /** - * Returns the reflection for an instance property on the given type, or null. - * - * Resolves the property through the type system, handling union types, - * intersection types, and visibility checks. Returns null if the property - * doesn't exist or is not accessible from the current scope. - */ public function getInstancePropertyReflection(Type $typeWithProperty, string $propertyName): ?ExtendedPropertyReflection; - /** - * Returns the reflection for a static property on the given type, or null. - * - * Like getInstancePropertyReflection() but for static properties (Foo::$bar). - * Returns null if the property doesn't exist or is not accessible. - */ public function getStaticPropertyReflection(Type $typeWithProperty, string $propertyName): ?ExtendedPropertyReflection; - /** - * Returns the reflection for a method on the given type, or null. - * - * Resolves the method through the type system, handling union types, - * intersection types, and visibility checks. Returns null if the method - * doesn't exist or is not accessible from the current scope. - */ public function getMethodReflection(Type $typeWithMethod, string $methodName): ?ExtendedMethodReflection; - /** - * Returns the reflection for a class constant on the given type, or null. - * - * Resolves the constant through the type system. Returns null if the - * constant doesn't exist or is not accessible from the current scope. - */ public function getConstantReflection(Type $typeWithConstant, string $constantName): ?ClassConstantReflection; - /** - * Returns the explicitly configured type for a global constant, if any. - * - * Checks the PHPStan configuration for user-specified constant type overrides - * (via the `constants` configuration option). Falls back to the given $constantType - * if no override is configured. - */ public function getConstantExplicitTypeFromConfig(string $constantName, Type $constantType): Type; - /** - * Returns the key type of an iterable type. - * - * Unlike calling $iteratee->getIterableKeyType() directly, this method - * goes through the Scope to properly resolve template types and handle - * scope-specific type refinements. - */ public function getIterableKeyType(Type $iteratee): Type; - /** - * Returns the value type of an iterable type. - * - * Unlike calling $iteratee->getIterableValueType() directly, this method - * goes through the Scope to properly resolve template types and handle - * scope-specific type refinements. - */ public function getIterableValueType(Type $iteratee): Type; /** - * Returns whether the current analysis context is inside an anonymous function - * (closure or arrow function). - * - * When true, both getAnonymousFunctionReflection() and - * getAnonymousFunctionReturnType() are guaranteed to return non-null. - * * @phpstan-assert-if-true !null $this->getAnonymousFunctionReflection() * @phpstan-assert-if-true !null $this->getAnonymousFunctionReturnType() */ public function isInAnonymousFunction(): bool; - /** - * Returns the ClosureType reflection of the current anonymous function, or null. - * - * Only non-null when isInAnonymousFunction() is true. The ClosureType - * contains the closure's parameter types, return type, and template types. - */ public function getAnonymousFunctionReflection(): ?ClosureType; - /** - * Returns the declared return type of the current anonymous function, or null. - * - * Only non-null when isInAnonymousFunction() is true. Used by return type - * rules to validate that the closure returns the correct type. - */ public function getAnonymousFunctionReturnType(): ?Type; /** - * Returns the type of a PHP expression at this point in the analysis. - * - * This is the most important method on Scope. It evaluates the type of any - * expression AST node, taking into account all type information available at - * the current analysis position — variable assignments, type narrowing from - * conditions, PHPDoc annotations, and more. - * - * The returned type reflects PHPDoc-enhanced types. Use getNativeType() to get - * the type based only on PHP's native type system (typehints, assignments). - * - * Note: This method may defer evaluation until the expression's analysis is - * complete (see getScopeType() for cases where immediate evaluation is needed). + * Returns the PHPDoc-enhanced type. Use getNativeType() for native types only. */ public function getType(Expr $node): Type; /** - * Returns the native PHP type of an expression, ignoring PHPDoc annotations. - * - * Unlike getType() which includes PHPDoc-enhanced type information (like - * generic types, more specific return types from @return tags, etc.), this - * method returns only what PHP's native type system knows. - * - * Used when you need to distinguish between what PHP enforces at runtime - * vs. what PHPDoc promises at the documentation level. + * Returns only what PHP's native type system knows, ignoring PHPDoc. */ public function getNativeType(Expr $expr): Type; /** - * Like getType(), but preserves the void type for function/method calls. - * - * Normally, getType() replaces void return types with null (since void - * functions effectively return null). This method keeps the void type, - * which is needed by return type rules that must distinguish between - * "returns null" and "returns void". + * Like getType(), but preserves void for function/method calls + * (normally getType() replaces void with null). */ public function getKeepVoidType(Expr $node): Type; /** - * Returns the type of an expression using the current scope state directly. - * - * Unlike getType(), which may defer evaluation until the expression's - * full analysis is complete (to handle cases like `doFoo($a = 1, $a)` - * where argument evaluation order matters), this method uses the scope's + * Unlike getType() which may defer evaluation, this uses the scope's * current state immediately. - * - * Use this when you intentionally want the type as it exists in the - * current scope snapshot, not the final resolved type. */ public function getScopeType(Expr $expr): Type; - /** - * Like getScopeType(), but returns the native PHP type only. - * - * Combines the immediate-evaluation behavior of getScopeType() with - * the PHPDoc-ignoring behavior of getNativeType(). - */ public function getScopeNativeType(Expr $expr): Type; /** diff --git a/src/Php/PhpVersion.php b/src/Php/PhpVersion.php index dc4c1c0463..4200917985 100644 --- a/src/Php/PhpVersion.php +++ b/src/Php/PhpVersion.php @@ -8,18 +8,9 @@ /** * Represents a specific PHP version for version-dependent analysis behavior. * - * PHPStan uses this to gate behavior based on PHP version — e.g. whether enums are - * supported (8.1+), whether property hooks exist (8.4+), whether non-standard casts - * are deprecated (8.5+), etc. - * * The version is stored as PHP_VERSION_ID format (e.g. 80100 for PHP 8.1.0). - * The source indicates where the version came from: runtime, phpstan.neon config, - * or composer.json platform config. - * - * This class provides numerous `supports*()` and `deprecates*()` methods that rules - * and extensions use to conditionally apply version-specific analysis. Extension - * developers can access it via `Scope::getPhpVersion()` (which returns PhpVersions, - * a range-aware wrapper) or by injecting PhpVersion directly. + * Extension developers can access it via `Scope::getPhpVersion()` (which returns + * PhpVersions, a range-aware wrapper) or by injecting PhpVersion directly. * * @api */ @@ -27,22 +18,14 @@ final class PhpVersion { - /** Version was detected from the running PHP runtime. */ public const SOURCE_RUNTIME = 1; - - /** Version was set in phpstan.neon configuration. */ public const SOURCE_CONFIG = 2; - - /** Version was read from config.platform.php in composer.json. */ public const SOURCE_COMPOSER_PLATFORM_PHP = 3; - - /** Version source is not known. */ public const SOURCE_UNKNOWN = 4; /** * @api - * - * @param self::SOURCE_* $source Where this version number came from + * @param self::SOURCE_* $source */ public function __construct(private int $versionId, private int $source = self::SOURCE_UNKNOWN) { diff --git a/src/Php/PhpVersions.php b/src/Php/PhpVersions.php index 8a61abd56d..a6849c0584 100644 --- a/src/Php/PhpVersions.php +++ b/src/Php/PhpVersions.php @@ -11,33 +11,21 @@ * * Unlike PhpVersion (which represents a single known version), PhpVersions wraps * a Type representing the possible PHP versions. When the exact version is known, - * queries return Yes/No. When a range of versions is possible (e.g. `int<80000, 80400>`), - * queries return Maybe. + * queries return Yes/No. When a range of versions is possible, queries return Maybe. * - * This is the return type of Scope::getPhpVersion(). Rules and extensions use it - * to query version-dependent features: - * - * $scope->getPhpVersion()->supportsNamedArguments() // TrinaryLogic - * - * The underlying type is an integer (range) type representing PHP_VERSION_ID values. + * This is the return type of Scope::getPhpVersion(). * * @api */ final class PhpVersions { - /** - * @param Type $phpVersions An integer type representing the possible PHP_VERSION_ID values - */ public function __construct( private Type $phpVersions, ) { } - /** - * Returns the underlying type representing the PHP version range. - */ public function getType(): Type { return $this->phpVersions; diff --git a/src/Reflection/Assertions.php b/src/Reflection/Assertions.php index 4d8f8de77f..30f9106b77 100644 --- a/src/Reflection/Assertions.php +++ b/src/Reflection/Assertions.php @@ -38,20 +38,14 @@ private function __construct(private array $asserts) { } - /** - * Returns all assert tags regardless of condition. - * - * @return AssertTag[] - */ + /** @return AssertTag[] */ public function getAll(): array { return $this->asserts; } /** - * Returns unconditional assertions (@phpstan-assert). - * - * These narrow parameter types regardless of the method's return value. + * Unconditional assertions — narrow parameter types regardless of the method's return value. * * @return AssertTag[] */ @@ -61,9 +55,7 @@ public function getAsserts(): array } /** - * Returns assertions that apply when the method returns true. - * - * Includes `@phpstan-assert-if-true` tags and negated `@phpstan-assert-if-false` tags. + * Includes @phpstan-assert-if-true tags and negated @phpstan-assert-if-false tags. * * @return AssertTag[] */ @@ -79,9 +71,7 @@ public function getAssertsIfTrue(): array } /** - * Returns assertions that apply when the method returns false. - * - * Includes `@phpstan-assert-if-false` tags and negated `@phpstan-assert-if-true` tags. + * Includes @phpstan-assert-if-false tags and negated @phpstan-assert-if-true tags. * * @return AssertTag[] */ @@ -96,14 +86,7 @@ public function getAssertsIfFalse(): array ); } - /** - * Transforms all assertion types using the given callback. - * - * Used when resolving template types — the assertion types need to be - * substituted with concrete type arguments. - * - * @param callable(Type): Type $callable - */ + /** @param callable(Type): Type $callable */ public function mapTypes(callable $callable): self { $assertTagsCallback = static fn (AssertTag $tag): AssertTag => $tag->withType($callable($tag->getType())); diff --git a/src/Reflection/AttributeReflection.php b/src/Reflection/AttributeReflection.php index 768732d565..34be236c67 100644 --- a/src/Reflection/AttributeReflection.php +++ b/src/Reflection/AttributeReflection.php @@ -20,24 +20,18 @@ final class AttributeReflection { /** - * @param string $name The fully qualified attribute class name * @param array $argumentTypes Argument types keyed by parameter name */ public function __construct(private string $name, private array $argumentTypes) { } - /** Returns the fully qualified attribute class name. */ public function getName(): string { return $this->name; } - /** - * Returns the types of the attribute's constructor arguments, keyed by parameter name. - * - * @return array - */ + /** @return array */ public function getArgumentTypes(): array { return $this->argumentTypes; diff --git a/src/Reflection/Callables/CallableParametersAcceptor.php b/src/Reflection/Callables/CallableParametersAcceptor.php index 1150dae562..139ca8a175 100644 --- a/src/Reflection/Callables/CallableParametersAcceptor.php +++ b/src/Reflection/Callables/CallableParametersAcceptor.php @@ -28,48 +28,28 @@ interface CallableParametersAcceptor extends ParametersAcceptor { - /** - * Returns the points where this callable may throw exceptions. - * - * @return SimpleThrowPoint[] - */ + /** @return SimpleThrowPoint[] */ public function getThrowPoints(): array; - /** Whether this callable is known to be pure (no side effects). */ public function isPure(): TrinaryLogic; - /** Whether this callable accepts named arguments. */ public function acceptsNamedArguments(): TrinaryLogic; - /** - * Returns the points where this callable may have side effects. - * - * @return SimpleImpurePoint[] - */ + /** @return SimpleImpurePoint[] */ public function getImpurePoints(): array; /** - * Returns expressions that become invalid after this callable is invoked. - * - * Used to track when calling a closure invalidates cached type information + * Tracks when calling a closure invalidates cached type information * for variables it captures by reference. * * @return InvalidateExprNode[] */ public function getInvalidateExpressions(): array; - /** - * Returns the names of outer-scope variables captured by this callable. - * - * Relevant for `use ($var)` in closures. - * - * @return string[] - */ + /** @return string[] */ public function getUsedVariables(): array; /** - * Whether this callable has the #[\NoDiscard] attribute. - * * On PHP 8.5+ if the return value is unused at runtime, a warning is emitted. * PHPStan reports this during analysis regardless of PHP version. */ diff --git a/src/Reflection/Callables/SimpleImpurePoint.php b/src/Reflection/Callables/SimpleImpurePoint.php index 07dc807628..6274d75ed1 100644 --- a/src/Reflection/Callables/SimpleImpurePoint.php +++ b/src/Reflection/Callables/SimpleImpurePoint.php @@ -36,9 +36,7 @@ final class SimpleImpurePoint ]; /** - * @param ImpurePointIdentifier $identifier Category of the side effect - * @param string $description Human-readable description of the impure action - * @param bool $certain Whether the side effect is certain (true) or possible (false) + * @param ImpurePointIdentifier $identifier */ public function __construct( private string $identifier, @@ -49,11 +47,7 @@ public function __construct( } /** - * Creates a SimpleImpurePoint from a function/method and its selected variant. - * * Returns null if the function is known to be pure (no side effects). - * Handles special cases like print_r() where a parameter can flip the - * function between impure (prints to output) and pure (returns string). * * @param Arg[] $args */ @@ -122,28 +116,17 @@ public static function createFromVariant(FunctionReflection|ExtendedMethodReflec return null; } - /** - * Returns the category identifier for this side effect (e.g. "functionCall", "methodCall"). - * - * @return ImpurePointIdentifier - */ + /** @return ImpurePointIdentifier */ public function getIdentifier(): string { return $this->identifier; } - /** Returns a human-readable description of the impure action. */ public function getDescription(): string { return $this->description; } - /** - * Whether the side effect is certain (vs. merely possible). - * - * Certain when the function is known to be impure (e.g. void return, or - * explicitly marked @phpstan-impure). Uncertain when purity is unknown. - */ public function isCertain(): bool { return $this->certain; diff --git a/src/Reflection/Callables/SimpleThrowPoint.php b/src/Reflection/Callables/SimpleThrowPoint.php index 37933801dc..3e7c510f81 100644 --- a/src/Reflection/Callables/SimpleThrowPoint.php +++ b/src/Reflection/Callables/SimpleThrowPoint.php @@ -28,40 +28,26 @@ private function __construct( { } - /** - * Creates a throw point from an explicit @throws annotation. - * - * @param Type $type The exception type declared in @throws - * @param bool $canContainAnyThrowable Whether the type could match any Throwable - */ public static function createExplicit(Type $type, bool $canContainAnyThrowable): self { return new self($type, true, $canContainAnyThrowable); } - /** - * Creates an implicit throw point that could throw any Throwable. - * - * Used when no @throws annotation is present and the callable is not marked pure. - */ public static function createImplicit(): self { return new self(new ObjectType(Throwable::class), false, true); } - /** Returns the type of exceptions that may be thrown. */ public function getType(): Type { return $this->type; } - /** Whether this throw point comes from an explicit @throws annotation. */ public function isExplicit(): bool { return $this->explicit; } - /** Whether the exception type is broad enough to include any Throwable. */ public function canContainAnyThrowable(): bool { return $this->canContainAnyThrowable; diff --git a/src/Reflection/ClassConstantReflection.php b/src/Reflection/ClassConstantReflection.php index 7151e8481e..9c5b9e18ff 100644 --- a/src/Reflection/ClassConstantReflection.php +++ b/src/Reflection/ClassConstantReflection.php @@ -23,36 +23,18 @@ interface ClassConstantReflection extends ClassMemberReflection, ConstantReflection { - /** - * Returns the AST expression for this constant's value. - * - * This is the raw expression from the parser, useful for rules that - * need to inspect the constant's definition. - */ public function getValueExpr(): Expr; - /** Whether this constant is declared final (PHP 8.1+). */ public function isFinal(): bool; - /** Whether this constant has a PHPDoc @var type. */ public function hasPhpDocType(): bool; - /** - * Returns the PHPDoc @var type for this constant, or null if none. - */ public function getPhpDocType(): ?Type; - /** Whether this constant has a native PHP type declaration (PHP 8.3+). */ public function hasNativeType(): bool; - /** - * Returns the native PHP type declaration, or null if none. - */ public function getNativeType(): ?Type; - /** - * Returns the resolved PHPDoc block for this constant, or null if none exists. - */ public function getResolvedPhpDoc(): ?ResolvedPhpDocBlock; } diff --git a/src/Reflection/ClassMemberAccessAnswerer.php b/src/Reflection/ClassMemberAccessAnswerer.php index ca69e6ab4e..5d19503e8e 100644 --- a/src/Reflection/ClassMemberAccessAnswerer.php +++ b/src/Reflection/ClassMemberAccessAnswerer.php @@ -20,22 +20,10 @@ interface ClassMemberAccessAnswerer { /** - * Returns whether the current analysis context is inside a class. - * - * When true, getClassReflection() is guaranteed to return non-null. - * Used to determine if protected/private members are accessible. - * * @phpstan-assert-if-true !null $this->getClassReflection() */ public function isInClass(): bool; - /** - * Returns the ClassReflection of the class the current code is in, - * or null if not inside a class. - * - * Used together with property/method/constant reflections to determine - * whether the current context has access to protected or private members. - */ public function getClassReflection(): ?ClassReflection; /** @@ -43,44 +31,12 @@ public function getClassReflection(): ?ClassReflection; */ public function canAccessProperty(PropertyReflection $propertyReflection): bool; - /** - * Returns whether the current context can read the given property. - * - * Checks visibility rules: public properties are always readable, - * protected properties are readable from the same class or subclasses, - * and private properties are only readable from the declaring class. - * - * Also accounts for PHP 8.4 asymmetric visibility where a property - * may have different read and write visibility. - */ public function canReadProperty(ExtendedPropertyReflection $propertyReflection): bool; - /** - * Returns whether the current context can write to the given property. - * - * Like canReadProperty(), but checks write visibility instead. - * With PHP 8.4 asymmetric visibility, a property like - * `public private(set) string $name` is publicly readable but only - * privately writable. - */ public function canWriteProperty(ExtendedPropertyReflection $propertyReflection): bool; - /** - * Returns whether the current context can call the given method. - * - * Checks visibility rules: public methods are always callable, - * protected methods are callable from the same class or subclasses, - * and private methods are only callable from the declaring class. - */ public function canCallMethod(MethodReflection $methodReflection): bool; - /** - * Returns whether the current context can access the given class constant. - * - * Checks visibility rules: public constants are always accessible, - * protected constants are accessible from the same class or subclasses, - * and private constants are only accessible from the declaring class. - */ public function canAccessConstant(ClassConstantReflection $constantReflection): bool; } diff --git a/src/Reflection/ClassMemberReflection.php b/src/Reflection/ClassMemberReflection.php index 8d83edc23c..eefe052541 100644 --- a/src/Reflection/ClassMemberReflection.php +++ b/src/Reflection/ClassMemberReflection.php @@ -18,27 +18,17 @@ interface ClassMemberReflection { /** - * Returns the class where this member is declared. - * * For inherited members, this returns the original declaring class, * not the class where the member was accessed. */ public function getDeclaringClass(): ClassReflection; - /** Whether this member is declared static. */ public function isStatic(): bool; - /** Whether this member has private visibility. */ public function isPrivate(): bool; - /** Whether this member has public visibility. */ public function isPublic(): bool; - /** - * Returns the raw PHPDoc comment for this member, or null if none exists. - * - * This is the unparsed comment string including the /** delimiters. - */ public function getDocComment(): ?string; } diff --git a/src/Reflection/ConstantReflection.php b/src/Reflection/ConstantReflection.php index 64cf783419..c8dcca1a2b 100644 --- a/src/Reflection/ConstantReflection.php +++ b/src/Reflection/ConstantReflection.php @@ -17,29 +17,19 @@ interface ConstantReflection { - /** Returns the constant name. */ public function getName(): string; - /** Returns the type of this constant's value. */ public function getValueType(): Type; - /** Whether this constant is marked as deprecated. */ public function isDeprecated(): TrinaryLogic; - /** Returns the deprecation message, or null if not deprecated. */ public function getDeprecatedDescription(): ?string; - /** Whether this constant is marked as @internal. */ public function isInternal(): TrinaryLogic; - /** Returns the file path where this constant is defined, or null for built-ins. */ public function getFileName(): ?string; - /** - * Returns PHP attributes on this constant. - * - * @return list - */ + /** @return list */ public function getAttributes(): array; } diff --git a/src/Reflection/ExtendedMethodReflection.php b/src/Reflection/ExtendedMethodReflection.php index 97e4581500..b959ae79d5 100644 --- a/src/Reflection/ExtendedMethodReflection.php +++ b/src/Reflection/ExtendedMethodReflection.php @@ -30,87 +30,53 @@ interface ExtendedMethodReflection extends MethodReflection { - /** - * Returns extended parameter/return type signatures with PHPDoc and native types. - * - * @return list - */ + /** @return list */ public function getVariants(): array; - /** - * Shortcut for methods with exactly one variant. - * - * @internal - */ + /** @internal */ public function getOnlyVariant(): ExtendedParametersAcceptor; /** * Returns alternative signatures used when the method is called with named arguments. - * - * Some built-in functions have different behavior with named arguments. * Returns null if the named argument variants are the same as regular variants. * * @return list|null */ public function getNamedArgumentsVariants(): ?array; - /** Whether this method accepts named arguments (PHP 8.0+). */ public function acceptsNamedArguments(): TrinaryLogic; - /** - * Returns type assertions declared via @phpstan-assert annotations. - * - * These narrow parameter or property types after the method call, - * similar to how is_string() narrows to string. - */ public function getAsserts(): Assertions; /** - * Returns the @phpstan-self-out type, if declared. - * * Used for fluent interfaces where calling a method changes the generic * type parameters of $this (e.g. a builder pattern). */ public function getSelfOutType(): ?Type; - /** Whether this method returns by reference (&). */ public function returnsByReference(): TrinaryLogic; - /** Whether this method has the `final` keyword explicitly. */ public function isFinalByKeyword(): TrinaryLogic; - /** Whether this method is abstract. */ public function isAbstract(): TrinaryLogic|bool; - /** Whether this method is a PHP built-in (not defined in userland code). */ public function isBuiltin(): TrinaryLogic|bool; /** - * Whether this method has a @phpstan-pure or @phpstan-impure annotation. - * * In most cases hasSideEffects() is more practical as it also accounts * for void return type (methods returning void are always impure). */ public function isPure(): TrinaryLogic; - /** - * Returns PHP attributes on this method. - * - * @return list - */ + /** @return list */ public function getAttributes(): array; /** - * Whether this method has the #[\NoDiscard] attribute. - * * On PHP 8.5+ if the return value is unused at runtime, a warning is emitted. * PHPStan reports this during analysis regardless of PHP version. */ public function mustUseReturnValue(): TrinaryLogic; - /** - * Returns the resolved PHPDoc block for this method, or null if none exists. - */ public function getResolvedPhpDoc(): ?ResolvedPhpDocBlock; } diff --git a/src/Reflection/ExtendedParametersAcceptor.php b/src/Reflection/ExtendedParametersAcceptor.php index 76f0fe0a07..43ebd06461 100644 --- a/src/Reflection/ExtendedParametersAcceptor.php +++ b/src/Reflection/ExtendedParametersAcceptor.php @@ -21,28 +21,13 @@ interface ExtendedParametersAcceptor extends ParametersAcceptor { - /** - * Returns extended parameter reflections with separate PHPDoc/native types. - * - * @return list - */ + /** @return list */ public function getParameters(): array; - /** - * Returns the PHPDoc @return type, separate from the native type. - */ public function getPhpDocReturnType(): Type; - /** - * Returns the native PHP return type declaration. - */ public function getNativeReturnType(): Type; - /** - * Returns the variance map for template types at the call site. - * - * Used for @template-covariant and other call-site variance specifications. - */ public function getCallSiteVarianceMap(): TemplateTypeVarianceMap; } diff --git a/src/Reflection/ExtendedPropertyReflection.php b/src/Reflection/ExtendedPropertyReflection.php index e275cdda5d..b8249987d2 100644 --- a/src/Reflection/ExtendedPropertyReflection.php +++ b/src/Reflection/ExtendedPropertyReflection.php @@ -32,82 +32,49 @@ interface ExtendedPropertyReflection extends PropertyReflection public const HOOK_SET = 'set'; - /** Returns the property name. */ public function getName(): string; - /** Whether this property has a PHPDoc @var type. */ public function hasPhpDocType(): bool; - /** - * Returns the PHPDoc @var type for this property. - * - * If no PHPDoc type exists, returns MixedType. - */ public function getPhpDocType(): Type; - /** Whether this property has a native PHP type declaration. */ public function hasNativeType(): bool; - /** - * Returns the native PHP type declaration for this property. - * - * If no native type exists, returns MixedType. - */ public function getNativeType(): Type; - /** Whether this property is abstract (requires implementation in child class). */ public function isAbstract(): TrinaryLogic; - /** Whether this property has the `final` keyword explicitly. */ public function isFinalByKeyword(): TrinaryLogic; - /** Whether this property is effectively final (by keyword or other means). */ public function isFinal(): TrinaryLogic; /** - * Whether this is a virtual property (has hooks but no backing store). - * - * Virtual properties exist only through their get/set hooks and don't - * occupy memory in the object. Introduced in PHP 8.4. + * Virtual properties (PHP 8.4+) exist only through their get/set hooks + * and don't occupy memory in the object. */ public function isVirtual(): TrinaryLogic; - /** - * Whether this property has the given hook type ('get' or 'set'). - * - * @param self::HOOK_* $hookType - */ + /** @param self::HOOK_* $hookType */ public function hasHook(string $hookType): bool; /** - * Returns the method reflection for the given hook type. - * * Property hooks (PHP 8.4+) are internally represented as methods. * * @param self::HOOK_* $hookType */ public function getHook(string $hookType): ExtendedMethodReflection; - /** Whether this property has protected(set) asymmetric visibility. */ public function isProtectedSet(): bool; - /** Whether this property has private(set) asymmetric visibility. */ public function isPrivateSet(): bool; - /** - * Returns PHP attributes on this property. - * - * @return list - */ + /** @return list */ public function getAttributes(): array; /** - * Whether this is a "dummy" property that may not actually exist. - * - * Returns no() for properties declared in code. * Returns yes() for properties that represent possibly-defined properties - * on non-final classes, mixed, object, etc. — these are placeholders - * PHPStan creates when it cannot prove a property doesn't exist. + * on non-final classes, mixed, object, etc. — placeholders PHPStan creates + * when it cannot prove a property doesn't exist. */ public function isDummy(): TrinaryLogic; diff --git a/src/Reflection/FunctionReflection.php b/src/Reflection/FunctionReflection.php index 5be9fbee46..a45c802d3f 100644 --- a/src/Reflection/FunctionReflection.php +++ b/src/Reflection/FunctionReflection.php @@ -23,95 +23,54 @@ interface FunctionReflection { - /** Returns the fully qualified function name. */ public function getName(): string; - /** Returns the file path where this function is defined, or null for built-ins. */ public function getFileName(): ?string; - /** - * Returns the function's parameter/return type signatures (one or more variants). - * - * @return list - */ + /** @return list */ public function getVariants(): array; - /** - * Shortcut for functions with exactly one variant. - * - * @internal - */ + /** @internal */ public function getOnlyVariant(): ExtendedParametersAcceptor; /** * Returns alternative signatures used when the function is called with named arguments. - * * Returns null if the named argument variants are the same as regular variants. * * @return list|null */ public function getNamedArgumentsVariants(): ?array; - /** Whether this function accepts named arguments (PHP 8.0+). */ public function acceptsNamedArguments(): TrinaryLogic; - /** Whether this function is marked as deprecated. */ public function isDeprecated(): TrinaryLogic; - /** Returns the deprecation message, or null if not deprecated. */ public function getDeprecatedDescription(): ?string; - /** Whether this function is marked as @internal. */ public function isInternal(): TrinaryLogic; - /** - * Returns the type of exceptions this function throws, or null if unknown. - * - * Comes from @throws PHPDoc tag. - */ public function getThrowType(): ?Type; - /** - * Whether this function has side effects (is impure). - * - * @see MethodReflection::hasSideEffects() for semantics. - */ public function hasSideEffects(): TrinaryLogic; - /** Whether this is a PHP built-in function (not defined in userland code). */ public function isBuiltin(): bool; - /** - * Returns type assertions declared via @phpstan-assert annotations. - */ public function getAsserts(): Assertions; - /** - * Returns the raw PHPDoc comment, or null if none exists. - */ public function getDocComment(): ?string; - /** Whether this function returns by reference (&). */ public function returnsByReference(): TrinaryLogic; /** - * Whether this function has a @phpstan-pure or @phpstan-impure annotation. - * * In most cases hasSideEffects() is more practical as it also accounts * for void return type (functions returning void are always impure). */ public function isPure(): TrinaryLogic; - /** - * Returns PHP attributes on this function. - * - * @return list - */ + /** @return list */ public function getAttributes(): array; /** - * Whether this function has the #[\NoDiscard] attribute. - * * On PHP 8.5+ if the return value is unused at runtime, a warning is emitted. * PHPStan reports this during analysis regardless of PHP version. */ diff --git a/src/Reflection/MethodReflection.php b/src/Reflection/MethodReflection.php index 790c9396e9..524fa98bfc 100644 --- a/src/Reflection/MethodReflection.php +++ b/src/Reflection/MethodReflection.php @@ -23,20 +23,15 @@ interface MethodReflection extends ClassMemberReflection { - /** Returns the method name. */ public function getName(): string; /** - * Returns the prototype (original declaration) of this method. - * * For methods that override a parent method, this returns the parent's * method reflection. For methods with no parent, returns itself. */ public function getPrototype(): ClassMemberReflection; /** - * Returns the method's parameter/return type signatures (one or more variants). - * * Most methods have a single variant. Built-in PHP functions with overloaded * signatures (e.g. different return types based on argument count) have multiple. * @@ -44,31 +39,19 @@ public function getPrototype(): ClassMemberReflection; */ public function getVariants(): array; - /** Whether this method is marked as deprecated. */ public function isDeprecated(): TrinaryLogic; - /** Returns the deprecation message, or null if not deprecated. */ public function getDeprecatedDescription(): ?string; - /** Whether this method is final. */ public function isFinal(): TrinaryLogic; - /** Whether this method is marked as @internal. */ public function isInternal(): TrinaryLogic; - /** - * Returns the type of exceptions this method throws, or null if unknown. - * - * Comes from @throws PHPDoc tag. - */ public function getThrowType(): ?Type; /** - * Whether this method has side effects (is impure). - * - * Returns Yes for methods known to be impure, No for pure methods, - * and Maybe when purity cannot be determined. Void methods are always - * considered impure since they must do something to be useful. + * Void methods are always considered impure since they must do something + * to be useful. */ public function hasSideEffects(): TrinaryLogic; diff --git a/src/Reflection/NamespaceAnswerer.php b/src/Reflection/NamespaceAnswerer.php index a4e46cef05..2a3ac2d40a 100644 --- a/src/Reflection/NamespaceAnswerer.php +++ b/src/Reflection/NamespaceAnswerer.php @@ -13,11 +13,7 @@ interface NamespaceAnswerer { - /** - * Returns the current namespace, or null if in the global namespace. - * - * @return non-empty-string|null - */ + /** @return non-empty-string|null */ public function getNamespace(): ?string; } diff --git a/src/Reflection/ParameterReflection.php b/src/Reflection/ParameterReflection.php index 24c2031fc0..bebd231c94 100644 --- a/src/Reflection/ParameterReflection.php +++ b/src/Reflection/ParameterReflection.php @@ -20,31 +20,16 @@ interface ParameterReflection { - /** Returns the parameter name (without the $ prefix). */ public function getName(): string; - /** - * Whether this parameter is optional (has a default value or is variadic). - */ public function isOptional(): bool; - /** - * Returns the parameter's type (combined PHPDoc + native type). - */ public function getType(): Type; - /** - * Returns how this parameter is passed: by value, by reference (reads existing), - * or by reference (creates new variable). - */ public function passedByReference(): PassedByReference; - /** Whether this parameter is variadic (...$param). */ public function isVariadic(): bool; - /** - * Returns the type of the default value, or null if the parameter has no default. - */ public function getDefaultValue(): ?Type; } diff --git a/src/Reflection/ParametersAcceptor.php b/src/Reflection/ParametersAcceptor.php index 8457bbc1dd..f4c116ade2 100644 --- a/src/Reflection/ParametersAcceptor.php +++ b/src/Reflection/ParametersAcceptor.php @@ -24,44 +24,25 @@ interface ParametersAcceptor { - /** - * Functions that access variadic arguments implicitly. - * Used by PHPStan to detect implicit variadic behavior. - */ public const VARIADIC_FUNCTIONS = [ 'func_get_args', 'func_get_arg', 'func_num_args', ]; - /** - * Returns the template type parameters declared on this signature. - * - * Maps template names to their bound types (e.g. @template T of object). - */ public function getTemplateTypeMap(): TemplateTypeMap; /** - * Returns the template type map with types resolved from the call site. - * * After template type inference at a call site, this map contains the * concrete types inferred for each template parameter. */ public function getResolvedTemplateTypeMap(): TemplateTypeMap; - /** - * Returns the list of parameters in this signature. - * - * @return list - */ + /** @return list */ public function getParameters(): array; - /** Whether this signature accepts additional arguments (is variadic). */ public function isVariadic(): bool; - /** - * Returns the return type of this signature (combined PHPDoc + native type). - */ public function getReturnType(): Type; } diff --git a/src/Reflection/PassedByReference.php b/src/Reflection/PassedByReference.php index 76883313a4..6e31c54f1f 100644 --- a/src/Reflection/PassedByReference.php +++ b/src/Reflection/PassedByReference.php @@ -46,40 +46,26 @@ private static function create(int $value): self return self::$registry[$value]; } - /** Parameter is passed by value. */ public static function createNo(): self { return self::create(self::NO); } - /** - * Parameter is passed by reference and may create the variable. - * - * The variable doesn't need to exist before the call — the function - * will create/initialize it. - */ public static function createCreatesNewVariable(): self { return self::create(self::CREATES_NEW_VARIABLE); } - /** - * Parameter is passed by reference and reads the existing variable. - * - * The variable should already exist before the call. - */ public static function createReadsArgument(): self { return self::create(self::READS_ARGUMENT); } - /** Returns true if the parameter is passed by value (not by reference). */ public function no(): bool { return $this->value === self::NO; } - /** Returns true if the parameter is passed by reference (either mode). */ public function yes(): bool { return !$this->no(); @@ -90,17 +76,12 @@ public function equals(self $other): bool return $this->value === $other->value; } - /** Returns true if this is the "creates new variable" by-reference mode. */ public function createsNewVariable(): bool { return $this->value === self::CREATES_NEW_VARIABLE; } - /** - * Combines two PassedByReference values, returning the stronger one. - * - * CreatesNewVariable > ReadsArgument > No. - */ + /** CreatesNewVariable > ReadsArgument > No. */ public function combine(self $other): self { if ($this->value > $other->value) { diff --git a/src/Reflection/PropertyReflection.php b/src/Reflection/PropertyReflection.php index 9b94c4ca2c..09cee4d8ed 100644 --- a/src/Reflection/PropertyReflection.php +++ b/src/Reflection/PropertyReflection.php @@ -24,42 +24,28 @@ interface PropertyReflection extends ClassMemberReflection { - /** - * Returns the type seen when reading from this property. - * - * This is the combined PHPDoc + native type that PHPStan uses for analysis. - */ public function getReadableType(): Type; /** - * Returns the type accepted when writing to this property. - * * May differ from the readable type for properties with asymmetric visibility * or property hooks with different get/set types. */ public function getWritableType(): Type; /** - * Whether the property's type can change after assignment. - * * Returns false for typed properties (which always retain their declared type) * and true for untyped properties (which take on the type of whatever is assigned). */ public function canChangeTypeAfterAssignment(): bool; - /** Whether this property can be read from. */ public function isReadable(): bool; - /** Whether this property can be written to. */ public function isWritable(): bool; - /** Whether this property is marked as deprecated. */ public function isDeprecated(): TrinaryLogic; - /** Returns the deprecation message, or null if not deprecated. */ public function getDeprecatedDescription(): ?string; - /** Whether this property is marked as @internal. */ public function isInternal(): TrinaryLogic; } diff --git a/src/Reflection/Type/UnresolvedMethodPrototypeReflection.php b/src/Reflection/Type/UnresolvedMethodPrototypeReflection.php index fef79c9ebf..fbebf595ac 100644 --- a/src/Reflection/Type/UnresolvedMethodPrototypeReflection.php +++ b/src/Reflection/Type/UnresolvedMethodPrototypeReflection.php @@ -23,15 +23,8 @@ interface UnresolvedMethodPrototypeReflection { - /** - * Returns a new instance that keeps template types unresolved instead of - * falling back to their bounds. Used during type inference. - */ public function doNotResolveTemplateTypeMapToBounds(): self; - /** - * Returns the method reflection without any template type substitution. - */ public function getNakedMethod(): ExtendedMethodReflection; /** @@ -40,11 +33,6 @@ public function getNakedMethod(): ExtendedMethodReflection; */ public function getTransformedMethod(): ExtendedMethodReflection; - /** - * Returns a new instance configured for the given called-on type. - * - * The called-on type provides the generic arguments used for template substitution. - */ public function withCalledOnType(Type $type): self; } diff --git a/src/Reflection/Type/UnresolvedPropertyPrototypeReflection.php b/src/Reflection/Type/UnresolvedPropertyPrototypeReflection.php index 456779a5a6..9d3fca54e9 100644 --- a/src/Reflection/Type/UnresolvedPropertyPrototypeReflection.php +++ b/src/Reflection/Type/UnresolvedPropertyPrototypeReflection.php @@ -25,15 +25,8 @@ interface UnresolvedPropertyPrototypeReflection { - /** - * Returns a new instance that keeps template types unresolved instead of - * falling back to their bounds. Used during type inference. - */ public function doNotResolveTemplateTypeMapToBounds(): self; - /** - * Returns the property reflection without any template type substitution. - */ public function getNakedProperty(): ExtendedPropertyReflection; /** @@ -42,11 +35,6 @@ public function getNakedProperty(): ExtendedPropertyReflection; */ public function getTransformedProperty(): ExtendedPropertyReflection; - /** - * Returns a new instance configured for the given fetched-on type. - * - * The fetched-on type provides the generic arguments used for template substitution. - */ public function withFechedOnType(Type $type): self; } diff --git a/src/TrinaryLogic.php b/src/TrinaryLogic.php index e82885cbc2..e628893e96 100644 --- a/src/TrinaryLogic.php +++ b/src/TrinaryLogic.php @@ -50,44 +50,21 @@ private function __construct(private int $value) { } - /** - * Creates a TrinaryLogic representing definite truth. - * - * Use when the answer is unconditionally true — e.g. `StringType::isString()` - * returns `TrinaryLogic::createYes()`. - */ public static function createYes(): self { return self::$registry[self::YES] ??= new self(self::YES); } - /** - * Creates a TrinaryLogic representing definite falsehood. - * - * Use when the answer is unconditionally false — e.g. `IntegerType::isString()` - * returns `TrinaryLogic::createNo()`. - */ public static function createNo(): self { return self::$registry[self::NO] ??= new self(self::NO); } - /** - * Creates a TrinaryLogic representing uncertainty. - * - * Use when the answer cannot be determined statically — e.g. `MixedType::isString()` - * returns `TrinaryLogic::createMaybe()` because mixed could be a string at runtime. - */ public static function createMaybe(): self { return self::$registry[self::MAYBE] ??= new self(self::MAYBE); } - /** - * Converts a boolean to TrinaryLogic (true → Yes, false → No). - * - * Useful when the answer is definitively known but comes from a boolean expression. - */ public static function createFromBoolean(bool $value): self { $yesNo = $value ? self::YES : self::NO; @@ -101,8 +78,6 @@ private static function create(int $value): self } /** - * Returns true if this represents definite truth (Yes). - * * @phpstan-assert-if-true =false $this->no() * @phpstan-assert-if-true =false $this->maybe() */ @@ -112,8 +87,6 @@ public function yes(): bool } /** - * Returns true if this represents uncertainty (Maybe). - * * @phpstan-assert-if-true =false $this->no() * @phpstan-assert-if-true =false $this->yes() */ @@ -123,8 +96,6 @@ public function maybe(): bool } /** - * Returns true if this represents definite falsehood (No). - * * @phpstan-assert-if-true =false $this->maybe() * @phpstan-assert-if-true =false $this->yes() */ @@ -133,12 +104,6 @@ public function no(): bool return $this->value === self::NO; } - /** - * Converts this TrinaryLogic to a BooleanType. - * - * Yes → ConstantBooleanType(true), No → ConstantBooleanType(false), - * Maybe → BooleanType (either true or false). - */ public function toBooleanType(): BooleanType { if ($this->value === self::MAYBE) { @@ -148,11 +113,6 @@ public function toBooleanType(): BooleanType return new ConstantBooleanType($this->value === self::YES); } - /** - * Logical AND — returns the minimum of all operands. - * - * Truth table: Yes ∧ Yes = Yes, Yes ∧ Maybe = Maybe, anything ∧ No = No. - */ public function and(self ...$operands): self { $min = $this->value; @@ -167,11 +127,6 @@ public function and(self ...$operands): self } /** - * Lazy logical AND that short-circuits on No. - * - * Evaluates callbacks only until a No result is found, then stops. - * More efficient than computing all results when early termination is likely. - * * @template T * @param T[] $objects * @param callable(T): self $callback @@ -198,11 +153,6 @@ public function lazyAnd( return $this->and(...$results); } - /** - * Logical OR — returns the maximum of all operands. - * - * Truth table: No ∨ No = No, No ∨ Maybe = Maybe, anything ∨ Yes = Yes. - */ public function or(self ...$operands): self { $max = $this->value; @@ -217,10 +167,6 @@ public function or(self ...$operands): self } /** - * Lazy logical OR that short-circuits on Yes. - * - * Evaluates callbacks only until a Yes result is found, then stops. - * * @template T * @param T[] $objects * @param callable(T): self $callback @@ -248,10 +194,7 @@ public function lazyOr( } /** - * Returns Yes if all operands are identical, No if all are identical and No, Maybe otherwise. - * - * Used when combining results from multiple sources where they must all agree. - * If any two operands differ, the result is Maybe. + * Returns the operands' value if they all agree, Maybe if any differ. */ public static function extremeIdentity(self ...$operands): self { @@ -265,8 +208,6 @@ public static function extremeIdentity(self ...$operands): self } /** - * Lazy version of extremeIdentity() that short-circuits when operands disagree. - * * @template T * @param T[] $objects * @param callable(T): self $callback @@ -299,9 +240,6 @@ public static function lazyExtremeIdentity( /** * Returns Yes if any operand is Yes, otherwise the minimum. - * - * Useful for combining results where a single Yes is sufficient to - * confirm, but No requires all operands to be No. */ public static function maxMin(self ...$operands): self { @@ -313,8 +251,6 @@ public static function maxMin(self ...$operands): self } /** - * Lazy version of maxMin() that short-circuits on Yes. - * * @template T * @param T[] $objects * @param callable(T): self $callback @@ -337,29 +273,18 @@ public static function lazyMaxMin( return self::maxMin(...$results); } - /** - * Logical negation — Yes becomes No, No becomes Yes, Maybe stays Maybe. - */ public function negate(): self { return self::create(-$this->value); } - /** - * Returns true if both TrinaryLogic values are the same state. - * - * Uses identity comparison since TrinaryLogic is a flyweight. - */ public function equals(self $other): bool { return $this === $other; } /** - * Returns the stronger of the two values, or null if they are equal. - * - * Yes > Maybe > No. Used when determining which branch provides - * more information about a type. + * Returns the stronger of the two values, or null if they are equal (Yes > Maybe > No). */ public function compareTo(self $other): ?self { @@ -372,9 +297,6 @@ public function compareTo(self $other): ?self return null; } - /** - * Returns a human-readable label: "Yes", "No", or "Maybe". - */ public function describe(): string { static $labels = [ diff --git a/src/Type/AcceptsResult.php b/src/Type/AcceptsResult.php index 0a28242f50..3696e43717 100644 --- a/src/Type/AcceptsResult.php +++ b/src/Type/AcceptsResult.php @@ -41,8 +41,6 @@ public function __construct( } /** - * Returns true if the type is definitely accepted. - * * @phpstan-assert-if-true =false $this->no() * @phpstan-assert-if-true =false $this->maybe() */ @@ -52,8 +50,6 @@ public function yes(): bool } /** - * Returns true if acceptance is uncertain. - * * @phpstan-assert-if-true =false $this->no() * @phpstan-assert-if-true =false $this->yes() */ @@ -63,8 +59,6 @@ public function maybe(): bool } /** - * Returns true if the type is definitely not accepted. - * * @phpstan-assert-if-true =false $this->maybe() * @phpstan-assert-if-true =false $this->yes() */ @@ -73,37 +67,27 @@ public function no(): bool return $this->result->no(); } - /** Creates a definite acceptance result with no reasons. */ public static function createYes(): self { return new self(TrinaryLogic::createYes(), []); } - /** - * Creates a definite rejection result with optional reasons. - * - * @param list $reasons - */ + /** @param list $reasons */ public static function createNo(array $reasons = []): self { return new self(TrinaryLogic::createNo(), $reasons); } - /** Creates an uncertain acceptance result with no reasons. */ public static function createMaybe(): self { return new self(TrinaryLogic::createMaybe(), []); } - /** Converts a boolean to an AcceptsResult (true → Yes, false → No). */ public static function createFromBoolean(bool $value): self { return new self(TrinaryLogic::createFromBoolean($value), []); } - /** - * Logical AND — combines this result with another, merging reasons. - */ public function and(self $other): self { return new self( @@ -112,9 +96,6 @@ public function and(self $other): self ); } - /** - * Logical OR — combines this result with another, merging reasons. - */ public function or(self $other): self { return new self( @@ -123,14 +104,7 @@ public function or(self $other): self ); } - /** - * Transforms all reason strings using the given callback. - * - * Useful for adding context to reasons, e.g. wrapping them in - * "Parameter $foo: ..." format. - * - * @param callable(string): string $cb - */ + /** @param callable(string): string $cb */ public function decorateReasons(callable $cb): self { $reasons = []; @@ -141,11 +115,7 @@ public function decorateReasons(callable $cb): self return new self($this->result, $reasons); } - /** - * Returns Yes if all operands agree, Maybe if any disagree. - * - * @see TrinaryLogic::extremeIdentity() - */ + /** @see TrinaryLogic::extremeIdentity() */ public static function extremeIdentity(self ...$operands): self { if ($operands === []) { @@ -163,11 +133,7 @@ public static function extremeIdentity(self ...$operands): self return new self($result, array_values(array_unique($reasons))); } - /** - * Returns Yes if any operand is Yes, otherwise the minimum. - * - * @see TrinaryLogic::maxMin() - */ + /** @see TrinaryLogic::maxMin() */ public static function maxMin(self ...$operands): self { if ($operands === []) { diff --git a/src/Type/ConstantScalarType.php b/src/Type/ConstantScalarType.php index 3ba36599ec..bbd2c22e9e 100644 --- a/src/Type/ConstantScalarType.php +++ b/src/Type/ConstantScalarType.php @@ -6,17 +6,7 @@ * A type whose value is known at analysis time — a compile-time constant scalar. * * Implemented by ConstantIntegerType, ConstantFloatType, ConstantStringType, - * ConstantBooleanType, and NullType. These types represent specific known values - * rather than just their general category. - * - * For example, ConstantStringType('hello') represents the specific string 'hello', - * while StringType represents any string. - * - * PHPStan tracks constant values to enable precise analysis of: - * - Array shapes (constant string keys) - * - Switch/match exhaustiveness - * - String operations with known inputs - * - Arithmetic with known values + * ConstantBooleanType, and NullType. * * Use Type::isConstantValue() to check if a type is constant without instanceof, * and Type::getConstantScalarTypes() to extract constant types from unions. @@ -26,11 +16,7 @@ interface ConstantScalarType extends Type { - /** - * Returns the actual PHP value this type represents. - * - * @return int|float|string|bool|null - */ + /** @return int|float|string|bool|null */ public function getValue(); } diff --git a/src/Type/GeneralizePrecision.php b/src/Type/GeneralizePrecision.php index c4c7db305d..2b4595470b 100644 --- a/src/Type/GeneralizePrecision.php +++ b/src/Type/GeneralizePrecision.php @@ -42,31 +42,19 @@ private static function create(int $value): self return self::$registry[$value]; } - /** - * Aggressive generalization — constant values become their general types. - * - * @api - */ + /** @api */ public static function lessSpecific(): self { return self::create(self::LESS_SPECIFIC); } - /** - * Preserves more detail during generalization. - * - * @api - */ + /** @api */ public static function moreSpecific(): self { return self::create(self::MORE_SPECIFIC); } - /** - * Used when generalizing template type arguments. - * - * @api - */ + /** @api */ public static function templateArgument(): self { return self::create(self::TEMPLATE_ARGUMENT); diff --git a/src/Type/Generic/TemplateTypeMap.php b/src/Type/Generic/TemplateTypeMap.php index 4b6b3b4d7f..356772085a 100644 --- a/src/Type/Generic/TemplateTypeMap.php +++ b/src/Type/Generic/TemplateTypeMap.php @@ -49,9 +49,6 @@ public function __construct(private array $types, private array $lowerBoundTypes { } - /** - * Moves all upper-bound types to lower-bound types, intersecting with existing lower bounds. - */ public function convertToLowerBoundTypes(): self { $lowerBoundTypes = $this->types; @@ -70,7 +67,6 @@ public function convertToLowerBoundTypes(): self return new self([], $lowerBoundTypes); } - /** Returns a shared empty TemplateTypeMap instance. */ public static function createEmpty(): self { $empty = self::$empty; @@ -95,11 +91,7 @@ public function count(): int return count($this->types + $this->lowerBoundTypes); } - /** - * Returns all template type bindings (upper-bound types with lower-bound fallbacks). - * - * @return array - */ + /** @return array */ public function getTypes(): array { $types = $this->types; @@ -143,11 +135,6 @@ public function unsetType(string $name): self return new self($types, $lowerBoundTypes); } - /** - * Unions this map with another — for each template, the types are unioned. - * - * Used when merging type information from different branches (e.g. if/else). - */ public function union(self $other): self { $result = $this->types; @@ -204,11 +191,6 @@ public function benevolentUnion(self $other): self return new self($result, $resultLowerBoundTypes); } - /** - * Intersects this map with another — for each template, the types are intersected. - * - * Used when combining constraints from multiple sources. - */ public function intersect(self $other): self { $result = $this->types; @@ -245,11 +227,7 @@ public function map(callable $cb): self } /** - * Replaces any unresolved TemplateType values with their declared bounds. - * - * For `@template T of Countable`, if T is still unresolved, it becomes `Countable`. - * If a default type is specified (`@template T of Countable = array`), uses the default. - * Result is cached. + * Replaces unresolved TemplateType values with their declared bounds (or defaults). */ public function resolveToBounds(): self { diff --git a/src/Type/Generic/TemplateTypeReference.php b/src/Type/Generic/TemplateTypeReference.php index e1ca98ff6f..1bf0b087fa 100644 --- a/src/Type/Generic/TemplateTypeReference.php +++ b/src/Type/Generic/TemplateTypeReference.php @@ -23,18 +23,11 @@ public function __construct(private TemplateType $type, private TemplateTypeVari { } - /** Returns the template type being referenced. */ public function getType(): TemplateType { return $this->type; } - /** - * Returns the variance of the position where this template type appears. - * - * For example, in `function foo(): T`, T is in a covariant position. - * In `function foo(T $x)`, T is in a contravariant position. - */ public function getPositionVariance(): TemplateTypeVariance { return $this->positionVariance; diff --git a/src/Type/Generic/TemplateTypeVariance.php b/src/Type/Generic/TemplateTypeVariance.php index e032a25ba3..35536317dd 100644 --- a/src/Type/Generic/TemplateTypeVariance.php +++ b/src/Type/Generic/TemplateTypeVariance.php @@ -110,13 +110,6 @@ public function bivariant(): bool return $this->value === self::BIVARIANT; } - /** - * Composes two variances together for nested generic types. - * - * For example, if a type appears in a contravariant position inside a - * covariant container, the effective variance is contravariant. - * Composition rules follow standard type theory. - */ public function compose(self $other): self { if ($this->contravariant()) { @@ -156,14 +149,6 @@ public function compose(self $other): self return $other; } - /** - * Checks whether two types satisfy this variance constraint. - * - * For invariant: types must be equal. - * For covariant: $a must be a supertype of $b. - * For contravariant: $b must be a supertype of $a. - * For bivariant: always valid. - */ public function isValidVariance(TemplateType $templateType, Type $a, Type $b): IsSuperTypeOfResult { if ($b instanceof NeverType) { diff --git a/src/Type/IsSuperTypeOfResult.php b/src/Type/IsSuperTypeOfResult.php index ca682028fe..24bdebc5c7 100644 --- a/src/Type/IsSuperTypeOfResult.php +++ b/src/Type/IsSuperTypeOfResult.php @@ -43,8 +43,6 @@ public function __construct( } /** - * Returns true if this type is definitely a supertype. - * * @phpstan-assert-if-true =false $this->no() * @phpstan-assert-if-true =false $this->maybe() */ @@ -54,8 +52,6 @@ public function yes(): bool } /** - * Returns true if the supertype relationship is uncertain. - * * @phpstan-assert-if-true =false $this->no() * @phpstan-assert-if-true =false $this->yes() */ @@ -65,8 +61,6 @@ public function maybe(): bool } /** - * Returns true if this type is definitely not a supertype. - * * @phpstan-assert-if-true =false $this->maybe() * @phpstan-assert-if-true =false $this->yes() */ @@ -75,47 +69,32 @@ public function no(): bool return $this->result->no(); } - /** Creates a definite supertype result with no reasons. */ public static function createYes(): self { return new self(TrinaryLogic::createYes(), []); } - /** - * Creates a definite non-supertype result with optional reasons. - * - * @param list $reasons - */ + /** @param list $reasons */ public static function createNo(array $reasons = []): self { return new self(TrinaryLogic::createNo(), $reasons); } - /** Creates an uncertain supertype result with no reasons. */ public static function createMaybe(): self { return new self(TrinaryLogic::createMaybe(), []); } - /** Converts a boolean to an IsSuperTypeOfResult (true → Yes, false → No). */ public static function createFromBoolean(bool $value): self { return new self(TrinaryLogic::createFromBoolean($value), []); } - /** - * Converts this to an AcceptsResult, preserving the result and reasons. - * - * Used when an isSuperTypeOf() check is sufficient for an accepts() implementation. - */ public function toAcceptsResult(): AcceptsResult { return new AcceptsResult($this->result, $this->reasons); } - /** - * Logical AND — combines with other results, merging reasons. - */ public function and(self ...$others): self { $results = []; @@ -131,9 +110,6 @@ public function and(self ...$others): self ); } - /** - * Logical OR — combines with other results, merging reasons. - */ public function or(self ...$others): self { $results = []; @@ -149,11 +125,7 @@ public function or(self ...$others): self ); } - /** - * Transforms all reason strings using the given callback. - * - * @param callable(string): string $cb - */ + /** @param callable(string): string $cb */ public function decorateReasons(callable $cb): self { $reasons = []; @@ -164,11 +136,7 @@ public function decorateReasons(callable $cb): self return new self($this->result, $reasons); } - /** - * Returns Yes if all operands agree, Maybe if any disagree. - * - * @see TrinaryLogic::extremeIdentity() - */ + /** @see TrinaryLogic::extremeIdentity() */ public static function extremeIdentity(self ...$operands): self { if ($operands === []) { @@ -180,11 +148,7 @@ public static function extremeIdentity(self ...$operands): self return new self($result, self::mergeReasons($operands)); } - /** - * Returns Yes if any operand is Yes, otherwise the minimum. - * - * @see TrinaryLogic::maxMin() - */ + /** @see TrinaryLogic::maxMin() */ public static function maxMin(self ...$operands): self { if ($operands === []) { @@ -196,18 +160,11 @@ public static function maxMin(self ...$operands): self return new self($result, self::mergeReasons($operands)); } - /** - * Logical negation — Yes becomes No and vice versa, Maybe stays Maybe. - * Reasons are preserved. - */ public function negate(): self { return new self($this->result->negate(), $this->reasons); } - /** - * Returns a human-readable label: "Yes", "No", or "Maybe". - */ public function describe(): string { return $this->result->describe(); diff --git a/src/Type/Type.php b/src/Type/Type.php index 0e14cecf0b..2515e9c46b 100644 --- a/src/Type/Type.php +++ b/src/Type/Type.php @@ -47,200 +47,74 @@ interface Type { /** - * Returns all class names referenced anywhere in this type, recursively. + * Returns all class names referenced anywhere in this type, recursively + * (including generic arguments, callable signatures, etc.). * - * Includes class names from object types, generic type arguments, callable - * signatures, conditional type branches, and any other nested positions. - * - * Used for dependency tracking and PHPDoc validation to ensure all referenced - * classes exist. - * - * @see Type::getObjectClassNames() for only the direct object type class names + * @see Type::getObjectClassNames() for only direct object type class names * * @return list */ public function getReferencedClasses(): array; /** - * Returns class names of the object types represented by this type. - * - * Unlike getReferencedClasses(), this only returns class names of actual - * object types — not classes referenced in generic arguments, callable - * signatures, or other nested positions. - * - * For a union type like Foo|Bar, returns ['Foo', 'Bar']. - * For a non-object type like string, returns []. + * Returns class names of the object types this type directly represents. + * Unlike getReferencedClasses(), excludes classes in generic arguments, etc. * * @return list */ public function getObjectClassNames(): array; - /** - * Returns ClassReflection instances for the object types represented by this type. - * - * Like getObjectClassNames() but returns full ClassReflection objects, - * giving access to methods, properties, constants, parent classes, interfaces, - * generics, PHPDocs, and attributes. - * - * @return list - */ + /** @return list */ public function getObjectClassReflections(): array; /** - * Returns the object type for a class-string type or a literal class name string. - * - * For class-string, returns the object type Foo. - * For a literal string 'Foo' where Foo is a valid class, returns the object type Foo. + * Returns the object type for a class-string or literal class name string. * For non-class-string types, returns ErrorType. - * - * Used in contexts like `new $className()` where a class-string is instantiated. */ public function getClassStringObjectType(): Type; /** - * Returns the object type for class-string types, literal class name strings, - * and object types themselves. - * * Like getClassStringObjectType(), but also returns object types as-is. - * For class-string, returns the object type Foo. - * For a literal string 'Foo' (if valid class), returns the object type Foo. - * For an object type Foo, returns the object type Foo. - * - * Used in contexts like static method/property access where the left side - * can be either a class-string or an object: `$classOrObject::method()`. + * Used for `$classOrObject::method()` where the left side can be either. */ public function getObjectTypeOrClassStringObjectType(): Type; - /** - * Returns whether this type is an object type. - * - * For ObjectType and its subtypes, returns yes. - * For union types, returns yes if all members are objects, - * maybe if some are, no if none are. - * For non-object types (scalars, arrays, etc.), returns no. - */ public function isObject(): TrinaryLogic; - /** - * Returns whether this type is an enum type. - * - * Returns yes for enum types and enum case types. - * Returns maybe for generic object types that might be enums. - * Returns no for non-enum types. - */ public function isEnum(): TrinaryLogic; - /** - * Returns the array type instances contained in this type. - * - * For a union type like array|array, returns both array types. - * For non-array types, returns an empty array. - * - * Used when you need to iterate over each array component separately, - * such as when computing array operation results. - * - * @see Type::getConstantArrays() for only array shapes with known structure - * - * @return list - */ + /** @return list */ public function getArrays(): array; /** - * Returns the constant array type instances (array shapes) contained in this type. - * - * Unlike getArrays(), only returns ConstantArrayType instances — arrays with - * known keys and value types like array{name: string, age: int}. - * Generic array types like array are excluded. + * Only ConstantArrayType instances (array shapes with known keys). * * @return list */ public function getConstantArrays(): array; - /** - * Returns the constant string type instances contained in this type. - * - * For a union type like 'foo'|'bar', returns both constant string types. - * For a generic string type, returns an empty array. - * - * Used when you need the actual literal string values, for example - * when evaluating string functions on known values. - * - * @return list - */ + /** @return list */ public function getConstantStrings(): array; /** - * Checks whether this type accepts the given type for assignment or parameter passing. - * - * This is the method used by rules to validate that a value can be passed to a - * parameter, assigned to a typed property, or returned from a typed function. - * * Unlike isSuperTypeOf(), accepts() takes into account PHP's implicit type coercion. * With $strictTypes = false, int is accepted by float, and Stringable objects are - * accepted by string. With $strictTypes = true, the behavior is closer to isSuperTypeOf(). - * - * Returns AcceptsResult with TrinaryLogic (yes/maybe/no) and optional reasons - * explaining why the type was not accepted, for better error messages. + * accepted by string. */ public function accepts(Type $type, bool $strictTypes): AcceptsResult; /** - * Checks whether this type is a supertype of the given type. - * - * This is the fundamental type relationship method. It answers the question: * "Does every value of $type belong to $this type?" * - * Examples: - * (new StringType())->isSuperTypeOf(new ConstantStringType('foo')) // yes - * (new StringType())->isSuperTypeOf(new IntegerType()) // no - * (new StringType())->isSuperTypeOf(new MixedType()) // maybe - * - * Returns IsSuperTypeOfResult with TrinaryLogic (yes/maybe/no) and optional - * reasons. Use ->yes(), ->maybe(), ->no() to check the result. - * - * This method is preferable to instanceof checks because it correctly handles + * Preferable to instanceof checks because it correctly handles * union types, intersection types, and all other composite types. */ public function isSuperTypeOf(Type $type): IsSuperTypeOfResult; - /** - * Checks whether two types are structurally equal. - * - * Returns true only if the types are exactly the same — not merely compatible. - * Unlike isSuperTypeOf(), this is a strict binary check (true/false). - * - * For example, int|float is NOT equal to int (but int|float is a supertype of int). - * - * Used for cache validation, result deduplication, and checking if a type changed - * across analysis iterations. - */ public function equals(Type $type): bool; - /** - * Returns a human-readable string representation of this type. - * - * The verbosity level controls how much detail is included: - * - VerbosityLevel::typeOnly(): Simple type name (e.g. "string", "Foo") - * - VerbosityLevel::value(): Includes constant values (e.g. "'hello'", "1|2|3") - * - VerbosityLevel::precise(): Full details including accessory types (e.g. "non-empty-list") - * - VerbosityLevel::cache(): Most detailed, for internal caching purposes - * - * Used in error messages, debugging output, and test assertions. - * Use VerbosityLevel::getRecommendedLevelByType() to choose the appropriate level - * for error messages based on the types being compared. - */ public function describe(VerbosityLevel $level): string; - /** - * Returns whether property access ($obj->prop) is possible on this type. - * - * Returns yes for object types that support property access. - * Returns maybe for mixed types. - * Returns no for scalars, arrays, and other non-object types. - * - * This is a general capability check. Use hasInstanceProperty() or hasStaticProperty() - * to check for a specific property. - */ public function canAccessProperties(): TrinaryLogic; /** @deprecated Use hasInstanceProperty or hasStaticProperty instead */ @@ -252,159 +126,51 @@ public function getProperty(string $propertyName, ClassMemberAccessAnswerer $sco /** @deprecated Use getUnresolvedInstancePropertyPrototype or getUnresolvedStaticPropertyPrototype instead */ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection; - /** - * Returns whether a specific instance property exists on this type. - * - * Queries reflection to find the property definition. Returns yes if the - * property definitely exists, no if it definitely doesn't, or maybe if - * it might exist (e.g. on a mixed type). - */ public function hasInstanceProperty(string $propertyName): TrinaryLogic; - /** - * Returns the reflection for a specific instance property. - * - * The ClassMemberAccessAnswerer provides scope context for visibility checks. - * Call hasInstanceProperty() first to verify the property exists. - */ public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection; /** - * Returns the unresolved property prototype for a specific instance property. - * - * Unlike getInstanceProperty(), this returns an intermediate representation - * that allows deferring template type resolution and applying transformations - * based on the called-on type (e.g. resolving static/self to the actual type). - * + * Unlike getInstanceProperty(), this defers template type resolution. * Use getInstanceProperty() in most rule implementations. - * Use this method in framework code that needs to apply type transformations. */ public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection; - /** - * Returns whether a specific static property exists on this type. - */ public function hasStaticProperty(string $propertyName): TrinaryLogic; - /** - * Returns the reflection for a specific static property. - * - * The ClassMemberAccessAnswerer provides scope context for visibility checks. - * Call hasStaticProperty() first to verify the property exists. - */ public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection; - /** - * Returns the unresolved property prototype for a specific static property. - * - * @see Type::getUnresolvedInstancePropertyPrototype() for explanation of resolved vs unresolved - */ public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection; - /** - * Returns whether method calls ($obj->method()) are possible on this type. - * - * Returns yes for object types. - * Returns maybe for mixed types. - * Returns no for scalars, arrays, and other types that don't support method calls. - * - * This is a general capability check. Use hasMethod() to check for a specific method. - */ public function canCallMethods(): TrinaryLogic; - /** - * Returns whether a specific method exists on this type. - * - * Queries reflection for the method definition. Returns yes if the method - * definitely exists, no if it definitely doesn't, or maybe if it might - * exist (e.g. on a mixed type, or via __call). - */ public function hasMethod(string $methodName): TrinaryLogic; - /** - * Returns the reflection for a specific method. - * - * The ClassMemberAccessAnswerer provides scope context for visibility checks. - * Call hasMethod() first to verify the method exists. - */ public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): ExtendedMethodReflection; /** - * Returns the unresolved method prototype for a specific method. - * - * Unlike getMethod(), this returns an intermediate representation that allows - * deferring template type resolution and applying transformations based on - * the called-on type (e.g. resolving static return types). - * + * Unlike getMethod(), this defers template type resolution. * Use getMethod() in most rule implementations. - * Use this method in framework code that needs to apply type transformations. */ public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection; - /** - * Returns whether class constant access (Foo::CONST) is possible on this type. - * - * Returns yes for object/class types. - * Returns maybe for mixed types. - * Returns no for scalars, arrays, and other types that don't support constant access. - * - * This is a general capability check. Use hasConstant() to check for a specific constant. - */ public function canAccessConstants(): TrinaryLogic; - /** - * Returns whether a specific class constant exists on this type. - */ public function hasConstant(string $constantName): TrinaryLogic; - /** - * Returns the reflection for a specific class constant. - * - * Call hasConstant() first to verify the constant exists. - */ public function getConstant(string $constantName): ClassConstantReflection; - /** - * Returns whether this type can be iterated over (in a foreach loop). - * - * Iterable types include arrays, objects implementing Traversable - * (Iterator or IteratorAggregate), and the iterable pseudo-type. - * - * Returns yes for arrays and Traversable objects. - * Returns maybe for mixed types. - * Returns no for scalars and non-traversable objects. - */ public function isIterable(): TrinaryLogic; - /** - * Returns whether this type is iterable and guaranteed to be non-empty. - * - * Unlike isIterable(), this also asserts the iterable has at least one element. - * - * Returns yes for non-empty arrays and non-empty iterables. - * Returns no for empty arrays or types that might be empty. - */ public function isIterableAtLeastOnce(): TrinaryLogic; /** - * Returns the count of elements as a Type, typically an IntegerRangeType. - * - * For constant arrays, returns the precise count (possibly a range - * accounting for optional keys). - * For generic arrays, returns int<0, max>. - * For non-empty arrays, returns int<1, max>. - * - * Used by count() return type extensions and array size comparisons. + * Returns the count of elements as a Type (typically IntegerRangeType). */ public function getArraySize(): Type; /** - * Returns the key type of this iterable. - * * Works for both arrays and Traversable objects. - * For array, returns string. - * For Iterator, returns int. - * For non-iterable types, returns ErrorType. */ public function getIterableKeyType(): Type; @@ -414,14 +180,6 @@ public function getFirstIterableKeyType(): Type; /** @deprecated use getIterableKeyType */ public function getLastIterableKeyType(): Type; - /** - * Returns the value type of this iterable. - * - * Works for both arrays and Traversable objects. - * For array, returns int. - * For Iterator, returns Foo. - * For non-iterable types, returns ErrorType. - */ public function getIterableValueType(): Type; /** @deprecated use getIterableValueType */ @@ -430,275 +188,95 @@ public function getFirstIterableValueType(): Type; /** @deprecated use getIterableValueType */ public function getLastIterableValueType(): Type; - /** - * Returns whether this type is an array. - * - * Returns yes for all array types (generic arrays, constant arrays, lists). - * Returns no for non-array types including objects implementing ArrayAccess. - */ public function isArray(): TrinaryLogic; - /** - * Returns whether this type is a constant array (array shape). - * - * Constant arrays have known keys and value types, like array{name: string, age: int}. - * Generic arrays like array are NOT constant arrays. - * - * Returns yes for ConstantArrayType instances. - * Returns no for generic array types and non-array types. - */ public function isConstantArray(): TrinaryLogic; /** - * Returns whether this type is an oversized array. - * * An oversized array is a constant array shape that grew too large to track - * precisely and was degraded to a generic array type with an OversizedArrayType - * accessory. This prevents performance issues from tracking thousands of keys. - * - * Returns yes for OversizedArrayType. - * Returns maybe for generic arrays (which could be oversized). - * Returns no for constant arrays (known size, not oversized). + * precisely and was degraded to a generic array type. */ public function isOversizedArray(): TrinaryLogic; /** - * Returns whether this type is a list (sequential integer-keyed array). - * * A list is an array with sequential integer keys starting from 0 with no gaps. - * For example, array{0: 'a', 1: 'b', 2: 'c'} is a list. - * array{0: 'a', 2: 'c'} is NOT a list (gap at key 1). - * array{name: string} is NOT a list (string key). - * - * Returns yes for types known to be lists. - * Returns maybe for generic arrays that might be lists. - * Returns no for arrays known not to be lists and non-array types. */ public function isList(): TrinaryLogic; - /** - * Returns whether this type supports array offset access ($a[$key]). - * - * Returns yes for arrays, strings, and objects implementing ArrayAccess. - * Returns maybe for mixed types. - * Returns no for scalars (other than string) and non-ArrayAccess objects. - */ public function isOffsetAccessible(): TrinaryLogic; /** - * Returns whether accessing an undefined offset on this type is legal. - * - * Unlike isOffsetAccessible() which checks if offset access is supported at all, - * this checks whether accessing a non-existent offset is safe (won't cause errors). - * - * Returns yes for arrays and strings (return null/empty string for missing offsets). - * Used by rules to decide whether to report undefined offset access errors. + * Whether accessing a non-existent offset is safe (won't cause errors). + * Unlike isOffsetAccessible() which checks if offset access is supported at all. */ public function isOffsetAccessLegal(): TrinaryLogic; - /** - * Returns whether the given offset exists in this type. - * - * For constant arrays, checks whether the specific key exists. - * For generic arrays, returns maybe (the key could exist). - * For objects implementing ArrayAccess, checks parameter compatibility. - * - * Returns yes if the offset definitely exists, no if it definitely doesn't, - * or maybe if it might exist. - */ public function hasOffsetValueType(Type $offsetType): TrinaryLogic; - /** - * Returns the type of the value at the given offset. - * - * For constant arrays, returns the value type for the specific key. - * For generic arrays, returns the generic value type. - * For strings, returns a single-character string type. - * - * Check hasOffsetValueType() first to determine whether the offset exists. - */ public function getOffsetValueType(Type $offsetType): Type; /** - * Returns a new type with the offset set to the given value type. - * - * This may add a new key to the array. It can change the array structure - * (e.g. break list type if the key is not sequential). - * - * When $offsetType is null, the value is appended (like $a[] = $value). - * When $unionValues is true, the new value type is unioned with any existing - * value type at that offset. + * May add a new key. When $offsetType is null, appends (like $a[] = $value). * * @see Type::setExistingOffsetValueType() for modifying an existing key without widening */ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type; /** - * Returns a new type with an existing offset's value type changed. - * - * Unlike setOffsetValueType(), this assumes the key already exists. - * It preserves the array shape and list type — it does not add new keys - * or widen the array. - * - * Used when modifying a known array element, like $a[$existingKey] = $newValue. + * Unlike setOffsetValueType(), assumes the key already exists. + * Preserves the array shape and list type. */ public function setExistingOffsetValueType(Type $offsetType, Type $valueType): Type; - /** - * Returns a new type with the given offset removed. - * - * For constant arrays, removes the specific key. - * For generic arrays, returns the same type (cannot determine which key was removed). - * - * Models the behavior of unset($a[$key]). - */ public function unsetOffset(Type $offsetType): Type; - /** - * Returns the keys of this array as a list type, filtered to only include - * keys whose values match the given filter type. - * - * Models the behavior of array_keys($array, $searchValue, $strict). - * The $strict parameter controls whether loose (==) or strict (===) comparison - * is used to match values. - */ + /** Models array_keys($array, $searchValue, $strict). */ public function getKeysArrayFiltered(Type $filterValueType, TrinaryLogic $strict): Type; - /** - * Returns the keys of this array as a list type. - * - * Models the behavior of array_keys($array). - * Returns a list — always a list with integer keys starting from 0. - */ + /** Models array_keys($array). */ public function getKeysArray(): Type; - /** - * Returns the values of this array as a reindexed list type. - * - * Models the behavior of array_values($array). - * Returns a list — always a list with integer keys starting from 0. - */ + /** Models array_values($array). */ public function getValuesArray(): Type; - /** - * Returns the type resulting from splitting this array into chunks. - * - * Models the behavior of array_chunk($array, $length, $preserveKeys). - * Returns a list of arrays, each containing up to $lengthType elements. - * When $preserveKeys is yes, original keys are preserved within each chunk. - * When $preserveKeys is no, each chunk is reindexed as a list. - */ + /** Models array_chunk($array, $length, $preserveKeys). */ public function chunkArray(Type $lengthType, TrinaryLogic $preserveKeys): Type; - /** - * Returns the type resulting from using this array's values as keys - * filled with the given value type. - * - * Models the behavior of array_fill_keys($keys, $value) where $keys - * is this array's values. - */ + /** Models array_fill_keys($keys, $value). */ public function fillKeysArray(Type $valueType): Type; - /** - * Returns the type resulting from swapping keys and values. - * - * Models the behavior of array_flip($array). - * Original keys become values and original values become keys - * (values are converted to valid array keys via toArrayKey()). - */ + /** Models array_flip($array). */ public function flipArray(): Type; - /** - * Returns the type resulting from keeping only keys that exist in the other arrays. - * - * Models the behavior of array_intersect_key($array, ...$otherArrays). - * Retains entries from this array whose keys also exist in $otherArraysType. - */ + /** Models array_intersect_key($array, ...$otherArrays). */ public function intersectKeyArray(Type $otherArraysType): Type; - /** - * Returns the type resulting from removing the last element. - * - * Models the effect of array_pop() on the array. - * For constant arrays, removes the last entry. - * For generic arrays, returns the same type. - */ + /** Models array_pop() effect on the array. */ public function popArray(): Type; - /** - * Returns the type resulting from reversing element order. - * - * Models the behavior of array_reverse($array, $preserveKeys). - * When $preserveKeys is yes, original keys are preserved. - * When $preserveKeys is no, integer keys are reindexed. - */ + /** Models array_reverse($array, $preserveKeys). */ public function reverseArray(TrinaryLogic $preserveKeys): Type; - /** - * Returns the type of the key found when searching for a value. - * - * Models the behavior of array_search($needle, $array, $strict). - * Returns a union of matching key types, or false if no match is found. - * When $strict is yes, uses strict comparison (===). - * When $strict is no or null, uses loose comparison (==). - */ + /** Models array_search($needle, $array, $strict). */ public function searchArray(Type $needleType, ?TrinaryLogic $strict = null): Type; - /** - * Returns the type resulting from removing the first element. - * - * Models the effect of array_shift() on the array. - * For constant arrays, removes the first entry and reindexes integer keys. - * For generic arrays, returns the same type. - */ + /** Models array_shift() effect on the array. */ public function shiftArray(): Type; - /** - * Returns the type resulting from randomizing element order. - * - * Models the effect of shuffle() on the array. - * The result is always a list (integer keys starting from 0). - * Constant array type information is degraded since order is unknown. - */ + /** Models shuffle() effect on the array. Result is always a list. */ public function shuffleArray(): Type; - /** - * Returns the type resulting from extracting a portion of the array. - * - * Models the behavior of array_slice($array, $offset, $length, $preserveKeys). - * Extracts elements starting at $offsetType with optional $lengthType limit. - * When $preserveKeys is yes, original keys are kept. - * When $preserveKeys is no, integer keys are reindexed. - */ + /** Models array_slice($array, $offset, $length, $preserveKeys). */ public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $preserveKeys): Type; - /** - * Returns the type resulting from removing and replacing a portion of the array. - * - * Models the effect of array_splice() on the array (the modified array, not the removed portion). - * Removes elements starting at $offsetType for $lengthType entries, then inserts - * $replacementType elements in their place. - */ + /** Models array_splice() effect on the array (the modified array, not the removed portion). */ public function spliceArray(Type $offsetType, Type $lengthType, Type $replacementType): Type; - /** - * Returns all enum cases represented by this type. - * - * For a specific enum case type, returns that single case. - * For a full enum type, returns all defined cases. - * For a union of enum cases, returns all cases in the union. - * For non-enum types, returns an empty array. - * - * @return list - */ + /** @return list */ public function getEnumCases(): array; /** - * Returns the single enum case this type represents, or null. - * - * Unlike getEnumCases() which returns all cases, this returns a single - * EnumCaseObjectType only when the type represents exactly one enum case. - * Returns null for non-enum types, full enum types, or unions of multiple cases. + * Returns the single enum case this type represents, or null if not exactly one case. */ public function getEnumCaseObject(): ?EnumCaseObjectType; @@ -714,111 +292,41 @@ public function getEnumCaseObject(): ?EnumCaseObjectType; * * For infinite types it returns an empty array. * - * Used to determine if a check covers all possible values - * (e.g. in switch exhaustiveness analysis). - * * @return list */ public function getFiniteTypes(): array; - /** - * Returns the type resulting from raising this type to the power of $exponent. - * - * Models the ** operator. For integer and float types, returns the appropriate - * numeric result type. For non-numeric types, returns ErrorType. - */ + /** Models the ** operator. */ public function exponentiate(Type $exponent): Type; - /** - * Returns whether this type can be called as a function/method. - * - * Returns yes for Closure, callable types, callable strings, callable arrays, - * and objects with __invoke(). - * Returns maybe for mixed types and generic strings. - * Returns no for non-callable types. - */ public function isCallable(): TrinaryLogic; - /** - * Returns the parameter acceptors (signatures) for this callable type. - * - * Each CallableParametersAcceptor describes one possible signature of the callable, - * including parameters and return type. Multiple entries indicate overloaded signatures. - * - * Call isCallable() first to verify this type is callable. - * - * @return list - */ + /** @return list */ public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array; - /** - * Returns whether values of this type can be cloned. - * - * Returns yes for object types (unless clone is restricted). - * Returns no for non-object types. - */ public function isCloneable(): TrinaryLogic; - /** - * Returns the type resulting from casting to bool. - * - * Models the (bool) cast and boolean coercion in conditions. - * Empty arrays, 0, 0.0, '', '0', null, and false are falsy. - * Everything else is truthy. - */ + /** Models the (bool) cast. */ public function toBoolean(): BooleanType; - /** - * Returns the type resulting from numeric coercion (int or float). - * - * Models the implicit conversion that occurs with arithmetic operators. - * Numeric strings become int or float, booleans become 0 or 1. - * Arrays and non-numeric types return ErrorType. - */ + /** Models numeric coercion for arithmetic operators. */ public function toNumber(): Type; - /** - * Returns the type resulting from casting to int. - * - * Models the (int) cast. Floats are truncated, strings are parsed, - * booleans become 0 or 1, null becomes 0. - * Arrays return ErrorType. - */ + /** Models the (int) cast. */ public function toInteger(): Type; - /** - * Returns the type resulting from casting to float. - * - * Models the (float) cast. Integers become floats, strings are parsed, - * booleans become 0.0 or 1.0, null becomes 0.0. - * Arrays return ErrorType. - */ + /** Models the (float) cast. */ public function toFloat(): Type; - /** - * Returns the type resulting from casting to string. - * - * Models the (string) cast. Integers, floats, and booleans become their - * string representations. Objects with __toString() return their string type. - * Arrays and objects without __toString() return ErrorType. - */ + /** Models the (string) cast. */ public function toString(): Type; - /** - * Returns the type resulting from casting to array. - * - * Models the (array) cast. Arrays return themselves, scalars are wrapped - * in a single-element array, and objects are converted to their property arrays. - */ + /** Models the (array) cast. */ public function toArray(): Type; /** - * Returns the type when used as an array key. - * - * Models PHP's implicit array key coercion: floats are truncated to int, - * booleans become 0/1, null becomes '', and strings that look like integers - * are converted to int. Objects and arrays return ErrorType since they - * cannot be used as array keys. + * Models PHP's implicit array key coercion: floats truncated to int, + * booleans become 0/1, null becomes '', numeric strings become int. */ public function toArrayKey(): Type; @@ -836,19 +344,8 @@ public function toArrayKey(): Type; */ public function toCoercedArgumentType(bool $strictTypes): self; - /** - * Returns whether this type is definitely smaller than the given type - * using PHP's < operator semantics. - * - * Takes PhpVersion into account because comparison behavior varies across - * PHP versions (e.g. comparing objects to other types). - */ public function isSmallerThan(Type $otherType, PhpVersion $phpVersion): TrinaryLogic; - /** - * Returns whether this type is definitely smaller than or equal to the given type - * using PHP's <= operator semantics. - */ public function isSmallerThanOrEqual(Type $otherType, PhpVersion $phpVersion): TrinaryLogic; /** @@ -867,182 +364,64 @@ public function isConstantValue(): TrinaryLogic; */ public function isConstantScalarValue(): TrinaryLogic; - /** - * Returns the constant scalar type instances contained in this type. - * - * For a union like 1|2|'foo', returns [ConstantIntegerType(1), ConstantIntegerType(2), ConstantStringType('foo')]. - * For non-constant or infinite types, returns an empty array. - * - * @return list - */ + /** @return list */ public function getConstantScalarTypes(): array; - /** - * Returns the actual PHP values of constant scalar types. - * - * For a union like 1|2|'foo', returns [1, 2, 'foo']. - * For non-constant or infinite types, returns an empty array. - * - * @return list - */ + /** @return list */ public function getConstantScalarValues(): array; - /** - * Returns whether this type is the null type. - */ public function isNull(): TrinaryLogic; - /** - * Returns whether this type is the true type. - */ public function isTrue(): TrinaryLogic; - /** - * Returns whether this type is the false type. - */ public function isFalse(): TrinaryLogic; - /** - * Returns whether this type is a boolean type (true, false, or bool). - */ public function isBoolean(): TrinaryLogic; - /** - * Returns whether this type is a float type. - */ public function isFloat(): TrinaryLogic; - /** - * Returns whether this type is an integer type. - */ public function isInteger(): TrinaryLogic; - /** - * Returns whether this type is a string type. - * - * Returns yes for all string types including constant strings, numeric strings, - * class-string, non-empty-string, literal-string, etc. - */ public function isString(): TrinaryLogic; - /** - * Returns whether this type is a numeric string type. - * - * A numeric string is a string that PHP considers valid for arithmetic, - * like '123', '1.5', or '0x1A'. Returns yes for AccessoryNumericStringType - * and constant strings that are numeric. - */ public function isNumericString(): TrinaryLogic; - /** - * Returns whether this type is a non-empty string type. - * - * Returns yes for strings guaranteed to have length >= 1, - * including non-falsy strings, class-strings, and non-empty constant strings. - * Returns no for '' (empty string constant) and generic string types. - */ public function isNonEmptyString(): TrinaryLogic; /** - * Returns whether this type is a non-falsy string type. - * - * A non-falsy string is a non-empty string that is also not '0'. - * This is a stricter subset of non-empty-string. - * Returns yes for AccessoryNonFalsyStringType and qualifying constant strings. + * Non-falsy string is a non-empty string that is also not '0'. + * Stricter subset of non-empty-string. */ public function isNonFalsyString(): TrinaryLogic; /** - * Returns whether this type is a literal string type. - * - * A literal-string is a string whose value was composed entirely from - * string literals in the source code (not from user input). Used for - * SQL injection prevention — literal strings are safe for query building. + * A literal-string is a string composed entirely from string literals + * in the source code (not from user input). Used for SQL injection prevention. */ public function isLiteralString(): TrinaryLogic; - /** - * Returns whether this type is a lowercase string type. - * - * Returns yes for strings known to be entirely lowercase, such as the result - * of strtolower() or constant strings where strtolower($value) === $value. - */ public function isLowercaseString(): TrinaryLogic; - /** - * Returns whether this type is an uppercase string type. - * - * Returns yes for strings known to be entirely uppercase, such as the result - * of strtoupper() or constant strings where strtoupper($value) === $value. - */ public function isUppercaseString(): TrinaryLogic; - /** - * Returns whether this type is a class-string type. - * - * A class-string is a string that contains a valid fully-qualified class name. - * Returns yes for class-string, class-string, and literal strings that - * are known class names. Returns maybe for generic strings. - */ public function isClassString(): TrinaryLogic; - /** - * Returns whether this type is the void type. - * - * Void is a return-type-only type that indicates a function returns no value. - * It cannot be used in union types or as a parameter type. - */ public function isVoid(): TrinaryLogic; - /** - * Returns whether this type is a scalar type. - * - * Scalar types are int, float, string, and bool. - * Returns yes for all scalar types including their constant subtypes. - * Returns no for arrays, objects, null, void, and resource. - */ public function isScalar(): TrinaryLogic; - /** - * Returns the result of a loose comparison (==) between this type and the given type. - * - * Models PHP's type juggling comparison rules. Returns a BooleanType that - * may be true, false, or bool (when the result is uncertain). - * Takes PhpVersion into account because loose comparison behavior varies - * across PHP versions (e.g. 0 == "foo" changed in PHP 8.0). - */ public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType; /** - * Returns a type representing all values that are smaller than this type. - * - * Used for type narrowing after < comparisons. - * For example, for ConstantIntegerType(5), returns int. + * Type narrowing methods for comparison operators. + * For example, for ConstantIntegerType(5), getSmallerType() returns int. */ public function getSmallerType(PhpVersion $phpVersion): Type; - /** - * Returns a type representing all values that are smaller than or equal to this type. - * - * Used for type narrowing after <= comparisons. - * For example, for ConstantIntegerType(5), returns int. - */ public function getSmallerOrEqualType(PhpVersion $phpVersion): Type; - /** - * Returns a type representing all values that are greater than this type. - * - * Used for type narrowing after > comparisons. - * For example, for ConstantIntegerType(5), returns int<6, max>. - */ public function getGreaterType(PhpVersion $phpVersion): Type; - /** - * Returns a type representing all values that are greater than or equal to this type. - * - * Used for type narrowing after >= comparisons. - * For example, for ConstantIntegerType(5), returns int<5, max>. - */ public function getGreaterOrEqualType(PhpVersion $phpVersion): Type; /** @@ -1064,13 +443,9 @@ public function getGreaterOrEqualType(PhpVersion $phpVersion): Type; public function getTemplateType(string $ancestorClassName, string $templateTypeName): Type; /** - * Infers template types. - * - * Infers the real Type of the TemplateTypes found in $this, based on - * the received Type. For example, if $this is array and $receivedType - * is array, it infers T = int. - * - * Returns a TemplateTypeMap mapping template type names to their inferred types. + * Infers the real types of TemplateTypes found in $this, based on + * the received Type. E.g. if $this is array and $receivedType + * is array, infers T = int. */ public function inferTemplateTypes(Type $receivedType): TemplateTypeMap; @@ -1092,79 +467,37 @@ public function inferTemplateTypes(Type $receivedType): TemplateTypeMap; */ public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array; - /** - * Returns the type resulting from taking the absolute value. - * - * Models the abs() function. For negative constant integers/floats, returns the - * positive counterpart. For integer ranges, adjusts bounds accordingly. - * For non-numeric types, returns ErrorType. - */ + /** Models abs(). */ public function toAbsoluteNumber(): Type; /** - * Traverses inner types. - * - * Returns a new instance with all inner types mapped through $cb. Might - * return the same instance if inner types did not change. - * - * Used to resolve template types, transform nested types, or collect - * information about type structure. For example, replacing TemplateType - * placeholders with concrete types in a generic instantiation. + * Returns a new instance with all inner types mapped through $cb. + * Returns the same instance if inner types did not change. * * @param callable(Type):Type $cb */ public function traverse(callable $cb): Type; /** - * Traverses inner types while keeping the same structure in another type. - * - * Like traverse(), but walks two types simultaneously, passing matching - * pairs of inner types from $this and $right to the callback. Used when - * two types with the same structure need to be compared or merged element-wise. + * Like traverse(), but walks two types simultaneously. * * @param callable(Type $left, Type $right): Type $cb */ public function traverseSimultaneously(Type $right, callable $cb): Type; - /** - * Converts this Type to its PHPDoc AST node representation. - * - * Used to serialize types back to PHPDoc format, for example when generating - * PHPDoc annotations or converting types for display in documentation tools. - */ public function toPhpDocNode(): TypeNode; - /** - * Return the difference with another type, or null if it cannot be represented. - * - * For example, int|string minus string returns int. - * Returns null when the subtraction cannot be cleanly represented as a Type. - * - * @see TypeCombinator::remove() - */ + /** @see TypeCombinator::remove() */ public function tryRemove(Type $typeToRemove): ?Type; /** - * Generalizes this type by removing constant value information. - * - * Converts specific/literal types to their more general equivalents: - * - GeneralizePrecision::lessSpecific(): Full generalization, e.g. 'foo' -> string, 1 -> int - * - GeneralizePrecision::moreSpecific(): Partial generalization, preserves some detail (used in loop analysis) - * - GeneralizePrecision::templateArgument(): For template argument generalization - * - * Used when types become too complex to track precisely, such as after - * repeated loop iterations where constant arrays grow unboundedly. + * Removes constant value information. E.g. 'foo' -> string, 1 -> int. + * Used when types become too complex to track precisely (e.g. loop iterations). */ public function generalize(GeneralizePrecision $precision): Type; /** - * Returns whether this type contains any template types or late-resolvable types. - * - * Template types are generic type parameters (T, TValue, etc.) waiting to be resolved. - * Late-resolvable types are types that cannot be fully determined during initial analysis. - * - * Used as a performance optimization to skip template resolution logic when - * no templates are present. + * Performance optimization to skip template resolution when no templates are present. */ public function hasTemplateOrLateResolvableType(): bool; diff --git a/src/Type/TypeWithClassName.php b/src/Type/TypeWithClassName.php index e169b2020d..952c904cd1 100644 --- a/src/Type/TypeWithClassName.php +++ b/src/Type/TypeWithClassName.php @@ -23,24 +23,13 @@ interface TypeWithClassName extends Type { - /** - * Returns the fully qualified class name (without leading backslash). - */ public function getClassName(): string; /** - * Walks the type's class hierarchy to find an ancestor matching the given class name. - * - * Returns a TypeWithClassName representing the type projected onto that ancestor, - * or null if the class is not in the hierarchy. Preserves generic type arguments - * when walking through the hierarchy. + * Returns this type projected onto an ancestor class, preserving generic type arguments. */ public function getAncestorWithClassName(string $className): ?self; - /** - * Returns the ClassReflection for this type's class, or null if the class - * cannot be reflected (e.g. the class doesn't exist). - */ public function getClassReflection(): ?ClassReflection; } diff --git a/src/Type/VerbosityLevel.php b/src/Type/VerbosityLevel.php index ca56ce5984..35c3add599 100644 --- a/src/Type/VerbosityLevel.php +++ b/src/Type/VerbosityLevel.php @@ -70,47 +70,26 @@ public function getLevelValue(): int return $this->value; } - /** - * Least verbose: only type names, no values or refinements. - * - * E.g. "string", "int", "array". - * - * @api - */ + /** @api */ public static function typeOnly(): self { return self::create(self::TYPE_ONLY); } - /** - * Includes constant values and basic refinements. - * - * E.g. "'hello'", "42", "array{foo: int, bar: string}", "non-empty-string". - * - * @api - */ + /** @api */ public static function value(): self { return self::create(self::VALUE); } - /** - * Maximum verbosity: includes all refinements like lowercase/uppercase. - * - * E.g. "non-empty-lowercase-string", "non-falsy-string". - * - * @api - */ + /** @api */ public static function precise(): self { return self::create(self::PRECISE); } /** - * Internal level used to generate unique cache keys for types. - * - * Produces the most specific string possible to distinguish any two - * structurally different types. Not intended for user-facing messages. + * Internal level for generating unique cache keys — not for user-facing messages. * * @api */ @@ -140,11 +119,7 @@ public function isCache(): bool } /** - * Chooses the minimum verbosity level needed to distinguish the accepting and accepted types. - * - * Examines both types and picks a level that provides enough detail to make the - * error message clear. For example, if the types differ only in constant values, - * it picks value(). If they differ in lowercase/uppercase, it picks precise(). + * Chooses the minimum verbosity needed to distinguish the two types in error messages. * * @api */ @@ -262,12 +237,6 @@ public static function getRecommendedLevelByType(Type $acceptingType, ?Type $acc } /** - * Dispatches to the appropriate callback based on the current level. - * - * Type implementations use this in their describe() method to provide - * different representations at each verbosity level. Falls back to less - * specific callbacks when more specific ones are not provided. - * * @param callable(): string $typeOnlyCallback * @param callable(): string $valueCallback * @param callable(): string|null $preciseCallback From b67159a2fc3bb321056163e543696410a93a8e74 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 11 Feb 2026 06:26:01 +0000 Subject: [PATCH 7/9] Add PHPDoc writing guidelines to CLAUDE.md https://claude.ai/code/session_01Htkqstd1mz1dbevHT6Y437 --- CLAUDE.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index 186a84d1d2..3093b8d0aa 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -309,6 +309,32 @@ Recent work on PHP 8.5 support shows the pattern: - **PhpVersion**: Add detection methods like `supportsPropertyHooks()`, `supportsPipeOperator()`, etc. - **Stubs**: Update function/class stubs for new built-in functions and changed signatures +## Writing PHPDocs + +When adding or editing PHPDoc comments in this codebase, follow these guidelines: + +### What to document + +- **Class-level docs on interfaces and key abstractions**: Explain the role of the interface, what implements it, and how it fits into the architecture. Mention non-obvious patterns like double-dispatch (CompoundType), the intersection-with-base-type requirement (AccessoryType), or the instanceof-avoidance rule (TypeWithClassName). +- **Non-obvious behavior**: Document when a method's behavior differs from what its name suggests, or when there are subtle contracts. For example: `getDeclaringClass()` returning the declaring class even for inherited members, `setExistingOffsetValueType()` vs `setOffsetValueType()` preserving list types differently, or `getWritableType()` potentially differing from `getReadableType()` due to asymmetric visibility. +- **`@api` tags**: Keep these — they mark the public API for extension developers. +- **`@phpstan-assert` tags**: Keep these — they provide type narrowing information that PHPStan uses. +- **`@return`, `@param`, `@template` tags**: Keep when they provide type information not expressible in native PHP types (e.g. `@return self::SOURCE_*`, `@param array`). + +### What NOT to document + +- **Obvious from the method name**: Do not write "Returns the name" above `getName()`, "Returns the value type" above `getValueType()`, or "Returns whether deprecated" above `isDeprecated()`. If the method name says it all, add no description. +- **Obvious to experienced PHP developers**: Do not explain standard visibility rules ("public methods are always callable, protected methods are callable from subclasses..."), standard PHP semantics, or basic design patterns. +- **Obvious from tags**: Do not add prose that restates what `@return`, `@phpstan-assert`, or `@param` tags already say. If `@return non-empty-string|null` is present, do not also write "Returns a non-empty string or null". +- **Factory method descriptions that repeat the class-level doc**: If the class doc already explains the levels/variants (like VerbosityLevel or GeneralizePrecision), don't repeat those descriptions on each factory method. A bare `@api` tag is sufficient. +- **Getter/setter/query methods on value objects**: Methods like `isInvariant()`, `isCovariant()`, `isEmpty()`, `count()`, `getType()`, `hasType()` on simple value objects need no PHPDoc. + +### Style + +- Keep descriptions concise — one or two sentences for method docs when needed. +- Use imperative voice without "Returns the..." preambles when a brief note suffices. Prefer `/** Replaces unresolved TemplateTypes with their bounds. */` over a multi-line block. +- Preserve `@api` and type tags on their own lines, with no redundant description alongside them. + ## Important dependencies - `nikic/php-parser` ^5.7.0 - PHP AST parsing From 4ac019968e1c562951ee5f461fc345e663cadc04 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 11 Feb 2026 09:45:53 +0100 Subject: [PATCH 8/9] Address PR review feedback on PHPDoc documentation - PhpVersion: mention only constructor injection, not Scope - CallableParametersAcceptor: restore #[\NoDiscard] attribute mention - ParametersAcceptor: use strtok as multi-variant example - PropertyReflection: fix canChangeTypeAfterAssignment() description to explain it's about matching read/write types and property hooks - UnresolvedMethodPrototypeReflection: document ObjectType vs StaticType implementations (CalledOnType vs Callback) - UnresolvedPropertyPrototypeReflection: same as above - Type::getUnresolvedMethodPrototype(): add "and static type" to doc - Type::traverse(): note to use TypeTraverser::map() instead - Type::traverseSimultaneously(): note to use SimultaneousTypeTraverser - VerbosityLevel: improve precise level description with concrete examples Co-Authored-By: Claude Opus 4.6 --- src/Php/PhpVersion.php | 3 +-- src/Reflection/Callables/CallableParametersAcceptor.php | 1 + src/Reflection/ParametersAcceptor.php | 2 +- src/Reflection/PropertyReflection.php | 6 ++++-- .../Type/UnresolvedMethodPrototypeReflection.php | 7 ++++++- .../Type/UnresolvedPropertyPrototypeReflection.php | 7 ++++++- src/Type/Type.php | 6 +++++- src/Type/VerbosityLevel.php | 3 ++- 8 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/Php/PhpVersion.php b/src/Php/PhpVersion.php index 4200917985..8c8ef1041b 100644 --- a/src/Php/PhpVersion.php +++ b/src/Php/PhpVersion.php @@ -9,8 +9,7 @@ * Represents a specific PHP version for version-dependent analysis behavior. * * The version is stored as PHP_VERSION_ID format (e.g. 80100 for PHP 8.1.0). - * Extension developers can access it via `Scope::getPhpVersion()` (which returns - * PhpVersions, a range-aware wrapper) or by injecting PhpVersion directly. + * Extension developers can access it by injecting PhpVersion via constructor injection. * * @api */ diff --git a/src/Reflection/Callables/CallableParametersAcceptor.php b/src/Reflection/Callables/CallableParametersAcceptor.php index 139ca8a175..45ca73857c 100644 --- a/src/Reflection/Callables/CallableParametersAcceptor.php +++ b/src/Reflection/Callables/CallableParametersAcceptor.php @@ -50,6 +50,7 @@ public function getInvalidateExpressions(): array; public function getUsedVariables(): array; /** + * Whether the callable is marked with the `#[\NoDiscard]` attribute. * On PHP 8.5+ if the return value is unused at runtime, a warning is emitted. * PHPStan reports this during analysis regardless of PHP version. */ diff --git a/src/Reflection/ParametersAcceptor.php b/src/Reflection/ParametersAcceptor.php index f4c116ade2..53580963c4 100644 --- a/src/Reflection/ParametersAcceptor.php +++ b/src/Reflection/ParametersAcceptor.php @@ -9,7 +9,7 @@ * Describes one signature variant of a function or method. * * A function/method may have multiple ParametersAcceptor variants — for example, - * the built-in `array_map` function has different signatures depending on argument count. + * the built-in `strtok` function has different signatures depending on argument count. * Each variant describes the template type parameters, positional parameters, variadicity, * and return type. * diff --git a/src/Reflection/PropertyReflection.php b/src/Reflection/PropertyReflection.php index 09cee4d8ed..a301e42225 100644 --- a/src/Reflection/PropertyReflection.php +++ b/src/Reflection/PropertyReflection.php @@ -33,8 +33,10 @@ public function getReadableType(): Type; public function getWritableType(): Type; /** - * Returns false for typed properties (which always retain their declared type) - * and true for untyped properties (which take on the type of whatever is assigned). + * Returns true when the readable and writable types are the same and no property hooks + * transform the value — PHPStan can then narrow the property's type based on assignments. + * Returns false when read and write types differ (e.g. `@property` with asymmetric types, + * property hooks, virtual properties). */ public function canChangeTypeAfterAssignment(): bool; diff --git a/src/Reflection/Type/UnresolvedMethodPrototypeReflection.php b/src/Reflection/Type/UnresolvedMethodPrototypeReflection.php index fbebf595ac..e2d3f01801 100644 --- a/src/Reflection/Type/UnresolvedMethodPrototypeReflection.php +++ b/src/Reflection/Type/UnresolvedMethodPrototypeReflection.php @@ -6,7 +6,7 @@ use PHPStan\Type\Type; /** - * Lazy method reflection that defers template type resolution. + * Lazy method reflection that defers template type and static type resolution. * * When calling a method on a generic type, the method's parameter and return types * need to be transformed by substituting template type parameters with their concrete @@ -18,6 +18,11 @@ * when concrete types are unknown (used during type inference) * - withCalledOnType() sets the type the method is being called on * + * This exists primarily because of StaticType. ObjectType uses + * CalledOnTypeUnresolvedMethodPrototypeReflection which has hardcoded logic + * to transform static types. StaticType uses CallbackUnresolvedMethodPrototypeReflection + * which accepts a custom callback for context-aware static type transformation. + * * This is the return type of Type::getUnresolvedMethodPrototype(). */ interface UnresolvedMethodPrototypeReflection diff --git a/src/Reflection/Type/UnresolvedPropertyPrototypeReflection.php b/src/Reflection/Type/UnresolvedPropertyPrototypeReflection.php index 9d3fca54e9..c0206bce65 100644 --- a/src/Reflection/Type/UnresolvedPropertyPrototypeReflection.php +++ b/src/Reflection/Type/UnresolvedPropertyPrototypeReflection.php @@ -6,7 +6,7 @@ use PHPStan\Type\Type; /** - * Lazy property reflection that defers template type resolution. + * Lazy property reflection that defers template type and static type resolution. * * When accessing a property on a generic type, the property's types need to be * transformed by substituting template type parameters with their concrete arguments. @@ -18,6 +18,11 @@ * when concrete types are unknown (used during type inference) * - withFechedOnType() sets the type the property is being accessed on * + * This exists primarily because of StaticType. ObjectType uses + * CalledOnTypeUnresolvedPropertyPrototypeReflection which has hardcoded logic + * to transform static types. StaticType uses CallbackUnresolvedPropertyPrototypeReflection + * which accepts a custom callback for context-aware static type transformation. + * * This is the return type of Type::getUnresolvedPropertyPrototype(), * Type::getUnresolvedInstancePropertyPrototype(), and * Type::getUnresolvedStaticPropertyPrototype(). diff --git a/src/Type/Type.php b/src/Type/Type.php index 2515e9c46b..77f7682ca5 100644 --- a/src/Type/Type.php +++ b/src/Type/Type.php @@ -149,7 +149,7 @@ public function hasMethod(string $methodName): TrinaryLogic; public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): ExtendedMethodReflection; /** - * Unlike getMethod(), this defers template type resolution. + * Unlike getMethod(), this defers template type and static type resolution. * Use getMethod() in most rule implementations. */ public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection; @@ -474,6 +474,8 @@ public function toAbsoluteNumber(): Type; * Returns a new instance with all inner types mapped through $cb. * Returns the same instance if inner types did not change. * + * Not used directly — use TypeTraverser::map() instead. + * * @param callable(Type):Type $cb */ public function traverse(callable $cb): Type; @@ -481,6 +483,8 @@ public function traverse(callable $cb): Type; /** * Like traverse(), but walks two types simultaneously. * + * Not used directly — use SimultaneousTypeTraverser::map() instead. + * * @param callable(Type $left, Type $right): Type $cb */ public function traverseSimultaneously(Type $right, callable $cb): Type; diff --git a/src/Type/VerbosityLevel.php b/src/Type/VerbosityLevel.php index 35c3add599..32be9683a8 100644 --- a/src/Type/VerbosityLevel.php +++ b/src/Type/VerbosityLevel.php @@ -24,7 +24,8 @@ * The four levels (from least to most verbose): * - **typeOnly**: Just the type name, e.g. "string", "array", "Foo" * - **value**: Includes constant values, e.g. "'hello'", "array{foo: int}", "non-empty-string" - * - **precise**: Maximum detail including lowercase/uppercase string distinctions + * - **precise**: Maximum detail — adds subtracted types on object/mixed (e.g. "object~Bar"), + * lowercase/uppercase string distinctions, untruncated array shapes, and template type scope * - **cache**: Internal level used for generating cache keys * * Used as a parameter to Type::describe() to control output detail: From d2724714c4fa60d23e15f77eb29d74e94e1d52c0 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 11 Feb 2026 09:50:28 +0100 Subject: [PATCH 9/9] Fix CS --- src/Analyser/Scope.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Analyser/Scope.php b/src/Analyser/Scope.php index cab588c4c6..5afda44c70 100644 --- a/src/Analyser/Scope.php +++ b/src/Analyser/Scope.php @@ -46,7 +46,6 @@ interface Scope extends ClassMemberAccessAnswerer, NamespaceAnswerer { - /** @var list PHP superglobal variable names that are always available */ public const SUPERGLOBAL_VARIABLES = [ 'GLOBALS', '_SERVER',