Skip to content

Commit

Permalink
Bleeding edge - check that function-scoped template type is in a para…
Browse files Browse the repository at this point in the history
…meter
  • Loading branch information
ondrejmirtes committed Feb 6, 2021
1 parent f11c0f0 commit 18bdd34
Show file tree
Hide file tree
Showing 11 changed files with 96 additions and 13 deletions.
1 change: 1 addition & 0 deletions conf/bleedingEdge.neon
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ parameters:
detectDuplicateStubFiles: true
checkLogicalAndConstantCondition: true
checkLogicalOrConstantCondition: true
checkMissingTemplateTypeInParameter: true
5 changes: 4 additions & 1 deletion conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ parameters:
detectDuplicateStubFiles: false
checkLogicalAndConstantCondition: false
checkLogicalOrConstantCondition: false
checkMissingTemplateTypeInParameter: false
fileExtensions:
- php
checkAlwaysTrueCheckTypeFunctionCall: false
Expand Down Expand Up @@ -174,7 +175,8 @@ parametersSchema:
dateTimeInstantiation: bool(),
detectDuplicateStubFiles: bool(),
checkLogicalAndConstantCondition: bool(),
checkLogicalOrConstantCondition: bool()
checkLogicalOrConstantCondition: bool(),
checkMissingTemplateTypeInParameter: bool()
])
fileExtensions: listOf(string())
checkAlwaysTrueCheckTypeFunctionCall: bool()
Expand Down Expand Up @@ -699,6 +701,7 @@ services:
arguments:
checkClassCaseSensitivity: %checkClassCaseSensitivity%
checkThisOnly: %checkThisOnly%
checkMissingTemplateTypeInParameter: %featureToggles.checkMissingTemplateTypeInParameter%

-
class: PHPStan\Rules\FunctionReturnTypeCheck
Expand Down
46 changes: 40 additions & 6 deletions src/Rules/FunctionDefinitionCheck.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@
use PHPStan\Reflection\ParametersAcceptorWithPhpDocs;
use PHPStan\Reflection\Php\PhpMethodFromParserNodeReflection;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\Generic\TemplateType;
use PHPStan\Type\NonexistentParentClassType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeTraverser;
use PHPStan\Type\VerbosityLevel;
use PHPStan\Type\VoidType;

Expand All @@ -36,34 +39,40 @@ class FunctionDefinitionCheck

private bool $checkThisOnly;

private bool $checkMissingTemplateTypeInParameter;

public function __construct(
ReflectionProvider $reflectionProvider,
ClassCaseSensitivityCheck $classCaseSensitivityCheck,
PhpVersion $phpVersion,
bool $checkClassCaseSensitivity,
bool $checkThisOnly
bool $checkThisOnly,
bool $checkMissingTemplateTypeInParameter
)
{
$this->reflectionProvider = $reflectionProvider;
$this->classCaseSensitivityCheck = $classCaseSensitivityCheck;
$this->phpVersion = $phpVersion;
$this->checkClassCaseSensitivity = $checkClassCaseSensitivity;
$this->checkThisOnly = $checkThisOnly;
$this->checkMissingTemplateTypeInParameter = $checkMissingTemplateTypeInParameter;
}

/**
* @param \PhpParser\Node\Stmt\Function_ $function
* @param string $parameterMessage
* @param string $returnMessage
* @param string $unionTypesMessage
* @param string $templateTypeMissingInParameterMessage
* @return RuleError[]
*/
public function checkFunction(
Function_ $function,
FunctionReflection $functionReflection,
string $parameterMessage,
string $returnMessage,
string $unionTypesMessage
string $unionTypesMessage,
string $templateTypeMissingInParameterMessage
): array
{
$parametersAcceptor = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants());
Expand All @@ -73,7 +82,8 @@ public function checkFunction(
$function,
$parameterMessage,
$returnMessage,
$unionTypesMessage
$unionTypesMessage,
$templateTypeMissingInParameterMessage
);
}

