diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 11023bb1cd..4f232409a2 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -1553,36 +1553,43 @@ private function specifyTypesFromAsserts(TypeSpecifierContext $context, Expr\Cal foreach ($asserts as $assert) { foreach ($argsMap[substr($assert->getParameter()->getParameterName(), 1)] ?? [] as $parameterExpr) { - $assertedType = TypeTraverser::map($assert->getType(), static function (Type $type, callable $traverse) use ($argsMap, $scope): Type { - if ($type instanceof ConditionalTypeForParameter) { - $parameterName = substr($type->getParameterName(), 1); - if (array_key_exists($parameterName, $argsMap)) { - $argType = TypeCombinator::union(...array_map(static fn (Expr $expr) => $scope->getType($expr), $argsMap[$parameterName])); - $type = $type->toConditional($argType); + $assertedType = $assert->getType(); + + if ($assertedType->hasTemplateOrLateResolvableType()) { + $assertedType = TypeTraverser::map($assertedType, static function (Type $type, callable $traverse) use ($argsMap, $scope): Type { + if ($type instanceof ConditionalTypeForParameter) { + $parameterName = substr($type->getParameterName(), 1); + if (array_key_exists($parameterName, $argsMap)) { + $argType = TypeCombinator::union(...array_map(static fn (Expr $expr) => $scope->getType($expr), $argsMap[$parameterName])); + $type = $type->toConditional($argType); + } } - } - return $traverse($type); - }); + return $traverse($type); + }); + } $assertExpr = $assert->getParameter()->getExpr($parameterExpr); - $templateTypeMap = $parametersAcceptor->getResolvedTemplateTypeMap(); $containsUnresolvedTemplate = false; - TypeTraverser::map( - $assert->getOriginalType(), - static function (Type $type, callable $traverse) use ($templateTypeMap, &$containsUnresolvedTemplate) { - if ($type instanceof TemplateType && $type->getScope()->getClassName() !== null) { - $resolvedType = $templateTypeMap->getType($type->getName()); - if ($resolvedType === null || $type->getBound()->equals($resolvedType)) { - $containsUnresolvedTemplate = true; - return $type; + if ($assert->getOriginalType()->hasTemplateOrLateResolvableType()) { + $templateTypeMap = $parametersAcceptor->getResolvedTemplateTypeMap(); + + TypeTraverser::map( + $assert->getOriginalType(), + static function (Type $type, callable $traverse) use ($templateTypeMap, &$containsUnresolvedTemplate) { + if ($type instanceof TemplateType && $type->getScope()->getClassName() !== null) { + $resolvedType = $templateTypeMap->getType($type->getName()); + if ($resolvedType === null || $type->getBound()->equals($resolvedType)) { + $containsUnresolvedTemplate = true; + return $type; + } } - } - return $traverse($type); - }, - ); + return $traverse($type); + }, + ); + } $newTypes = $this->create( $assertExpr, diff --git a/src/Rules/Classes/LocalTypeAliasesCheck.php b/src/Rules/Classes/LocalTypeAliasesCheck.php index a849ecac8c..d5851b28b5 100644 --- a/src/Rules/Classes/LocalTypeAliasesCheck.php +++ b/src/Rules/Classes/LocalTypeAliasesCheck.php @@ -335,8 +335,8 @@ private function isAliasNameValid(string $aliasName, ?NameScope $nameScope): boo } $aliasNameResolvedType = $this->typeNodeResolver->resolve(new IdentifierTypeNode($aliasName), $nameScope->bypassTypeAliases()); - return ($aliasNameResolvedType->isObject()->yes() && !in_array($aliasName, ['self', 'parent'], true)) - || $aliasNameResolvedType instanceof TemplateType; // aliases take precedence over type parameters, this is reported by other rules using TemplateTypeCheck + return $aliasNameResolvedType instanceof TemplateType || // aliases take precedence over type parameters, this is reported by other rules using TemplateTypeCheck + ($aliasNameResolvedType->isObject()->yes() && !in_array($aliasName, ['self', 'parent'], true)); } /** diff --git a/src/Rules/FunctionDefinitionCheck.php b/src/Rules/FunctionDefinitionCheck.php index c888ed886f..8923179a82 100644 --- a/src/Rules/FunctionDefinitionCheck.php +++ b/src/Rules/FunctionDefinitionCheck.php @@ -591,6 +591,10 @@ private function checkParametersAcceptor( $templateTypes = $templateTypeMap->getTypes(); if (count($templateTypes) > 0) { foreach ($parametersAcceptor->getParameters() as $parameter) { + if (!$parameter->getType()->hasTemplateOrLateResolvableType()) { + continue; + } + TypeTraverser::map($parameter->getType(), static function (Type $type, callable $traverse) use (&$templateTypes): Type { if ($type instanceof TemplateType) { unset($templateTypes[$type->getName()]); diff --git a/src/Rules/PhpDoc/ConditionalReturnTypeRuleHelper.php b/src/Rules/PhpDoc/ConditionalReturnTypeRuleHelper.php index af7cfda155..0224209b0c 100644 --- a/src/Rules/PhpDoc/ConditionalReturnTypeRuleHelper.php +++ b/src/Rules/PhpDoc/ConditionalReturnTypeRuleHelper.php @@ -77,14 +77,16 @@ public function check(ExtendedParametersAcceptor $acceptor): array continue; } $templateTypes = []; - TypeTraverser::map($subjectType, static function (Type $type, callable $traverse) use (&$templateTypes): Type { - if ($type instanceof TemplateType) { - $templateTypes[] = $type; - return $type; - } - - return $traverse($type); - }); + if ($subjectType->hasTemplateOrLateResolvableType()) { + TypeTraverser::map($subjectType, static function (Type $type, callable $traverse) use (&$templateTypes): Type { + if ($type instanceof TemplateType) { + $templateTypes[] = $type; + return $type; + } + + return $traverse($type); + }); + } if (count($templateTypes) === 0) { $errors[] = RuleErrorBuilder::message(sprintf('Conditional return type uses subject type %s which is not part of PHPDoc @template tags.', $subjectType->describe(VerbosityLevel::typeOnly())))