Skip to content
Merged
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
50 changes: 6 additions & 44 deletions src/Rules/Variables/ParameterOutAssignedTypeRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,9 @@
use PHPStan\Analyser\Scope;
use PHPStan\DependencyInjection\RegisteredRule;
use PHPStan\Node\VariableAssignNode;
use PHPStan\Reflection\ExtendedMethodReflection;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Rules\RuleLevelHelper;
use PHPStan\Type\ErrorType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeUtils;
use PHPStan\Type\VerbosityLevel;
use function is_string;
use function sprintf;

/**
* @implements Rule<VariableAssignNode>
Expand All @@ -25,7 +18,7 @@ final class ParameterOutAssignedTypeRule implements Rule
{

public function __construct(
private RuleLevelHelper $ruleLevelHelper,
private ParameterOutTypeCheck $parameterOutTypeCheck,
)
{
}
Expand Down Expand Up @@ -78,45 +71,14 @@ public function processNode(Node $node, Scope $scope): array

$outType = TypeUtils::resolveLateResolvableTypes($outType);

$typeResult = $this->ruleLevelHelper->findTypeToCheck(
return $this->parameterOutTypeCheck->check(
$scope,
$inFunction,
$foundParameter,
$node->getAssignedExpr(),
'',
static fn (Type $type): bool => $outType->isSuperTypeOf($type)->yes(),
$outType,
$isParamOutType,
);
$type = $typeResult->getType();
if ($type instanceof ErrorType) {
return $typeResult->getUnknownClassErrors();
}

$assignedExprType = $scope->getType($node->getAssignedExpr());
if ($outType->isSuperTypeOf($assignedExprType)->yes()) {
return [];
}

if ($inFunction instanceof ExtendedMethodReflection) {
$functionDescription = sprintf('method %s::%s()', $inFunction->getDeclaringClass()->getDisplayName(), $inFunction->getName());
} else {
$functionDescription = sprintf('function %s()', $inFunction->getName());
}

$verbosityLevel = VerbosityLevel::getRecommendedLevelByType($outType, $assignedExprType);
$errorBuilder = RuleErrorBuilder::message(sprintf(
'Parameter &$%s %s of %s expects %s, %s given.',
$foundParameter->getName(),
$isParamOutType ? '@param-out type' : 'by-ref type',
$functionDescription,
$outType->describe($verbosityLevel),
$assignedExprType->describe($verbosityLevel),
))->identifier(sprintf('%s.type', $isParamOutType ? 'paramOut' : 'parameterByRef'));

if (!$isParamOutType) {
$errorBuilder->tip('You can change the parameter out type with @param-out PHPDoc tag.');
}

return [
$errorBuilder->build(),
];
}

}
47 changes: 7 additions & 40 deletions src/Rules/Variables/ParameterOutExecutionEndTypeRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,8 @@
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Rules\IdentifierRuleError;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Rules\RuleLevelHelper;
use PHPStan\Type\ErrorType;
use PHPStan\Type\NeverType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeUtils;
use PHPStan\Type\VerbosityLevel;
use function sprintf;

/**
* @implements Rule<ExecutionEndNode>
Expand All @@ -29,7 +23,7 @@ final class ParameterOutExecutionEndTypeRule implements Rule
{

public function __construct(
private RuleLevelHelper $ruleLevelHelper,
private ParameterOutTypeCheck $parameterOutTypeCheck,
)
{
}
Expand Down Expand Up @@ -94,41 +88,14 @@ private function processSingleParameter(

$outType = TypeUtils::resolveLateResolvableTypes($outType);

$variableExpr = new Node\Expr\Variable($parameter->getName());
$typeResult = $this->ruleLevelHelper->findTypeToCheck(
return $this->parameterOutTypeCheck->check(
$scope,
$variableExpr,
'',
static fn (Type $type): bool => $outType->isSuperTypeOf($type)->yes(),
$inFunction,
$parameter,
new Node\Expr\Variable($parameter->getName()),
$outType,
true, // this rule only runs when @param-out is present
);
$type = $typeResult->getType();
if ($type instanceof ErrorType) {
return $typeResult->getUnknownClassErrors();
}

$assignedExprType = $scope->getType($variableExpr);
if ($outType->isSuperTypeOf($assignedExprType)->yes()) {
return [];
}

if ($inFunction instanceof ExtendedMethodReflection) {
$functionDescription = sprintf('method %s::%s()', $inFunction->getDeclaringClass()->getDisplayName(), $inFunction->getName());
} else {
$functionDescription = sprintf('function %s()', $inFunction->getName());
}

$verbosityLevel = VerbosityLevel::getRecommendedLevelByType($outType, $assignedExprType);
$errorBuilder = RuleErrorBuilder::message(sprintf(
'Parameter &$%s @param-out type of %s expects %s, %s given.',
$parameter->getName(),
$functionDescription,
$outType->describe($verbosityLevel),
$assignedExprType->describe($verbosityLevel),
))->identifier(sprintf('paramOut.type'));

return [
$errorBuilder->build(),
];
}

}
90 changes: 90 additions & 0 deletions src/Rules/Variables/ParameterOutTypeCheck.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Variables;

use PhpParser\Node\Expr;
use PHPStan\Analyser\Scope;
use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\Reflection\ExtendedMethodReflection;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Reflection\ParameterReflection;
use PHPStan\Rules\IdentifierRuleError;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Rules\RuleLevelHelper;
use PHPStan\Type\ErrorType;
use PHPStan\Type\Type;
use PHPStan\Type\VerbosityLevel;
use function sprintf;

/**
* Compares what a by-ref parameter is left holding against the type its callers are promised.
*
* The promise is either an explicit `@param-out` or, in its absence, the parameter's own type.
* Which one it is only shows in the error message, so callers report it via $isParamOutType.
*
* @internal
*/
#[AutowiredService]
final class ParameterOutTypeCheck
{

public function __construct(
private RuleLevelHelper $ruleLevelHelper,
)
{
}

/**
* @param Type $outType Already passed through TypeUtils::resolveLateResolvableTypes()
* @return list<IdentifierRuleError>
*/
public function check(
Scope $scope,
FunctionReflection|ExtendedMethodReflection $inFunction,
ParameterReflection $parameter,
Expr $checkedExpr,
Type $outType,
bool $isParamOutType,
): array
{
$typeResult = $this->ruleLevelHelper->findTypeToCheck(
$scope,
$checkedExpr,
'',
static fn (Type $type): bool => $outType->isSuperTypeOf($type)->yes(),

Check warning on line 54 in src/Rules/Variables/ParameterOutTypeCheck.php

View workflow job for this annotation

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

Escaped Mutant for Mutator "PHPStan\Infection\IsSuperTypeOfCalleeAndArgumentMutator": @@ @@ $scope, $checkedExpr, '', - static fn (Type $type): bool => $outType->isSuperTypeOf($type)->yes(), + static fn (Type $type): bool => $type->isSuperTypeOf($outType)->yes(), ); if ($typeResult->getType() instanceof ErrorType) { return $typeResult->getUnknownClassErrors();

Check warning on line 54 in src/Rules/Variables/ParameterOutTypeCheck.php

View workflow job for this annotation

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

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ $scope, $checkedExpr, '', - static fn (Type $type): bool => $outType->isSuperTypeOf($type)->yes(), + static fn (Type $type): bool => !$outType->isSuperTypeOf($type)->no(), ); if ($typeResult->getType() instanceof ErrorType) { return $typeResult->getUnknownClassErrors();
);
if ($typeResult->getType() instanceof ErrorType) {
return $typeResult->getUnknownClassErrors();
}

$assignedExprType = $scope->getType($checkedExpr);
if ($outType->isSuperTypeOf($assignedExprType)->yes()) {
return [];
}

if ($inFunction instanceof ExtendedMethodReflection) {
$functionDescription = sprintf('method %s::%s()', $inFunction->getDeclaringClass()->getDisplayName(), $inFunction->getName());
} else {
$functionDescription = sprintf('function %s()', $inFunction->getName());
}

$verbosityLevel = VerbosityLevel::getRecommendedLevelByType($outType, $assignedExprType);
$errorBuilder = RuleErrorBuilder::message(sprintf(
'Parameter &$%s %s of %s expects %s, %s given.',
$parameter->getName(),
$isParamOutType ? '@param-out type' : 'by-ref type',
$functionDescription,
$outType->describe($verbosityLevel),
$assignedExprType->describe($verbosityLevel),
))->identifier(sprintf('%s.type', $isParamOutType ? 'paramOut' : 'parameterByRef'));

if (!$isParamOutType) {
$errorBuilder->tip('You can change the parameter out type with @param-out PHPDoc tag.');
}

return [
$errorBuilder->build(),
];
}

}
20 changes: 11 additions & 9 deletions tests/PHPStan/Rules/Variables/ParameterOutAssignedTypeRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,17 @@ class ParameterOutAssignedTypeRuleTest extends RuleTestCase
protected function getRule(): TRule
{
return new ParameterOutAssignedTypeRule(
new RuleLevelHelper(
self::createReflectionProvider(),
checkNullables: true,
checkThisOnly: false,
checkUnionTypes: true,
checkExplicitMixed: true,
checkImplicitMixed: false,
checkBenevolentUnionTypes: false,
discoveringSymbolsTip: true,
new ParameterOutTypeCheck(
new RuleLevelHelper(
self::createReflectionProvider(),
checkNullables: true,
checkThisOnly: false,
checkUnionTypes: true,
checkExplicitMixed: true,
checkImplicitMixed: false,
checkBenevolentUnionTypes: false,
discoveringSymbolsTip: true,
),
),
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,17 @@ class ParameterOutExecutionEndTypeRuleTest extends RuleTestCase
protected function getRule(): Rule
{
return new ParameterOutExecutionEndTypeRule(
new RuleLevelHelper(
self::createReflectionProvider(),
checkNullables: true,
checkThisOnly: false,
checkUnionTypes: true,
checkExplicitMixed: true,
checkImplicitMixed: false,
checkBenevolentUnionTypes: false,
discoveringSymbolsTip: true,
new ParameterOutTypeCheck(
new RuleLevelHelper(
self::createReflectionProvider(),
checkNullables: true,
checkThisOnly: false,
checkUnionTypes: true,
checkExplicitMixed: true,
checkImplicitMixed: false,
checkBenevolentUnionTypes: false,
discoveringSymbolsTip: true,
),
),
);
}
Expand Down
Loading