Expand Down Expand Up @@ -170,14 +180,16 @@ public function checkAnonymousFunction(
* @param string $parameterMessage
* @param string $returnMessage
* @param string $unionTypesMessage
* @param string $templateTypeMissingInParameterMessage
* @return RuleError[]
*/
public function checkClassMethod(
PhpMethodFromParserNodeReflection $methodReflection,
ClassMethod $methodNode,
string $parameterMessage,
string $returnMessage,
string $unionTypesMessage
string $unionTypesMessage,
string $templateTypeMissingInParameterMessage
): array
{
/** @var \PHPStan\Reflection\ParametersAcceptorWithPhpDocs $parametersAcceptor */
Expand All @@ -188,7 +200,8 @@ public function checkClassMethod(
$methodNode,
$parameterMessage,
$returnMessage,
$unionTypesMessage
$unionTypesMessage,
$templateTypeMissingInParameterMessage
);
}

Expand All @@ -198,14 +211,16 @@ public function checkClassMethod(
* @param string $parameterMessage
* @param string $returnMessage
* @param string $unionTypesMessage
* @param string $templateTypeMissingInParameterMessage
* @return RuleError[]
*/
private function checkParametersAcceptor(
ParametersAcceptor $parametersAcceptor,
FunctionLike $functionNode,
string $parameterMessage,
string $returnMessage,
string $unionTypesMessage
string $unionTypesMessage,
string $templateTypeMissingInParameterMessage
): array
{
$errors = [];
Expand Down Expand Up @@ -301,6 +316,25 @@ private function checkParametersAcceptor(
$errors[] = RuleErrorBuilder::message(sprintf($returnMessage, $parametersAcceptor->getReturnType()->describe(VerbosityLevel::typeOnly())))->line($returnTypeNode->getLine())->build();
}

if ($this->checkMissingTemplateTypeInParameter) {
$templateTypeMap = $parametersAcceptor->getTemplateTypeMap();
$templateTypes = $templateTypeMap->getTypes();
foreach ($parametersAcceptor->getParameters() as $parameter) {
TypeTraverser::map($parameter->getType(), static function (Type $type, callable $traverse) use (&$templateTypes): Type {
if ($type instanceof TemplateType) {
unset($templateTypes[$type->getName()]);
return $type;
}

return $traverse($type);
});
}

foreach (array_keys($templateTypes) as $templateTypeName) {
$errors[] = RuleErrorBuilder::message(sprintf($templateTypeMissingInParameterMessage, $templateTypeName))->build();
}
}

return $errors;
}

Expand Down
3 changes: 2 additions & 1 deletion src/Rules/Functions/ExistingClassesInTypehintsRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ public function processNode(Node $node, Scope $scope): array
'Return typehint of function %s() has invalid type %%s.',
$functionName
),
sprintf('Function %s() uses native union types but they\'re supported only on PHP 8.0 and later.', $functionName)
sprintf('Function %s() uses native union types but they\'re supported only on PHP 8.0 and later.', $functionName),
sprintf('Template type %%s of function %s() is not referenced in a parameter.', $functionName)
);
}

Expand Down
3 changes: 2 additions & 1 deletion src/Rules/Methods/ExistingClassesInTypehintsRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ public function processNode(Node $node, Scope $scope): array
$scope->getClassReflection()->getDisplayName(),
$methodReflection->getName()
),
sprintf('Method %s::%s() uses native union types but they\'re supported only on PHP 8.0 and later.', $scope->getClassReflection()->getDisplayName(), $methodReflection->getName())
sprintf('Method %s::%s() uses native union types but they\'re supported only on PHP 8.0 and later.', $scope->getClassReflection()->getDisplayName(), $methodReflection->getName()),
sprintf('Template type %%s of method %s::%s() is not referenced in a parameter.', $scope->getClassReflection()->getDisplayName(), $methodReflection->getName())
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class ExistingClassesInArrowFunctionTypehintsRuleTest extends \PHPStan\Testing\R
protected function getRule(): \PHPStan\Rules\Rule
{
$broker = $this->createReflectionProvider();
return new ExistingClassesInArrowFunctionTypehintsRule(new FunctionDefinitionCheck($broker, new ClassCaseSensitivityCheck($broker), new PhpVersion($this->phpVersionId), true, false));
return new ExistingClassesInArrowFunctionTypehintsRule(new FunctionDefinitionCheck($broker, new ClassCaseSensitivityCheck($broker), new PhpVersion($this->phpVersionId), true, false, true));
}

