Skip to content

Commit

Permalink
Bleeding edge - detect unresolvable return type after generic functio…
Browse files Browse the repository at this point in the history
…n call
  • Loading branch information
ondrejmirtes committed Jun 9, 2021
1 parent 99aae63 commit 9070c5b
Show file tree
Hide file tree
Showing 23 changed files with 170 additions and 7 deletions.
1 change: 1 addition & 0 deletions conf/bleedingEdge.neon
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ parameters:
preciseExceptionTracking: true
apiRules: true
deepInspectTypes: true
neverInGenericReturnType: true
5 changes: 4 additions & 1 deletion conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ parameters:
preciseExceptionTracking: false
apiRules: false
deepInspectTypes: false
neverInGenericReturnType: false
fileExtensions:
- php
checkAlwaysTrueCheckTypeFunctionCall: false
Expand Down Expand Up @@ -213,7 +214,8 @@ parametersSchema:
rememberFunctionValues: bool(),
preciseExceptionTracking: bool(),
apiRules: bool(),
deepInspectTypes: bool()
deepInspectTypes: bool(),
neverInGenericReturnType: bool()
])
fileExtensions: listOf(string())
checkAlwaysTrueCheckTypeFunctionCall: bool()
Expand Down Expand Up @@ -796,6 +798,7 @@ services:
checkArgumentsPassedByReference: %checkArgumentsPassedByReference%
checkExtraArguments: %checkExtraArguments%
checkMissingTypehints: %checkMissingTypehints%
checkNeverInGenericReturnType: %featureToggles.neverInGenericReturnType%

-
class: PHPStan\Rules\FunctionDefinitionCheck
Expand Down
1 change: 1 addition & 0 deletions src/Rules/AttributesCheck.php
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ public function check(
'Unable to resolve the template type %s in instantiation of attribute class ' . $attributeClass->getDisplayName(),
'Missing parameter $%s in call to ' . $attributeClass->getDisplayName() . ' constructor.',
'Unknown parameter $%s in call to ' . $attributeClass->getDisplayName() . ' constructor.',
'Return type of call to ' . $attributeClass->getDisplayName() . ' constructor contains unresolvable type.',
]
);

Expand Down
1 change: 1 addition & 0 deletions src/Rules/Classes/InstantiationRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ private function checkClassName(string $class, bool $isName, Node $node, Scope $
'Unable to resolve the template type %s in instantiation of class ' . $classReflection->getDisplayName(),
'Missing parameter $%s in call to ' . $classReflection->getDisplayName() . ' constructor.',
'Unknown parameter $%s in call to ' . $classReflection->getDisplayName() . ' constructor.',
'Return type of call to ' . $classReflection->getDisplayName() . ' constructor contains unresolvable type.',
]
));
}
Expand Down
21 changes: 19 additions & 2 deletions src/Rules/FunctionCallParametersCheck.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use PHPStan\Php\PhpVersion;
use PHPStan\Reflection\ParametersAcceptor;
use PHPStan\Reflection\ResolvedFunctionVariant;
use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper;
use PHPStan\Type\ErrorType;
use PHPStan\Type\Generic\TemplateType;
use PHPStan\Type\NeverType;
Expand All @@ -26,6 +27,8 @@ class FunctionCallParametersCheck

private PhpVersion $phpVersion;

private UnresolvableTypeHelper $unresolvableTypeHelper;

private bool $checkArgumentTypes;

private bool $checkArgumentsPassedByReference;
Expand All @@ -34,30 +37,36 @@ class FunctionCallParametersCheck

private bool $checkMissingTypehints;

private bool $checkNeverInGenericReturnType;

public function __construct(
RuleLevelHelper $ruleLevelHelper,
NullsafeCheck $nullsafeCheck,
PhpVersion $phpVersion,
UnresolvableTypeHelper $unresolvableTypeHelper,
bool $checkArgumentTypes,
bool $checkArgumentsPassedByReference,
bool $checkExtraArguments,
bool $checkMissingTypehints
bool $checkMissingTypehints,
bool $checkNeverInGenericReturnType
)
{
$this->ruleLevelHelper = $ruleLevelHelper;
$this->nullsafeCheck = $nullsafeCheck;
$this->phpVersion = $phpVersion;
$this->unresolvableTypeHelper = $unresolvableTypeHelper;
$this->checkArgumentTypes = $checkArgumentTypes;
$this->checkArgumentsPassedByReference = $checkArgumentsPassedByReference;
$this->checkExtraArguments = $checkExtraArguments;
$this->checkMissingTypehints = $checkMissingTypehints;
$this->checkNeverInGenericReturnType = $checkNeverInGenericReturnType;
}

