Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 29 additions & 22 deletions src/Analyser/TypeSpecifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions src/Rules/Classes/LocalTypeAliasesCheck.php
Original file line number Diff line number Diff line change
Expand Up @@ -335,8 +335,8 @@
}

$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));

Check warning on line 339 in src/Rules/Classes/LocalTypeAliasesCheck.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.4, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ $aliasNameResolvedType = $this->typeNodeResolver->resolve(new IdentifierTypeNode($aliasName), $nameScope->bypassTypeAliases()); 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)); + (!$aliasNameResolvedType->isObject()->no() && !in_array($aliasName, ['self', 'parent'], true)); } /**

Check warning on line 339 in src/Rules/Classes/LocalTypeAliasesCheck.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.3, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ $aliasNameResolvedType = $this->typeNodeResolver->resolve(new IdentifierTypeNode($aliasName), $nameScope->bypassTypeAliases()); 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)); + (!$aliasNameResolvedType->isObject()->no() && !in_array($aliasName, ['self', 'parent'], true)); } /**
}

/**
Expand Down
4 changes: 4 additions & 0 deletions src/Rules/FunctionDefinitionCheck.php
Original file line number Diff line number Diff line change
Expand Up @@ -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()]);
Expand Down
18 changes: 10 additions & 8 deletions src/Rules/PhpDoc/ConditionalReturnTypeRuleHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -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())))
Expand Down
Loading