public function testRule(): void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class ExistingClassesInClosureTypehintsRuleTest extends \PHPStan\Testing\RuleTes
protected function getRule(): \PHPStan\Rules\Rule
{
$broker = $this->createReflectionProvider();
return new ExistingClassesInClosureTypehintsRule(new FunctionDefinitionCheck($broker, new ClassCaseSensitivityCheck($broker), new PhpVersion($this->phpVersionId), true, false));
return new ExistingClassesInClosureTypehintsRule(new FunctionDefinitionCheck($broker, new ClassCaseSensitivityCheck($broker), new PhpVersion($this->phpVersionId), true, false, true));
}

public function testExistingClassInTypehint(): void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class ExistingClassesInTypehintsRuleTest extends \PHPStan\Testing\RuleTestCase
protected function getRule(): \PHPStan\Rules\Rule
{
$broker = $this->createReflectionProvider();
return new ExistingClassesInTypehintsRule(new FunctionDefinitionCheck($broker, new ClassCaseSensitivityCheck($broker), new PhpVersion($this->phpVersionId), true, false));
return new ExistingClassesInTypehintsRule(new FunctionDefinitionCheck($broker, new ClassCaseSensitivityCheck($broker), new PhpVersion($this->phpVersionId), true, false, true));
}

public function testExistingClassInTypehint(): void
Expand Down Expand Up @@ -86,6 +86,10 @@ public function testExistingClassInTypehint(): void
'Parameter $string of function TestFunctionTypehints\genericTemplateClassString() has invalid typehint type TestFunctionTypehints\SomeNonexistentClass.',
87,
],
[
'Template type T of function TestFunctionTypehints\templateTypeMissingInParameter() is not referenced in a parameter.',
96,
],
]);
}

Expand Down
9 changes: 9 additions & 0 deletions tests/PHPStan/Rules/Functions/data/typehints.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,12 @@ function genericTemplateClassString(string $string)
{

}

/**
* @template T
* @param class-string $a
*/
function templateTypeMissingInParameter(string $a)
{

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class ExistingClassesInTypehintsRuleTest extends \PHPStan\Testing\RuleTestCase
protected function getRule(): \PHPStan\Rules\Rule
{
$broker = $this->createReflectionProvider();
return new ExistingClassesInTypehintsRule(new FunctionDefinitionCheck($broker, new ClassCaseSensitivityCheck($broker), new PhpVersion($this->phpVersionId), true, false));
return new ExistingClassesInTypehintsRule(new FunctionDefinitionCheck($broker, new ClassCaseSensitivityCheck($broker), new PhpVersion($this->phpVersionId), true, false, true));
}

public function testExistingClassInTypehint(): void
Expand Down Expand Up @@ -124,6 +124,10 @@ public function testExistingClassInTypehint(): void
'Parameter $cb of method TestMethodTypehints\CallableTypehints::doFoo() has invalid typehint type TestMethodTypehints\Ble.',
113,
],
[
'Template type U of method TestMethodTypehints\TemplateTypeMissingInParameter::doFoo() is not referenced in a parameter.',
130,
],
]);
}

Expand Down
26 changes: 26 additions & 0 deletions tests/PHPStan/Rules/Methods/data/typehints.php
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,29 @@ public function doFoo(callable $cb): void
}

}

/**
* @template T
*/
class TemplateTypeMissingInParameter
{

/**
* @template U of object
* @param class-string $class
*/
public function doFoo(string $class): void
{

}

/**
* @template U of object
* @param class-string<U> $class
*/
public function doBar(string $class): void
{

}

}

0 comments on commit 18bdd34

Please sign in to comment.