/**
* @param \PHPStan\Reflection\ParametersAcceptor $parametersAcceptor
* @param \PHPStan\Analyser\Scope $scope
* @param \PhpParser\Node\Expr\FuncCall|\PhpParser\Node\Expr\MethodCall|\PhpParser\Node\Expr\StaticCall|\PhpParser\Node\Expr\New_ $funcCall
* @param array{string, string, string, string, string, string, string, string, string, string, string, string} $messages
* @param array{string, string, string, string, string, string, string, string, string, string, string, string, string} $messages
* @return RuleError[]
*/
public function check(
Expand Down Expand Up @@ -346,6 +355,14 @@ static function (Type $type): bool {
$errors[] = RuleErrorBuilder::message(sprintf($messages[9], $name))->line($funcCall->getLine())->tip('See: https://phpstan.org/blog/solving-phpstan-error-unable-to-resolve-template-type')->build();
}
}

if (
$this->checkNeverInGenericReturnType
&& !$this->unresolvableTypeHelper->containsUnresolvableType($originalParametersAcceptor->getReturnType())
&& $this->unresolvableTypeHelper->containsUnresolvableType($parametersAcceptor->getReturnType())
) {
$errors[] = RuleErrorBuilder::message($messages[12])->line($funcCall->getLine())->build();
}
}

return $errors;
Expand Down
1 change: 1 addition & 0 deletions src/Rules/Functions/CallCallablesRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ static function (Type $type): bool {
'Unable to resolve the template type %s in call to ' . $callableDescription,
'Missing parameter $%s in call to ' . $callableDescription . '.',
'Unknown parameter $%s in call to ' . $callableDescription . '.',
'Return type of call to ' . $callableDescription . ' contains unresolvable type.',
]
)
);
Expand Down
1 change: 1 addition & 0 deletions src/Rules/Functions/CallToFunctionParametersRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ public function processNode(Node $node, Scope $scope): array
'Unable to resolve the template type %s in call to function ' . $function->getName(),
'Missing parameter $%s in call to function ' . $function->getName() . '.',
'Unknown parameter $%s in call to function ' . $function->getName() . '.',
'Return type of call to function ' . $function->getName() . ' contains unresolvable type.',
]
);
}
Expand Down
1 change: 1 addition & 0 deletions src/Rules/Methods/CallMethodsRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ static function (Type $type) use ($name): bool {
'Unable to resolve the template type %s in call to method ' . $messagesMethodName,
'Missing parameter $%s in call to method ' . $messagesMethodName . '.',
'Unknown parameter $%s in call to method ' . $messagesMethodName . '.',
'Return type of call to method ' . $messagesMethodName . ' contains unresolvable type.',
]
));

Expand Down
1 change: 1 addition & 0 deletions src/Rules/Methods/CallStaticMethodsRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,7 @@ static function (Type $type) use ($methodName): bool {
'Unable to resolve the template type %s in call to method ' . $lowercasedMethodName,
'Missing parameter $%s in call to ' . $lowercasedMethodName . '.',
'Unknown parameter $%s in call to ' . $lowercasedMethodName . '.',
'Return type of call to ' . $lowercasedMethodName . ' contains unresolvable type.',
]
));

