diff --git a/.idea/icon.png b/.idea/icon.png deleted file mode 100644 index 5f346e71c16..00000000000 Binary files a/.idea/icon.png and /dev/null differ diff --git a/composer.json b/composer.json index f8114f00410..1e2b2ae0da7 100644 --- a/composer.json +++ b/composer.json @@ -112,6 +112,10 @@ "symfony/console": [ "patches/OutputFormatter.patch", "patches/Application.patch" + ], + "phpstan/phpdoc-parser": [ + "patches/TypeAliasTagValueNode.patch", + "patches/PhpDocParser.patch" ] } }, diff --git a/composer.lock b/composer.lock index 71d8e2207b8..eb8df98c9d9 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b4aec40b93330400ac2e7427e2d76f11", + "content-hash": "bbf4084326b80a7098c029e87afce8dd", "packages": [ { "name": "clue/ndjson-react", diff --git a/patches/PhpDocParser.patch b/patches/PhpDocParser.patch new file mode 100644 index 00000000000..167968588ff --- /dev/null +++ b/patches/PhpDocParser.patch @@ -0,0 +1,39 @@ +--- src/Parser/PhpDocParser.php ++++ src/Parser/PhpDocParser.php +@@ -1067,6 +1067,21 @@ + $alias = $tokens->currentTokenValue(); + $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); + ++ $templateTypes = []; ++ if ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET)) { ++ do { ++ $startLine = $tokens->currentTokenLine(); ++ $startIndex = $tokens->currentTokenIndex(); ++ $templateTypes[] = $this->enrichWithAttributes( ++ $tokens, ++ $this->typeParser->parseTemplateTagValue($tokens), ++ $startLine, ++ $startIndex, ++ ); ++ } while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)); ++ $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET); ++ } ++ + // support phan-type/psalm-type syntax + $tokens->tryConsumeTokenType(Lexer::TOKEN_EQUAL); + +@@ -1087,12 +1102,13 @@ + } + } + +- return new Ast\PhpDoc\TypeAliasTagValueNode($alias, $type); ++ return new Ast\PhpDoc\TypeAliasTagValueNode($alias, $type, $templateTypes); + } catch (ParserException $e) { + $this->parseOptionalDescription($tokens, false); + return new Ast\PhpDoc\TypeAliasTagValueNode( + $alias, + $this->enrichWithAttributes($tokens, new Ast\Type\InvalidTypeNode($e), $startLine, $startIndex), ++ $templateTypes, + ); + } + } diff --git a/patches/TypeAliasTagValueNode.patch b/patches/TypeAliasTagValueNode.patch new file mode 100644 index 00000000000..4137b44f237 --- /dev/null +++ b/patches/TypeAliasTagValueNode.patch @@ -0,0 +1,47 @@ +--- src/Ast/PhpDoc/TypeAliasTagValueNode.php ++++ src/Ast/PhpDoc/TypeAliasTagValueNode.php +@@ -4,6 +4,7 @@ + + use PHPStan\PhpDocParser\Ast\NodeAttributes; + use PHPStan\PhpDocParser\Ast\Type\TypeNode; ++use function implode; + use function trim; + + class TypeAliasTagValueNode implements PhpDocTagValueNode +@@ -15,15 +16,25 @@ + + public TypeNode $type; + +- public function __construct(string $alias, TypeNode $type) ++ /** @var TemplateTagValueNode[] */ ++ public array $templateTypes; ++ ++ /** ++ * @param TemplateTagValueNode[] $templateTypes ++ */ ++ public function __construct(string $alias, TypeNode $type, array $templateTypes = []) + { + $this->alias = $alias; + $this->type = $type; ++ $this->templateTypes = $templateTypes; + } + + public function __toString(): string + { +- return trim("{$this->alias} {$this->type}"); ++ $templateTypes = $this->templateTypes !== [] ++ ? '<' . implode(', ', $this->templateTypes) . '>' ++ : ''; ++ return trim("{$this->alias}{$templateTypes} {$this->type}"); + } + + /** +@@ -31,7 +42,7 @@ + */ + public static function __set_state(array $properties): self + { +- $instance = new self($properties['alias'], $properties['type']); ++ $instance = new self($properties['alias'], $properties['type'], $properties['templateTypes'] ?? []); + if (isset($properties['attributes'])) { + foreach ($properties['attributes'] as $key => $value) { + $instance->setAttribute($key, $value); diff --git a/src/PhpDoc/PhpDocNodeResolver.php b/src/PhpDoc/PhpDocNodeResolver.php index 004444161d0..89e5afd7224 100644 --- a/src/PhpDoc/PhpDocNodeResolver.php +++ b/src/PhpDoc/PhpDocNodeResolver.php @@ -554,7 +554,7 @@ public function resolveTypeAliasTags(PhpDocNode $phpDocNode, NameScope $nameScop foreach ($phpDocNode->getTypeAliasTagValues($tagName) as $typeAliasTagValue) { $alias = $typeAliasTagValue->alias; $typeNode = $typeAliasTagValue->type; - $resolved[$alias] = new TypeAliasTag($alias, $typeNode, $nameScope); + $resolved[$alias] = new TypeAliasTag($alias, $typeNode, $nameScope, $typeAliasTagValue->templateTypes); } } diff --git a/src/PhpDoc/Tag/TypeAliasTag.php b/src/PhpDoc/Tag/TypeAliasTag.php index d5cd10e5d68..df8fd58aa77 100644 --- a/src/PhpDoc/Tag/TypeAliasTag.php +++ b/src/PhpDoc/Tag/TypeAliasTag.php @@ -3,6 +3,7 @@ namespace PHPStan\PhpDoc\Tag; use PHPStan\Analyser\NameScope; +use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\Type\TypeAlias; @@ -12,10 +13,14 @@ final class TypeAliasTag { + /** + * @param TemplateTagValueNode[] $templateTagValueNodes + */ public function __construct( private string $aliasName, private TypeNode $typeNode, private NameScope $nameScope, + private array $templateTagValueNodes = [], ) { } @@ -30,6 +35,8 @@ public function getTypeAlias(): TypeAlias return new TypeAlias( $this->typeNode, $this->nameScope, + $this->templateTagValueNodes, + $this->aliasName, ); } diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index 832cb06d94c..2a14e0b4ecf 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -110,6 +110,7 @@ use PHPStan\Type\StringType; use PHPStan\Type\ThisType; use PHPStan\Type\Type; +use PHPStan\Type\TypeAlias; use PHPStan\Type\TypeAliasResolver; use PHPStan\Type\TypeAliasResolverProvider; use PHPStan\Type\TypeCombinator; @@ -119,6 +120,7 @@ use PHPStan\Type\ValueOfType; use PHPStan\Type\VoidType; use Traversable; +use function array_filter; use function array_key_exists; use function array_map; use function array_values; @@ -873,6 +875,23 @@ static function (string $variance): TemplateTypeVariance { return new ErrorType(); } + // Check for a generic type alias (e.g. MyList) before falling through to + // class-based generic resolution. + $genericTypeAlias = $typeNode->type instanceof IdentifierTypeNode ? $this->findGenericTypeAlias($typeNode->type->name, $nameScope) : null; + if ($genericTypeAlias !== null) { + $templateNodes = $genericTypeAlias->getTemplateTagValueNodes(); + $totalParams = count($templateNodes); + $requiredParams = count(array_filter($templateNodes, static fn ($tvn) => $tvn->default === null)); + $providedArgs = count($genericTypes); + + if ($providedArgs > $totalParams || $providedArgs < $requiredParams) { + return new ErrorType(); + } + + $appType = $genericTypeAlias->createApplicationType($this, $genericTypes); + return $appType->isResolvable() ? $appType->resolve() : $appType; + } + $mainType = $typeNode->type instanceof NameNode ? $this->resolveNameNode($typeNode->type, $nameScope) : $this->resolveIdentifierTypeNode($typeNode->type, $nameScope); @@ -1524,4 +1543,39 @@ private function getTypeAliasResolver(): TypeAliasResolver return $this->typeAliasResolverProvider->getTypeAliasResolver(); } + /** + * Returns the TypeAlias for $name if it is a generic (parameterised) type alias + * visible in the current $nameScope, or null otherwise. + */ + private function findGenericTypeAlias(string $name, NameScope $nameScope): ?TypeAlias + { + if ($nameScope->shouldBypassTypeAliases()) { + return null; + } + + // Fast path: if the name isn't registered as a type alias in this scope, skip the + // more expensive ClassReflection::getTypeAliases() call. This also prevents a circular + // NameScope-building issue: getTypeAliases() can trigger FileTypeMapper::getResolvedPhpDoc() + // which calls getNameScope() — if we are already inside getNameScope() for this class, + // that throws NameScopeAlreadyBeingCreatedException, causing the class's ResolvedPhpDocBlock + // to be poisoned with an empty block and all its type aliases to be lost. + // UsefulTypeAliasResolver uses the same guard. + if (!$nameScope->hasTypeAlias($name)) { + return null; + } + + $className = $nameScope->getClassNameForTypeAlias(); + if ($className === null || !$this->getReflectionProvider()->hasClass($className)) { + return null; + } + + $typeAliases = $this->getReflectionProvider()->getClass($className)->getTypeAliases(); + if (!array_key_exists($name, $typeAliases)) { + return null; + } + + $typeAlias = $typeAliases[$name]; + return $typeAlias->isGeneric() ? $typeAlias : null; + } + } diff --git a/src/Rules/MissingTypehintCheck.php b/src/Rules/MissingTypehintCheck.php index aeedc64afd7..0689f985663 100644 --- a/src/Rules/MissingTypehintCheck.php +++ b/src/Rules/MissingTypehintCheck.php @@ -19,6 +19,7 @@ use PHPStan\Type\Generic\GenericStaticType; use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\Generic\TemplateTypeHelper; +use PHPStan\Type\GenericTypeAliasType; use PHPStan\Type\IntersectionType; use PHPStan\Type\MixedType; use PHPStan\Type\ObjectType; @@ -30,6 +31,7 @@ use function array_filter; use function array_keys; use function array_merge; +use function array_unique; use function array_values; use function count; use function implode; @@ -159,6 +161,16 @@ public function getNonGenericObjectTypesWithGenericClass(Type $type): array if ($type instanceof TemplateType) { return $type; } + if ($type instanceof GenericTypeAliasType) { + $missing = $type->getMissingRequiredParamNames(); + if ($missing !== []) { + $objectTypes[] = [ + sprintf('type alias %s', $type->getAliasName()), + implode(', ', array_unique($missing)), + ]; + } + return $traverse($type); + } if ($type instanceof ObjectType) { $classReflection = $type->getClassReflection(); if ($classReflection === null) { diff --git a/src/Rules/PhpDoc/InvalidPhpDocVarTagTypeRule.php b/src/Rules/PhpDoc/InvalidPhpDocVarTagTypeRule.php index 8e59a217053..b1d8b84df29 100644 --- a/src/Rules/PhpDoc/InvalidPhpDocVarTagTypeRule.php +++ b/src/Rules/PhpDoc/InvalidPhpDocVarTagTypeRule.php @@ -121,6 +121,7 @@ public function processNode(Node $node, Scope $scope): array ->identifier('missingType.generics') ->build(); } + } $escapedIdentifier = SprintfHelper::escapeFormatString($identifier); diff --git a/src/Type/Generic/TemplateTypeScope.php b/src/Type/Generic/TemplateTypeScope.php index 32dbd8eb4b8..d7c121c94db 100644 --- a/src/Type/Generic/TemplateTypeScope.php +++ b/src/Type/Generic/TemplateTypeScope.php @@ -4,6 +4,9 @@ use PHPStan\Turbo\ShadowedByTurboExtension; use function sprintf; +use function str_starts_with; +use function strlen; +use function substr; #[ShadowedByTurboExtension(implementation: __DIR__ . '/../../../turbo-ext/src/TemplateTypeScope.cpp')] final class TemplateTypeScope @@ -14,6 +17,11 @@ public static function createWithAnonymousFunction(): self return new self(null, null); } + public static function createWithTypeAlias(string $className, string $aliasName): self + { + return new self($className, '__typeAlias_' . $aliasName); + } + public static function createWithFunction(string $functionName): self { return new self(null, $functionName); @@ -45,6 +53,22 @@ public function getFunctionName(): ?string return $this->functionName; } + /** @api */ + public function isTypeAlias(): bool + { + return $this->functionName !== null && str_starts_with($this->functionName, '__typeAlias_'); + } + + /** @api */ + public function getTypeAliasName(): ?string + { + if (!$this->isTypeAlias() || $this->functionName === null) { + return null; + } + + return substr($this->functionName, strlen('__typeAlias_')); + } + /** @api */ public function equals(self $other): bool { diff --git a/src/Type/GenericTypeAliasType.php b/src/Type/GenericTypeAliasType.php new file mode 100644 index 00000000000..f1913d4b3ef --- /dev/null +++ b/src/Type/GenericTypeAliasType.php @@ -0,0 +1,231 @@ +} where {@code @phpstan-type Filter} is + * declared expands lazily to the alias body with TItem substituted. + * + * Mirrors the role of GenericObjectType for classes: GenericObjectType is a class constructor + * applied to type args; GenericTypeAliasType is a type alias applied to type args. + * + * Implements LateResolvableType so TypeUtils::resolveLateResolvableTypes() expands it at the + * right moment without leaking TemplateType placeholders to the rest of the type system. + */ +final class GenericTypeAliasType implements CompoundType, LateResolvableType +{ + + use LateResolvableTypeTrait; + use NonGeneralizableTypeTrait; + + /** + * @param list $paramNames Ordered parameter names from the alias declaration. + * @param list $args Supplied type arguments (may be shorter than paramNames + * when trailing params are covered by defaults). + * @param list $defaults Per-param declared default type; null when the param has no default. + * @param list $boundFallbacks Per-param bound type used when both arg and default are absent. + */ + public function __construct( + private readonly string $aliasName, + private readonly Type $resolvedBody, + private readonly array $paramNames, + private readonly array $args, + private readonly array $defaults, + private readonly array $boundFallbacks, + ) + { + } + + public function getAliasName(): string + { + return $this->aliasName; + } + + /** + * Returns the names of required params (no declared default) that were not supplied as args. + * A non-empty list means this is a "raw" usage of a generic alias that should be reported. + * + * @return list + */ + public function getMissingRequiredParamNames(): array + { + $missing = []; + foreach ($this->paramNames as $i => $name) { + if (isset($this->args[$i]) || $this->defaults[$i] !== null) { + continue; + } + + $missing[] = $name; + } + + return $missing; + } + + public function getReferencedClasses(): array + { + $classes = $this->resolvedBody->getReferencedClasses(); + foreach ($this->args as $arg) { + $classes = array_merge($classes, $arg->getReferencedClasses()); + } + + return $classes; + } + + public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array + { + $refs = []; + foreach ($this->args as $arg) { + $refs = array_merge($refs, $arg->getReferencedTemplateTypes($positionVariance)); + } + + return $refs; + } + + public function equals(Type $type): bool + { + if (!$type instanceof self) { + return false; + } + + if ($this->aliasName !== $type->aliasName || count($this->args) !== count($type->args)) { + return false; + } + + foreach ($this->args as $i => $arg) { + if (!$arg->equals($type->args[$i])) { + return false; + } + } + + return true; + } + + public function describe(VerbosityLevel $level): string + { + if ($this->args === []) { + return $this->aliasName; + } + + return sprintf( + '%s<%s>', + $this->aliasName, + implode(', ', array_map(static fn (Type $t) => $t->describe($level), $this->args)), + ); + } + + public function isResolvable(): bool + { + foreach ($this->args as $arg) { + if (TypeUtils::containsTemplateType($arg)) { + return false; + } + } + + foreach (array_keys($this->paramNames) as $i) { + if (!isset($this->args[$i]) && $this->defaults[$i] === null) { + return false; + } + } + + return true; + } + + protected function getResult(): Type + { + $map = []; + foreach ($this->paramNames as $i => $name) { + $map[$name] = $this->args[$i] ?? $this->defaults[$i] ?? $this->boundFallbacks[$i]; + } + + return TemplateTypeHelper::resolveTemplateTypes( + $this->resolvedBody, + new TemplateTypeMap($map), + TemplateTypeVarianceMap::createEmpty(), + TemplateTypeVariance::createInvariant(), + ); + } + + /** + * @param callable(Type): Type $cb + */ + public function traverse(callable $cb): Type + { + $newArgs = array_map($cb, $this->args); + + foreach ($this->args as $i => $arg) { + if ($arg !== $newArgs[$i]) { + return new self( + $this->aliasName, + $this->resolvedBody, + $this->paramNames, + $newArgs, + $this->defaults, + $this->boundFallbacks, + ); + } + } + + return $this; + } + + public function traverseSimultaneously(Type $right, callable $cb): Type + { + if (!$right instanceof self) { + return $this; + } + + $newArgs = []; + $changed = false; + foreach ($this->args as $i => $arg) { + $newArg = isset($right->args[$i]) ? $cb($arg, $right->args[$i]) : $arg; + if ($newArg !== $arg) { + $changed = true; + } + + $newArgs[] = $newArg; + } + + if (!$changed) { + return $this; + } + + return new self( + $this->aliasName, + $this->resolvedBody, + $this->paramNames, + $newArgs, + $this->defaults, + $this->boundFallbacks, + ); + } + + public function toPhpDocNode(): TypeNode + { + if ($this->args === []) { + return new IdentifierTypeNode($this->aliasName); + } + + return new GenericTypeNode( + new IdentifierTypeNode($this->aliasName), + array_map(static fn (Type $t) => $t->toPhpDocNode(), $this->args), + ); + } + +} diff --git a/src/Type/TypeAlias.php b/src/Type/TypeAlias.php index 43798ff1435..d9cf5df3aac 100644 --- a/src/Type/TypeAlias.php +++ b/src/Type/TypeAlias.php @@ -3,10 +3,19 @@ namespace PHPStan\Type; use PHPStan\Analyser\NameScope; +use PHPStan\PhpDoc\Tag\TemplateTag; use PHPStan\PhpDoc\TypeNodeResolver; +use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\Turbo\ReferencedByTurboExtension; +use PHPStan\Type\Generic\TemplateTypeFactory; +use PHPStan\Type\Generic\TemplateTypeMap; +use PHPStan\Type\Generic\TemplateTypeScope; +use PHPStan\Type\Generic\TemplateTypeVariance; +use function array_map; +use function array_values; +use function count; #[ReferencedByTurboExtension(key: 'typeAlias')] final class TypeAlias @@ -14,9 +23,14 @@ final class TypeAlias private ?Type $resolvedType = null; + /** + * @param TemplateTagValueNode[] $templateTagValueNodes + */ public function __construct( private TypeNode $typeNode, private NameScope $nameScope, + private array $templateTagValueNodes = [], + private string $aliasName = '', ) { } @@ -28,12 +42,103 @@ public static function invalid(): self return $self; } + /** + * Returns the type with TemplateType placeholders for any declared template params. + * For non-generic aliases this is the fully-resolved concrete type. + */ public function resolve(TypeNodeResolver $typeNodeResolver): Type { - return $this->resolvedType ??= $typeNodeResolver->resolve( - $this->typeNode, - $this->nameScope, + if ($this->resolvedType !== null) { + return $this->resolvedType; + } + + $nameScope = $this->nameScope; + + if (count($this->templateTagValueNodes) > 0) { + $nameScope = $this->buildNameScopeWithTemplates($typeNodeResolver, $nameScope); + } + + return $this->resolvedType = $typeNodeResolver->resolve($this->typeNode, $nameScope); + } + + /** Whether this alias was declared with type parameters (e.g. @phpstan-type Foo). */ + public function isGeneric(): bool + { + return count($this->templateTagValueNodes) > 0; + } + + /** + * @return TemplateTagValueNode[] + */ + public function getTemplateTagValueNodes(): array + { + return $this->templateTagValueNodes; + } + + /** + * Creates a GenericTypeAliasType for this alias with the given type arguments. + * + * @param list $args Concrete or partially-resolved type arguments in parameter order. + */ + public function createApplicationType(TypeNodeResolver $typeNodeResolver, array $args): GenericTypeAliasType + { + $resolvedBody = $this->resolve($typeNodeResolver); + + $paramNames = []; + $defaults = []; + $boundFallbacks = []; + + foreach (array_values($this->templateTagValueNodes) as $tvn) { + $paramNames[] = $tvn->name; + $defaults[] = $tvn->default !== null + ? $typeNodeResolver->resolve($tvn->default, $this->nameScope) + : null; + $boundFallbacks[] = $tvn->bound !== null + ? $typeNodeResolver->resolve($tvn->bound, $this->nameScope) + : new MixedType(true); + } + + return new GenericTypeAliasType( + $this->aliasName, + $resolvedBody, + $paramNames, + $args, + $defaults, + $boundFallbacks, ); } + /** + * Builds a NameScope augmented with TemplateType placeholders for each declared template param, + * so the alias body can reference them (e.g. `TFilter` resolves to a TemplateType). + */ + private function buildNameScopeWithTemplates(TypeNodeResolver $typeNodeResolver, NameScope $nameScope): NameScope + { + $templateTags = []; + foreach ($this->templateTagValueNodes as $templateTagValueNode) { + $templateTags[$templateTagValueNode->name] = new TemplateTag( + $templateTagValueNode->name, + $templateTagValueNode->bound !== null + ? $typeNodeResolver->resolve($templateTagValueNode->bound, $nameScope) + : new MixedType(true), + $templateTagValueNode->default !== null + ? $typeNodeResolver->resolve($templateTagValueNode->default, $nameScope) + : null, + TemplateTypeVariance::createInvariant(), + ); + } + + $className = $nameScope->getClassNameForTypeAlias(); + $templateTypeScope = $className !== null && $this->aliasName !== '' + ? TemplateTypeScope::createWithTypeAlias($className, $this->aliasName) + : TemplateTypeScope::createWithAnonymousFunction(); + + $templateTypeMap = new TemplateTypeMap(array_map( + static fn (TemplateTag $tag): Type => TemplateTypeFactory::fromTemplateTag($templateTypeScope, $tag), + $templateTags, + )); + + return $nameScope->withTemplateTypeMap($templateTypeMap, $templateTags); + } + } diff --git a/src/Type/UsefulTypeAliasResolver.php b/src/Type/UsefulTypeAliasResolver.php index d35864b3a1a..af958540e03 100644 --- a/src/Type/UsefulTypeAliasResolver.php +++ b/src/Type/UsefulTypeAliasResolver.php @@ -119,7 +119,18 @@ private function resolveLocalTypeAlias(string $aliasName, NameScope $nameScope): try { $unresolvedAlias = $localTypeAliases[$aliasName]; - $resolvedAliasType = $unresolvedAlias->resolve($this->typeNodeResolver); + + // For a generic alias used bare (no type args provided), build a GenericTypeAliasType + // with empty args. If all params have declared defaults, isResolvable() will be true and + // the type is immediately expanded to the concrete default form. When at least one param + // has no default, the GenericTypeAliasType stays unresolved so that + // MissingTypehintCheck::getNonGenericObjectTypesWithGenericClass() can detect the bare-usage error. + if ($unresolvedAlias->isGeneric()) { + $appType = $unresolvedAlias->createApplicationType($this->typeNodeResolver, []); + $resolvedAliasType = $appType->isResolvable() ? $appType->resolve() : $appType; + } else { + $resolvedAliasType = $unresolvedAlias->resolve($this->typeNodeResolver); + } } catch (CircularTypeAliasDefinitionException) { $resolvedAliasType = new CircularTypeAliasErrorType(); } diff --git a/test-generic-aliases.php b/test-generic-aliases.php new file mode 100644 index 00000000000..6cd90a0efd9 --- /dev/null +++ b/test-generic-aliases.php @@ -0,0 +1,230 @@ + + * @phpstan-type ProviderRequest array{ + * filters?: TFilter, + * limit?: int, + * offset?: int, + * } + */ +abstract class Provider +{ + /** + * @param ProviderRequest $request + * @return array + */ + public function find(array $request): array { + return []; + } +} + +/** + * @phpstan-type AppraisalFilter array{skuId?: int, condition?: string} + * @extends Provider + */ +final class SkuProvider extends Provider +{ + #[\Override] + public function find(array $request): array + { + // PHPStan now knows $request is array{filters?: array{skuId?: int, condition?: string}, ...} + $filters = $request['filters'] ?? []; + + // This is int|null, not mixed! + $skuId = $filters['skuId'] ?? null; + + return [$skuId]; + } +} + +// --------------------------------------------------------------------------- +// Two-param alias +// --------------------------------------------------------------------------- + +/** + * @phpstan-type Pair array{first: TFirst, second: TSecond} + */ +final class PairHolder +{ + /** + * @param Pair $pair + */ + public function use(array $pair): void + { + echo $pair['first']; // string + echo (string) $pair['second']; // int → cast to string for echo + } +} + +// --------------------------------------------------------------------------- +// With default +// --------------------------------------------------------------------------- + +/** + * @phpstan-type Response> array{data: TData, status: int} + */ +final class ApiClient +{ + /** + * @return Response + */ + public function getUser(): array + { + return ['data' => ['id' => 1, 'name' => 'Alice'], 'status' => 200]; + } +} + +// --------------------------------------------------------------------------- +// @return of generic alias +// --------------------------------------------------------------------------- + +/** + * @phpstan-type Page array{items: list, total: int, page: int} + */ +final class PagedRepo +{ + /** + * @return Page<\stdClass> // resolves to array{items: list, total: int, page: int} + */ + public function getPage(): array + { + return ['items' => [], 'total' => 0, 'page' => 1]; + } +} + +// --------------------------------------------------------------------------- +// @var property annotation +// --------------------------------------------------------------------------- + +/** + * @phpstan-type Config array{key: string, value: TValue} + */ +final class Settings +{ + /** @var Config */ + public array $timeout = ['key' => 'timeout', 'value' => 30]; + + /** @var Config */ + public array $name = ['key' => 'name', 'value' => 'default']; + + public function check(): void + { + // $this->timeout['value'] — int + // $this->name['value'] — string + } +} + +// --------------------------------------------------------------------------- +// Nested generic alias (alias referencing another generic alias with type args) +// --------------------------------------------------------------------------- + +/** + * @phpstan-type Item array{id: int, data: T} + * @phpstan-type ItemList list> + */ +final class ItemRepo +{ + /** + * @param ItemList $items // list + */ + public function process(array $items): void + { + // $items[0]['data'] — string + } +} + +// --------------------------------------------------------------------------- +// @phpstan-import-type of a generic alias, then used with type args +// --------------------------------------------------------------------------- + +/** + * @phpstan-import-type Pair from PairHolder + */ +final class PairConsumer +{ + /** + * @param Pair $p + */ + public function check(array $p): void + { + // $p['first'] — int + // $p['second'] — bool + } +} + +// --------------------------------------------------------------------------- +// Default type arg — using alias WITHOUT args should be OK (default kicks in) +// --------------------------------------------------------------------------- + +/** + * @phpstan-type WithDefault array{value: T} + */ +final class DefaultConsumer +{ + /** + * @param WithDefault $explicit no error: type arg provided + * @param WithDefault $implicit no error: T has a default (string) + */ + public function check(array $explicit, array $implicit): void + { + // $explicit['value'] — int + // $implicit['value'] — string (default applied ✓) + } +} + +// --------------------------------------------------------------------------- +// Generic alias in a standalone function (not a class method) +// --------------------------------------------------------------------------- + +/** + * @phpstan-type Range array{min: T, max: T} + */ +final class RangeHolder +{ + /** + * @param Range $r + * @return Range + */ + public function convert(array $r): array + { + // $r['min'] — int + return ['min' => (float) $r['min'], 'max' => (float) $r['max']]; + } +} + +// --------------------------------------------------------------------------- +// Too many type args — should error +// --------------------------------------------------------------------------- + +/** + * @phpstan-type Single array{value: T} + */ +final class TooManyArgs +{ + /** + * @param Single $x ERROR: Single takes 1 type arg, 2 given + * @phpstan-ignore parameter.unresolvableType, missingType.iterableValue + */ + public function check(array $x): void {} +} + +// --------------------------------------------------------------------------- +// Too few required type args (partial application of multi-param alias) — should error +// --------------------------------------------------------------------------- + +/** + * @phpstan-type KeyValue array{key: TKey, value: TValue} + */ +final class TooFewArgs +{ + /** + * @param KeyValue $x ERROR: KeyValue requires 2 type args, 1 given + * @phpstan-ignore parameter.unresolvableType, missingType.iterableValue + */ + public function check(array $x): void {} +} diff --git a/test-type-error-demo.php b/test-type-error-demo.php new file mode 100644 index 00000000000..af48b18090a --- /dev/null +++ b/test-type-error-demo.php @@ -0,0 +1,26 @@ +> array{filters?: TFilter, limit?: int} + */ +final class ProviderTypeError +{ + /** + * @param Request $req + */ + public function find(array $req): void + { + $filters = $req['filters'] ?? []; + + // PHPStan now knows $filters is array{skuId?: int, condition?: string} + // so this arithmetic on int + string IS caught: + $bad = ($filters['skuId'] ?? 0) + 'hello'; // Error: binary + with string + + // And this wrong-type pass is also caught: + $this->takeString($filters['skuId'] ?? 0); // Error: passing int where string expected + } + + public function takeString(string $s): void {} +} + diff --git a/tests/PHPStan/Analyser/nsrt/generic-type-aliases.php b/tests/PHPStan/Analyser/nsrt/generic-type-aliases.php new file mode 100644 index 00000000000..fe9bc243470 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/generic-type-aliases.php @@ -0,0 +1,235 @@ + = array> array{ + * filters?: TFilter, + * limit?: int, + * offset?: int, + * } + */ +abstract class Provider +{ + /** + * @param Request $request + */ + abstract public function find(array $request): void; +} + +class ConcreteProvider extends Provider +{ + public function find(array $request): void + { + // Access an optional key – PHPStan represents the array{filters?:Filter,...} type + // as a union of the possible ConstantArrayType shapes (with/without the optional key). + // The important thing is that Filter IS substituted: `filters` carries array{skuId?: int, condition?: string}. + assertType('array{filters?: array{skuId?: int, condition?: string}, limit?: int, offset?: int}', $request); + } +} + +// ------------------------------------------------------- +// Direct usage in the same class – simpler and more reliable test +// ------------------------------------------------------- + +/** + * @phpstan-type AppraisalFilter array{skuId?: int, condition?: string} + * + * @phpstan-type ProviderRequest> array{ + * filters?: TFilter, + * limit?: int, + * offset?: int, + * } + */ +class DirectUsage +{ + /** + * @param ProviderRequest $request + */ + public function find(array $request): void + { + assertType('array{filters?: array{skuId?: int, condition?: string}, limit?: int, offset?: int}', $request); + } +} + +// ------------------------------------------------------- +// Two template params +// ------------------------------------------------------- + +/** + * @phpstan-type Pair array{first: TFirst, second: TSecond} + */ +class PairHolder +{ + /** + * @param Pair $pair + */ + public function check(array $pair): void + { + assertType('string', $pair['first']); + assertType('int', $pair['second']); + } +} + +// ------------------------------------------------------- +// @return of generic alias with bound constraint +// ------------------------------------------------------- + +/** + * @phpstan-type Range array{min: T, max: T} + */ +class RangeHolder +{ + /** + * @param Range $r + * @return Range + */ + public function convert(array $r): array + { + assertType('int', $r['min']); + assertType('int', $r['max']); + $result = ['min' => (float) $r['min'], 'max' => (float) $r['max']]; + assertType('array{min: float, max: float}', $result); + return $result; + } +} + +// ------------------------------------------------------- +// @var property annotation +// ------------------------------------------------------- + +/** + * @phpstan-type Config array{key: string, value: TValue} + */ +class Settings +{ + /** @var Config */ + public array $timeout = ['key' => 'timeout', 'value' => 30]; + + /** @var Config */ + public array $name = ['key' => 'name', 'value' => 'default']; + + public function check(): void + { + assertType('int', $this->timeout['value']); + assertType('string', $this->name['value']); + } +} + +// ------------------------------------------------------- +// Test with list +// ------------------------------------------------------- + +/** + * @phpstan-type Paged array{items: list, total: int} + */ +class Repo +{ + /** + * @param Paged<\stdClass> $result + */ + public function check(array $result): void + { + assertType('list', $result['items']); + assertType('int', $result['total']); + } +} + +// ------------------------------------------------------- +// Nested generic alias (alias referencing another generic alias) +// ------------------------------------------------------- + +/** + * @phpstan-type Item array{id: int, data: T} + * @phpstan-type ItemList list> + */ +class ItemRepo +{ + /** + * @param ItemList $items + */ + public function process(array $items): void + { + assertType('list', $items); + } +} + +// ------------------------------------------------------- +// Test with two template params +// ------------------------------------------------------- + +/** + * @phpstan-type Map array + */ +class MapHolder +{ + /** + * @param Map $m + */ + public function check(array $m): void + { + assertType('array', $m); + } +} + +// ------------------------------------------------------- +// Default param: explicit arg vs bare usage (default applied) +// ------------------------------------------------------- + +/** + * @phpstan-type WithDefault array{value: T} + */ +class DefaultHolder +{ + /** + * @param WithDefault $explicit explicit arg overrides default + * @param WithDefault $implicit bare usage – T defaults to string + */ + public function check(array $explicit, array $implicit): void + { + assertType('int', $explicit['value']); + assertType('string', $implicit['value']); + } +} + +// ------------------------------------------------------- +// @phpstan-import-type of a generic alias +// ------------------------------------------------------- + +/** + * @phpstan-import-type Map from MapHolder + * @phpstan-import-type Paged from Repo + * @phpstan-import-type Pair from PairHolder + */ +class ImportConsumer +{ + /** + * @param Map $m + */ + public function mapCheck(array $m): void + { + assertType('array', $m); + } + + /** + * @param Paged<\DateTime> $p + */ + public function pagedCheck(array $p): void + { + assertType('list', $p['items']); + assertType('int', $p['total']); + } + + /** + * @param Pair $p + */ + public function pairCheck(array $p): void + { + assertType('int', $p['first']); + assertType('bool', $p['second']); + } +} diff --git a/tests/PHPStan/Rules/Classes/data/local-type-aliases.php b/tests/PHPStan/Rules/Classes/data/local-type-aliases.php index 152e77d8d7b..40096ec6208 100644 --- a/tests/PHPStan/Rules/Classes/data/local-type-aliases.php +++ b/tests/PHPStan/Rules/Classes/data/local-type-aliases.php @@ -104,3 +104,14 @@ class GenericsCheck { } + +/** + * Generic type alias – template params should not trigger "invalid type" errors. + * + * @phpstan-type GenericShape array{item: TItem, extra: TExtra} + */ +class GenericAlias +{ + +} + diff --git a/tests/PHPStan/Rules/Methods/MissingMethodParameterTypehintRuleTest.php b/tests/PHPStan/Rules/Methods/MissingMethodParameterTypehintRuleTest.php index f722e9e6e23..9a1b717cbc8 100644 --- a/tests/PHPStan/Rules/Methods/MissingMethodParameterTypehintRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MissingMethodParameterTypehintRuleTest.php @@ -189,4 +189,38 @@ public function testRepeatedIterableTypeReportedOnce(): void ]); } + public function testGenericTypeAliasMissingTypehint(): void + { + $this->analyse([__DIR__ . '/data/generic-type-alias-missing-typehint.php'], [ + [ + 'Method GenericTypeAliasMissingTypehint\RawUsage::check() has parameter $b with generic type alias Filter but does not specify its types: TItem', + 18, + ], + [ + 'Method GenericTypeAliasMissingTypehint\PartialDefault::check() has parameter $noArgs with generic type alias Pair but does not specify its types: TFirst', + 61, + ], + [ + 'Method GenericTypeAliasMissingTypehint\ImportedRawUsage::check() has parameter $bad with generic type alias Filter but does not specify its types: TItem', + 77, + ], + ]); + } + + public function testGenericTypeAliasWrongArgCount(): void + { + $this->analyse([__DIR__ . '/../PhpDoc/data/generic-type-alias-wrong-arg-count.php'], [ + [ + 'Method GenericTypeAliasWrongArgCount\TooManyArgs::badParam() has parameter $x with no value type specified in iterable type array.', + 17, + MissingTypehintCheck::MISSING_ITERABLE_VALUE_TYPE_TIP, + ], + [ + 'Method GenericTypeAliasWrongArgCount\TooFewArgs::badParam() has parameter $x with no value type specified in iterable type array.', + 47, + MissingTypehintCheck::MISSING_ITERABLE_VALUE_TYPE_TIP, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php b/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php index ffbf4bded52..f9ce8237e92 100644 --- a/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php @@ -128,4 +128,30 @@ public function testInheritPhpDocReturnTypeWithNarrowerNativeReturnType(): void $this->analyse([__DIR__ . '/data/inherit-phpdoc-return-type-with-narrower-native-return-type.php'], []); } + public function testGenericTypeAliasMissingTypehint(): void + { + $this->analyse([__DIR__ . '/data/generic-type-alias-missing-typehint.php'], [ + [ + 'Method GenericTypeAliasMissingTypehint\RawUsage::getRaw() return type with generic type alias Filter does not specify its types: TItem', + 28, + ], + ]); + } + + public function testGenericTypeAliasWrongArgCount(): void + { + $this->analyse([__DIR__ . '/../PhpDoc/data/generic-type-alias-wrong-arg-count.php'], [ + [ + 'Method GenericTypeAliasWrongArgCount\TooManyArgs::badReturn() return type has no value type specified in iterable type array.', + 22, + MissingTypehintCheck::MISSING_ITERABLE_VALUE_TYPE_TIP, + ], + [ + 'Method GenericTypeAliasWrongArgCount\TooFewArgs::badReturn() return type has no value type specified in iterable type array.', + 52, + MissingTypehintCheck::MISSING_ITERABLE_VALUE_TYPE_TIP, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/generic-type-alias-missing-typehint.php b/tests/PHPStan/Rules/Methods/data/generic-type-alias-missing-typehint.php new file mode 100644 index 00000000000..e7dff97252a --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/generic-type-alias-missing-typehint.php @@ -0,0 +1,79 @@ + array{items: list} + */ +class RawUsage +{ + /** + * @param Filter $a OK: type arg provided + * @param Filter $b ERROR: Filter requires 1 type arg + */ + public function check(array $a, array $b): void {} + + /** + * @return Filter OK + */ + public function getFiltered(): array { return ['items' => []]; } + + /** + * @return Filter ERROR: Filter requires 1 type arg + */ + public function getRaw(): array { return ['items' => []]; } +} + +// --------------------------------------------------------------------------- +// Alias with a default — bare usage should NOT error +// --------------------------------------------------------------------------- + +/** + * @phpstan-type WithDefault array{value: T} + */ +class DefaultedUsage +{ + /** + * @param WithDefault $implicit OK: T has a default + * @param WithDefault $explicit OK: T provided + */ + public function check(array $implicit, array $explicit): void {} +} + +// --------------------------------------------------------------------------- +// Two-param alias, one required, one defaulted +// --------------------------------------------------------------------------- + +/** + * @phpstan-type Pair array{first: TFirst, second: TSecond} + */ +class PartialDefault +{ + /** + * @param Pair $oneArg OK: TFirst provided, TSecond defaults to bool + * @param Pair $twoArgs OK: both provided + * @param Pair $noArgs ERROR: TFirst has no default + */ + public function check(array $oneArg, array $twoArgs, array $noArgs): void {} +} + +// --------------------------------------------------------------------------- +// Imported generic alias — raw usage should also error +// --------------------------------------------------------------------------- + +/** + * @phpstan-import-type Filter from RawUsage + */ +class ImportedRawUsage +{ + /** + * @param Filter $ok OK + * @param Filter $bad ERROR: Filter requires 1 type arg + */ + public function check(array $ok, array $bad): void {} +} + diff --git a/tests/PHPStan/Rules/PhpDoc/IncompatiblePhpDocTypeRuleTest.php b/tests/PHPStan/Rules/PhpDoc/IncompatiblePhpDocTypeRuleTest.php index c284aea9916..a6a2e6a7518 100644 --- a/tests/PHPStan/Rules/PhpDoc/IncompatiblePhpDocTypeRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/IncompatiblePhpDocTypeRuleTest.php @@ -548,4 +548,26 @@ public function testBug11510(): void $this->analyse([__DIR__ . '/data/bug-11510.php'], []); } + public function testGenericTypeAliasWrongArgCount(): void + { + $this->analyse([__DIR__ . '/data/generic-type-alias-wrong-arg-count.php'], [ + [ + 'PHPDoc tag @param for parameter $x contains unresolvable type.', + 17, + ], + [ + 'PHPDoc tag @return contains unresolvable type.', + 22, + ], + [ + 'PHPDoc tag @param for parameter $x contains unresolvable type.', + 47, + ], + [ + 'PHPDoc tag @return contains unresolvable type.', + 52, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/PhpDoc/data/generic-type-alias-wrong-arg-count.php b/tests/PHPStan/Rules/PhpDoc/data/generic-type-alias-wrong-arg-count.php new file mode 100644 index 00000000000..443c9d495dc --- /dev/null +++ b/tests/PHPStan/Rules/PhpDoc/data/generic-type-alias-wrong-arg-count.php @@ -0,0 +1,66 @@ + array{value: T} + */ +final class TooManyArgs +{ + /** + * @param Single $x + */ + public function badParam(array $x): void {} + + /** + * @return Single + */ + public function badReturn(): array { return ['value' => 1]; } + + /** + * @param Single $ok + */ + public function goodParam(array $ok): void {} + + /** + * @return Single + */ + public function goodReturn(): array { return ['value' => 1]; } +} + +// --------------------------------------------------------------------------- +// Too few required type args (partial application of multi-param alias) +// --------------------------------------------------------------------------- + +/** + * @phpstan-type KeyVal array{key: TKey, value: TValue} + */ +final class TooFewArgs +{ + /** + * @param KeyVal $x + */ + public function badParam(array $x): void {} + + /** + * @return KeyVal + */ + public function badReturn(): array { return ['key' => 'k', 'value' => 'v']; } + + /** + * @param KeyVal $ok + */ + public function goodParam(array $ok): void {} + + /** + * @return KeyVal + */ + public function goodReturn(): array { return ['key' => 'k', 'value' => 1]; } +} + + + diff --git a/turbo-ext/src/TemplateTypeScope.cpp b/turbo-ext/src/TemplateTypeScope.cpp index 013b10fd285..c5587f2dfb5 100644 --- a/turbo-ext/src/TemplateTypeScope.cpp +++ b/turbo-ext/src/TemplateTypeScope.cpp @@ -18,6 +18,9 @@ namespace sigs = ptdecl::TemplateTypeScope::sig; zend_class_entry *pt_ce_template_type_scope = NULL; +/* the function name prefix of a type alias scope (createWithTypeAlias()) */ +#define PT_TTS_TYPE_ALIAS_PREFIX "__typeAlias_" + namespace phpstanturbo { /* Mirrors PHPStan\Type\Generic\TemplateTypeScope. State lives in the PHP @@ -51,6 +54,14 @@ class TemplateTypeScope } static zv::Val createWithAnonymousFunction() { return create(NULL, NULL); } + /* new self($className, '__typeAlias_' . $aliasName) */ + static zv::Val createWithTypeAlias(zend_string *className, zend_string *aliasName) + { + zend_string *functionName = zend_string_concat2(PT_LC(PT_TTS_TYPE_ALIAS_PREFIX), ZSTR_VAL(aliasName), ZSTR_LEN(aliasName)); + zv::Val scope = create(className, functionName); + zend_string_release(functionName); + return scope; + } static zv::Val createWithFunction(zend_string *functionName) { return create(NULL, functionName); } static zv::Val createWithMethod(zend_string *className, zend_string *functionName) { return create(className, functionName); } static zv::Val createWithClass(zend_string *className) { return create(className, NULL); } @@ -68,6 +79,29 @@ class TemplateTypeScope return value == NULL ? zv::Val() : zv::Val::copyOf(zv::Ref(value)); } + /* isTypeAlias(): $functionName !== null && str_starts_with($functionName, + * '__typeAlias_'); false = pending exception */ + [[nodiscard]] bool isTypeAlias(bool &out) const + { + zval *functionName = this->functionName(); + if (UNEXPECTED(functionName == NULL)) return false; + out = Z_TYPE_P(functionName) == IS_STRING + && Z_STRLEN_P(functionName) >= sizeof(PT_TTS_TYPE_ALIAS_PREFIX) - 1 + && memcmp(Z_STRVAL_P(functionName), PT_TTS_TYPE_ALIAS_PREFIX, sizeof(PT_TTS_TYPE_ALIAS_PREFIX) - 1) == 0; + return true; + } + + /* getTypeAliasName(): the function name after the '__typeAlias_' prefix, + * null when not a type alias scope; UNDEF = pending exception */ + zv::Val getTypeAliasName() const + { + bool typeAlias; + if (UNEXPECTED(!isTypeAlias(typeAlias))) return zv::Val(); + if (!typeAlias) return zv::Val::null(); + zval *functionName = this->functionName(); + return zv::Val::string(Z_STRVAL_P(functionName) + sizeof(PT_TTS_TYPE_ALIAS_PREFIX) - 1, Z_STRLEN_P(functionName) - (sizeof(PT_TTS_TYPE_ALIAS_PREFIX) - 1)); + } + /* equals(): both names identical (=== on ?string); false = pending * exception */ [[nodiscard]] bool equals(zend_object *other, bool &out) const @@ -193,6 +227,12 @@ PT_MINIT_REGISTRATION(pt_register_template_type_scope) PT_RETURN_VAL(TemplateTypeScope::createWithAnonymousFunction()); }); + cls.method(sigs::createWithTypeAlias, [](INTERNAL_FUNCTION_PARAMETERS) { + zend_string *className, *aliasName; + if (!zp::parse(execute_data, className, aliasName)) RETURN_THROWS(); + PT_RETURN_VAL(TemplateTypeScope::createWithTypeAlias(className, aliasName)); + }); + cls.method(sigs::createWithFunction, [](INTERNAL_FUNCTION_PARAMETERS) { zend_string *functionName; if (!zp::parse(execute_data, functionName)) RETURN_THROWS(); @@ -227,6 +267,18 @@ PT_MINIT_REGISTRATION(pt_register_template_type_scope) PT_RETURN_VAL(PT_TTS_THIS.getFunctionName()); }); + cls.method(sigs::isTypeAlias, [](INTERNAL_FUNCTION_PARAMETERS) { + ZEND_PARSE_PARAMETERS_NONE(); + bool result; + if (UNEXPECTED(!PT_TTS_THIS.isTypeAlias(result))) RETURN_THROWS(); + RETURN_BOOL(result); + }); + + cls.method(sigs::getTypeAliasName, [](INTERNAL_FUNCTION_PARAMETERS) { + ZEND_PARSE_PARAMETERS_NONE(); + PT_RETURN_VAL(PT_TTS_THIS.getTypeAliasName()); + }); + cls.method(sigs::equals, [](INTERNAL_FUNCTION_PARAMETERS) { zval *other; ZEND_PARSE_PARAMETERS_START(1, 1) diff --git a/turbo-ext/src/generated/TemplateTypeScope.h b/turbo-ext/src/generated/TemplateTypeScope.h index 7c0b9707676..75f6e75d188 100644 --- a/turbo-ext/src/generated/TemplateTypeScope.h +++ b/turbo-ext/src/generated/TemplateTypeScope.h @@ -32,31 +32,40 @@ inline constexpr char strings[] = "\0" /* 0 */ "PHPStan\\Type\\Generic\\TemplateTypeScope\0" /* 1 */ "createWithAnonymousFunction\0" /* 40 */ - "functionName\0" /* 68 */ - "createWithFunction\0" /* 81 */ - "className\0" /* 100 */ - "createWithMethod\0" /* 110 */ - "createWithClass\0" /* 127 */ - "__construct\0" /* 143 */ - "getClassName\0" /* 155 */ - "getFunctionName\0" /* 168 */ - "other\0" /* 184 */ - "equals\0" /* 190 */ - "describe"; /* 197 */ + "className\0" /* 68 */ + "aliasName\0" /* 78 */ + "createWithTypeAlias\0" /* 88 */ + "functionName\0" /* 108 */ + "createWithFunction\0" /* 121 */ + "createWithMethod\0" /* 140 */ + "createWithClass\0" /* 157 */ + "__construct\0" /* 173 */ + "getClassName\0" /* 185 */ + "getFunctionName\0" /* 198 */ + "isTypeAlias\0" /* 214 */ + "getTypeAliasName\0" /* 226 */ + "other\0" /* 243 */ + "equals\0" /* 249 */ + "describe"; /* 256 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 1), /* createWithAnonymousFunction return */ - reg::packed(68, MAY_BE_STRING), /* createWithFunction $functionName */ + reg::packed(68, MAY_BE_STRING), /* createWithTypeAlias $className */ + reg::packed(78, MAY_BE_STRING), /* createWithTypeAlias $aliasName */ + reg::packed(0, 0, 1), /* createWithTypeAlias return */ + reg::packed(108, MAY_BE_STRING), /* createWithFunction $functionName */ reg::packed(0, 0, 1), /* createWithFunction return */ - reg::packed(100, MAY_BE_STRING), /* createWithMethod $className */ - reg::packed(68, MAY_BE_STRING), /* createWithMethod $functionName */ + reg::packed(68, MAY_BE_STRING), /* createWithMethod $className */ + reg::packed(108, MAY_BE_STRING), /* createWithMethod $functionName */ reg::packed(0, 0, 1), /* createWithMethod return */ - reg::packed(100, MAY_BE_STRING), /* createWithClass $className */ + reg::packed(68, MAY_BE_STRING), /* createWithClass $className */ reg::packed(0, 0, 1), /* createWithClass return */ - reg::packed(100, MAY_BE_NULL | MAY_BE_STRING), /* __construct $className */ - reg::packed(68, MAY_BE_NULL | MAY_BE_STRING), /* __construct $functionName */ + reg::packed(68, MAY_BE_NULL | MAY_BE_STRING), /* __construct $className */ + reg::packed(108, MAY_BE_NULL | MAY_BE_STRING), /* __construct $functionName */ reg::packed(0, MAY_BE_NULL | MAY_BE_STRING), /* getClassName return */ reg::packed(0, MAY_BE_NULL | MAY_BE_STRING), /* getFunctionName return */ - reg::packed(184, 0, 1), /* equals $other */ + reg::packed(0, MAY_BE_BOOL), /* isTypeAlias return */ + reg::packed(0, MAY_BE_NULL | MAY_BE_STRING), /* getTypeAliasName return */ + reg::packed(243, 0, 1), /* equals $other */ reg::packed(0, MAY_BE_BOOL), /* equals return */ reg::packed(0, MAY_BE_STRING), /* describe return */ }; @@ -66,14 +75,17 @@ using Sig = reg::Sig; /* the signatures of the methods the class declares itself (a used trait's are in the trait's header) */ namespace sig { inline constexpr sigtab::Sig createWithAnonymousFunction = { { 40 /* createWithAnonymousFunction */, 0, 0, 0, 0, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig createWithFunction = { { 81 /* createWithFunction */, 1, 1, 1, 2, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig createWithMethod = { { 110 /* createWithMethod */, 2, 3, 2, 5, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig createWithClass = { { 127 /* createWithClass */, 1, 6, 1, 7, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig __construct = { { 143 /* __construct */, 2, 8, 2, reg::NoArg, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getClassName = { { 155 /* getClassName */, 0, 10, 0, 10, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getFunctionName = { { 168 /* getFunctionName */, 0, 11, 0, 11, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig equals = { { 190 /* equals */, 1, 12, 1, 13, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig describe = { { 197 /* describe */, 0, 14, 0, 14, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig createWithTypeAlias = { { 88 /* createWithTypeAlias */, 2, 1, 2, 3, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig createWithFunction = { { 121 /* createWithFunction */, 1, 4, 1, 5, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig createWithMethod = { { 140 /* createWithMethod */, 2, 6, 2, 8, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig createWithClass = { { 157 /* createWithClass */, 1, 9, 1, 10, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig __construct = { { 173 /* __construct */, 2, 11, 2, reg::NoArg, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getClassName = { { 185 /* getClassName */, 0, 13, 0, 13, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getFunctionName = { { 198 /* getFunctionName */, 0, 14, 0, 14, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig isTypeAlias = { { 214 /* isTypeAlias */, 0, 15, 0, 15, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getTypeAliasName = { { 226 /* getTypeAliasName */, 0, 16, 0, 16, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig equals = { { 249 /* equals */, 1, 17, 1, 18, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig describe = { { 256 /* describe */, 0, 19, 0, 19, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::TemplateTypeScope