Skip to content

Commit

Permalink
Report missing types in SetPropertyHookParameterRule - level 6
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Dec 28, 2024
1 parent acd559e commit 70572a1
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 5 deletions.
1 change: 1 addition & 0 deletions conf/config.level0.neon
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ services:
class: PHPStan\Rules\Properties\SetPropertyHookParameterRule
arguments:
checkPhpDocMethodSignatures: %checkPhpDocMethodSignatures%
checkMissingTypehints: %checkMissingTypehints%
tags:
- phpstan.rules.rule

Expand Down
58 changes: 55 additions & 3 deletions src/Rules/Properties/SetPropertyHookParameterRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Node\InPropertyHookNode;
use PHPStan\Rules\MissingTypehintCheck;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\ShouldNotHappenException;
Expand All @@ -18,7 +19,11 @@
final class SetPropertyHookParameterRule implements Rule
{

public function __construct(private bool $checkPhpDocMethodSignatures)
public function __construct(
private MissingTypehintCheck $missingTypehintCheck,
private bool $checkPhpDocMethodSignatures,
private bool $checkMissingTypehints,
)
{
}

Expand Down Expand Up @@ -87,10 +92,12 @@ public function processNode(Node $node, Scope $scope): array
return $errors;
}

if (!$parameter->getType()->isSuperTypeOf($propertyReflection->getReadableType())->yes()) {
$parameterType = $parameter->getType();

if (!$parameterType->isSuperTypeOf($propertyReflection->getReadableType())->yes()) {
$errors[] = RuleErrorBuilder::message(sprintf(
'Type %s of set hook parameter $%s is not contravariant with type %s of property %s::$%s.',
$parameter->getType()->describe(VerbosityLevel::value()),
$parameterType->describe(VerbosityLevel::value()),
$parameter->getName(),
$propertyReflection->getReadableType()->describe(VerbosityLevel::value()),
$classReflection->getDisplayName(),
Expand All @@ -99,6 +106,51 @@ public function processNode(Node $node, Scope $scope): array
->build();
}

if (!$this->checkMissingTypehints) {
return $errors;
}

if ($parameter->getNativeType()->equals($propertyReflection->getReadableType())) {
return $errors;
}

foreach ($this->missingTypehintCheck->getIterableTypesWithMissingValueTypehint($parameterType) as $iterableType) {
$iterableTypeDescription = $iterableType->describe(VerbosityLevel::typeOnly());
$errors[] = RuleErrorBuilder::message(sprintf(
'Set hook for property %s::$%s has parameter $%s with no value type specified in iterable type %s.',
$classReflection->getDisplayName(),
$hookReflection->getHookedPropertyName(),
$parameter->getName(),
$iterableTypeDescription,
))
->tip(MissingTypehintCheck::MISSING_ITERABLE_VALUE_TYPE_TIP)
->identifier('missingType.iterableValue')
->build();
}

foreach ($this->missingTypehintCheck->getNonGenericObjectTypesWithGenericClass($parameterType) as [$name, $genericTypeNames]) {
$errors[] = RuleErrorBuilder::message(sprintf(
'Set hook for property %s::$%s has parameter $%s with generic %s but does not specify its types: %s',
$classReflection->getDisplayName(),
$hookReflection->getHookedPropertyName(),
$parameter->getName(),
$name,
$genericTypeNames,
))
->identifier('missingType.generics')
->build();
}

foreach ($this->missingTypehintCheck->getCallablesWithMissingSignature($parameterType) as $callableType) {
$errors[] = RuleErrorBuilder::message(sprintf(
'Set hook for property %s::$%s has parameter $%s with no signature specified for %s.',
$classReflection->getDisplayName(),
$hookReflection->getHookedPropertyName(),
$parameter->getName(),
$callableType->describe(VerbosityLevel::typeOnly()),
))->identifier('missingType.callable')->build();
}

return $errors;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace PHPStan\Rules\Properties;

use PHPStan\Rules\MissingTypehintCheck;
use PHPStan\Rules\Rule as TRule;
use PHPStan\Testing\RuleTestCase;
use const PHP_VERSION_ID;
Expand All @@ -14,7 +15,7 @@ class SetPropertyHookParameterRuleTest extends RuleTestCase

protected function getRule(): TRule
{
return new SetPropertyHookParameterRule(true);
return new SetPropertyHookParameterRule(new MissingTypehintCheck(true, []), true, true);
}

public function testRule(): void
Expand Down Expand Up @@ -48,6 +49,19 @@ public function testRule(): void
'Type array<string>|int<1, max> of set hook parameter $v is not contravariant with type int of property SetPropertyHookParameter\Bar::$f.',
73,
],
[
'Set hook for property SetPropertyHookParameter\MissingTypes::$f has parameter $v with no value type specified in iterable type array.',
123,
'See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type',
],
[
'Set hook for property SetPropertyHookParameter\MissingTypes::$g has parameter $value with generic class SetPropertyHookParameter\GenericFoo but does not specify its types: T',
129,
],
[
'Set hook for property SetPropertyHookParameter\MissingTypes::$h has parameter $value with no signature specified for callable.',
135,
],
]);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class Foo

/** @var positive-int */
public int $ok3 {
/** @param positive-int|array<string> */
/** @param positive-int|array<string> $v */
set (int|array $v) {

}
Expand Down Expand Up @@ -76,3 +76,65 @@ class Bar
}

}

/**
* @template T
*/
class GenericFoo
{

}

class MissingTypes
{

public array $a {
set { // do not report, taken care of above the property
}
}

/** @var array<string> */
public array $b {
set { // do not report, inherited from property
}
}

public array $c {
set (array $v) { // do not report, taken care of above the property

}
}

/** @var array<string> */
public array $d {
set (array $v) { // do not report, inherited from property

}
}

public int $e {
/** @param array<string> $v */
set (int|array $v) { // do not report, type specified

}
}

public int $f {
set (int|array $v) { // report

}
}

public int $g {
set (int|GenericFoo $value) { // report

}
}

public int $h {
set (int|callable $value) { // report

}
}

}

0 comments on commit 70572a1

Please sign in to comment.