Expand Down
3 changes: 3 additions & 0 deletions tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use PHPStan\Rules\ClassCaseSensitivityCheck;
use PHPStan\Rules\FunctionCallParametersCheck;
use PHPStan\Rules\NullsafeCheck;
use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleLevelHelper;
use PHPStan\Testing\RuleTestCase;
Expand All @@ -27,6 +28,8 @@ protected function getRule(): Rule
new RuleLevelHelper($reflectionProvider, true, false, true),
new NullsafeCheck(),
new PhpVersion(80000),
new UnresolvableTypeHelper(true),
true,
true,
true,
true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use PHPStan\Rules\ClassCaseSensitivityCheck;
use PHPStan\Rules\FunctionCallParametersCheck;
use PHPStan\Rules\NullsafeCheck;
use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleLevelHelper;
use PHPStan\Testing\RuleTestCase;
Expand All @@ -27,6 +28,8 @@ protected function getRule(): Rule
new RuleLevelHelper($reflectionProvider, true, false, true),
new NullsafeCheck(),
new PhpVersion(80000),
new UnresolvableTypeHelper(true),
true,
true,
true,
true,
Expand Down
3 changes: 2 additions & 1 deletion tests/PHPStan/Rules/Classes/InstantiationRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use PHPStan\Rules\ClassCaseSensitivityCheck;
use PHPStan\Rules\FunctionCallParametersCheck;
use PHPStan\Rules\NullsafeCheck;
use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper;
use PHPStan\Rules\RuleLevelHelper;

/**
Expand All @@ -19,7 +20,7 @@ protected function getRule(): \PHPStan\Rules\Rule
$broker = $this->createReflectionProvider();
return new InstantiationRule(
$broker,
new FunctionCallParametersCheck(new RuleLevelHelper($broker, true, false, true, false), new NullsafeCheck(), new PhpVersion(80000), true, true, true, true),
new FunctionCallParametersCheck(new RuleLevelHelper($broker, true, false, true, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(true), true, true, true, true, true),
new ClassCaseSensitivityCheck($broker)
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use PHPStan\Rules\ClassCaseSensitivityCheck;
use PHPStan\Rules\FunctionCallParametersCheck;
use PHPStan\Rules\NullsafeCheck;
use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleLevelHelper;
use PHPStan\Testing\RuleTestCase;
Expand All @@ -27,6 +28,8 @@ protected function getRule(): Rule
new RuleLevelHelper($reflectionProvider, true, false, true),
new NullsafeCheck(),
new PhpVersion(80000),
new UnresolvableTypeHelper(true),
true,
true,
true,
true,
Expand Down
3 changes: 3 additions & 0 deletions tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use PHPStan\Php\PhpVersion;
use PHPStan\Rules\FunctionCallParametersCheck;
use PHPStan\Rules\NullsafeCheck;
use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper;
use PHPStan\Rules\RuleLevelHelper;

/**
Expand All @@ -21,6 +22,8 @@ protected function getRule(): \PHPStan\Rules\Rule
$ruleLevelHelper,
new NullsafeCheck(),
new PhpVersion(80000),
new UnresolvableTypeHelper(true),
true,
true,
true,
true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use PHPStan\Php\PhpVersion;
use PHPStan\Rules\FunctionCallParametersCheck;
use PHPStan\Rules\NullsafeCheck;
use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper;
use PHPStan\Rules\RuleLevelHelper;

/**
Expand All @@ -18,7 +19,7 @@ protected function getRule(): \PHPStan\Rules\Rule
$broker = $this->createReflectionProvider();
return new CallToFunctionParametersRule(
$broker,
new FunctionCallParametersCheck(new RuleLevelHelper($broker, true, false, true, false), new NullsafeCheck(), new PhpVersion(80000), true, true, true, true)
new FunctionCallParametersCheck(new RuleLevelHelper($broker, true, false, true, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(true), true, true, true, true, true)
);
}

Expand Down
3 changes: 3 additions & 0 deletions tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use PHPStan\Rules\ClassCaseSensitivityCheck;
use PHPStan\Rules\FunctionCallParametersCheck;
use PHPStan\Rules\NullsafeCheck;
use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleLevelHelper;
use PHPStan\Testing\RuleTestCase;
Expand All @@ -27,6 +28,8 @@ protected function getRule(): Rule
new RuleLevelHelper($reflectionProvider, true, false, true),
new NullsafeCheck(),
new PhpVersion(80000),
new UnresolvableTypeHelper(true),
true,
true,
true,
true,
Expand Down
3 changes: 3 additions & 0 deletions tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use PHPStan\Rules\ClassCaseSensitivityCheck;
use PHPStan\Rules\FunctionCallParametersCheck;
use PHPStan\Rules\NullsafeCheck;
use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleLevelHelper;
use PHPStan\Testing\RuleTestCase;
Expand All @@ -27,6 +28,8 @@ protected function getRule(): Rule
new RuleLevelHelper($reflectionProvider, true, false, true),
new NullsafeCheck(),
new PhpVersion(80000),
new UnresolvableTypeHelper(true),
true,
true,
true,
true,
Expand Down
3 changes: 3 additions & 0 deletions tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use PHPStan\Rules\ClassCaseSensitivityCheck;
use PHPStan\Rules\FunctionCallParametersCheck;
use PHPStan\Rules\NullsafeCheck;
use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleLevelHelper;
use PHPStan\Testing\RuleTestCase;
Expand All @@ -27,6 +28,8 @@ protected function getRule(): Rule
new RuleLevelHelper($reflectionProvider, true, false, true),
new NullsafeCheck(),
new PhpVersion(80000),
new UnresolvableTypeHelper(true),
true,
true,
true,
true,
Expand Down
32 changes: 31 additions & 1 deletion tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use PHPStan\Php\PhpVersion;
use PHPStan\Rules\FunctionCallParametersCheck;
use PHPStan\Rules\NullsafeCheck;
use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleLevelHelper;
use const PHP_VERSION_ID;
Expand All @@ -30,13 +31,16 @@ class CallMethodsRuleTest extends \PHPStan\Testing\RuleTestCase
/** @var int */
private $phpVersion = PHP_VERSION_ID;

/** @var bool */
private $checkNeverInGenericReturnType = false;

protected function getRule(): Rule
{
$broker = $this->createReflectionProvider();
$ruleLevelHelper = new RuleLevelHelper($broker, $this->checkNullables, $this->checkThisOnly, $this->checkUnionTypes, $this->checkExplicitMixed);
return new CallMethodsRule(
$broker,
new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new PhpVersion($this->phpVersion), true, true, true, true),
new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new PhpVersion($this->phpVersion), new UnresolvableTypeHelper(true), true, true, true, true, $this->checkNeverInGenericReturnType),
$ruleLevelHelper,
true,
true
Expand Down Expand Up @@ -1921,4 +1925,30 @@ public function testBug4800(): void
]);
}

public function testGenericReturnTypeResolvedToNever(): void
{
$this->checkThisOnly = false;
$this->checkNullables = true;
$this->checkUnionTypes = true;
$this->checkNeverInGenericReturnType = true;
$this->analyse([__DIR__ . '/data/generic-return-type-never.php'], [
[
'Return type of call to method GenericReturnTypeNever\Foo::doBar() contains unresolvable type.',
70,
],
[
'Return type of call to method GenericReturnTypeNever\Foo::doBazBaz() contains unresolvable type.',
73,
],
]);
}

public function testDoNotReportGenericReturnTypeResolvedToNever(): void
{
$this->checkThisOnly = false;
$this->checkNullables = true;
$this->checkUnionTypes = true;
$this->analyse([__DIR__ . '/data/generic-return-type-never.php'], []);
}

}
3 changes: 2 additions & 1 deletion tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use PHPStan\Rules\ClassCaseSensitivityCheck;
use PHPStan\Rules\FunctionCallParametersCheck;
use PHPStan\Rules\NullsafeCheck;
use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper;
use PHPStan\Rules\RuleLevelHelper;

/**
Expand All @@ -23,7 +24,7 @@ protected function getRule(): \PHPStan\Rules\Rule
$ruleLevelHelper = new RuleLevelHelper($broker, true, $this->checkThisOnly, true, false);
return new CallStaticMethodsRule(
$broker,
new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new PhpVersion(80000), true, true, true, true),
new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(true), true, true, true, true, true),
$ruleLevelHelper,
new ClassCaseSensitivityCheck($broker),
true,
Expand Down
3 changes: 3 additions & 0 deletions tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use PHPStan\Rules\ClassCaseSensitivityCheck;
use PHPStan\Rules\FunctionCallParametersCheck;
use PHPStan\Rules\NullsafeCheck;
use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleLevelHelper;
use PHPStan\Testing\RuleTestCase;
Expand All @@ -27,6 +28,8 @@ protected function getRule(): Rule
new RuleLevelHelper($reflectionProvider, true, false, true),
new NullsafeCheck(),
new PhpVersion(80000),
new UnresolvableTypeHelper(true),
true,
true,
true,
true,
Expand Down
Loading

0 comments on commit 9070c5b

Please sign in to comment.