Skip to content

Commit 70572a1

Browse files
committed
Report missing types in SetPropertyHookParameterRule - level 6
1 parent acd559e commit 70572a1

File tree

4 files changed

+134
-5
lines changed

4 files changed

+134
-5
lines changed

conf/config.level0.neon

+1
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ services:
216216
class: PHPStan\Rules\Properties\SetPropertyHookParameterRule
217217
arguments:
218218
checkPhpDocMethodSignatures: %checkPhpDocMethodSignatures%
219+
checkMissingTypehints: %checkMissingTypehints%
219220
tags:
220221
- phpstan.rules.rule
221222

src/Rules/Properties/SetPropertyHookParameterRule.php

+55-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use PhpParser\Node;
66
use PHPStan\Analyser\Scope;
77
use PHPStan\Node\InPropertyHookNode;
8+
use PHPStan\Rules\MissingTypehintCheck;
89
use PHPStan\Rules\Rule;
910
use PHPStan\Rules\RuleErrorBuilder;
1011
use PHPStan\ShouldNotHappenException;
@@ -18,7 +19,11 @@
1819
final class SetPropertyHookParameterRule implements Rule
1920
{
2021

21-
public function __construct(private bool $checkPhpDocMethodSignatures)
22+
public function __construct(
23+
private MissingTypehintCheck $missingTypehintCheck,
24+
private bool $checkPhpDocMethodSignatures,
25+
private bool $checkMissingTypehints,
26+
)
2227
{
2328
}
2429

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

90-
if (!$parameter->getType()->isSuperTypeOf($propertyReflection->getReadableType())->yes()) {
95+
$parameterType = $parameter->getType();
96+
97+
if (!$parameterType->isSuperTypeOf($propertyReflection->getReadableType())->yes()) {
9198
$errors[] = RuleErrorBuilder::message(sprintf(
9299
'Type %s of set hook parameter $%s is not contravariant with type %s of property %s::$%s.',
93-
$parameter->getType()->describe(VerbosityLevel::value()),
100+
$parameterType->describe(VerbosityLevel::value()),
94101
$parameter->getName(),
95102
$propertyReflection->getReadableType()->describe(VerbosityLevel::value()),
96103
$classReflection->getDisplayName(),
@@ -99,6 +106,51 @@ public function processNode(Node $node, Scope $scope): array
99106
->build();
100107
}
101108

109+
if (!$this->checkMissingTypehints) {
110+
return $errors;
111+
}
112+
113+
if ($parameter->getNativeType()->equals($propertyReflection->getReadableType())) {
114+
return $errors;
115+
}
116+
117+
foreach ($this->missingTypehintCheck->getIterableTypesWithMissingValueTypehint($parameterType) as $iterableType) {
118+
$iterableTypeDescription = $iterableType->describe(VerbosityLevel::typeOnly());
119+
$errors[] = RuleErrorBuilder::message(sprintf(
120+
'Set hook for property %s::$%s has parameter $%s with no value type specified in iterable type %s.',
121+
$classReflection->getDisplayName(),
122+
$hookReflection->getHookedPropertyName(),
123+
$parameter->getName(),
124+
$iterableTypeDescription,
125+
))
126+
->tip(MissingTypehintCheck::MISSING_ITERABLE_VALUE_TYPE_TIP)
127+
->identifier('missingType.iterableValue')
128+
->build();
129+
}
130+
131+
foreach ($this->missingTypehintCheck->getNonGenericObjectTypesWithGenericClass($parameterType) as [$name, $genericTypeNames]) {
132+
$errors[] = RuleErrorBuilder::message(sprintf(
133+
'Set hook for property %s::$%s has parameter $%s with generic %s but does not specify its types: %s',
134+
$classReflection->getDisplayName(),
135+
$hookReflection->getHookedPropertyName(),
136+
$parameter->getName(),
137+
$name,
138+
$genericTypeNames,
139+
))
140+
->identifier('missingType.generics')
141+
->build();
142+
}
143+
144+
foreach ($this->missingTypehintCheck->getCallablesWithMissingSignature($parameterType) as $callableType) {
145+
$errors[] = RuleErrorBuilder::message(sprintf(
146+
'Set hook for property %s::$%s has parameter $%s with no signature specified for %s.',
147+
$classReflection->getDisplayName(),
148+
$hookReflection->getHookedPropertyName(),
149+
$parameter->getName(),
150+
$callableType->describe(VerbosityLevel::typeOnly()),
151+
))->identifier('missingType.callable')->build();
152+
}
153+
102154
return $errors;
103155
}
104156

tests/PHPStan/Rules/Properties/SetPropertyHookParameterRuleTest.php

+15-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace PHPStan\Rules\Properties;
44

5+
use PHPStan\Rules\MissingTypehintCheck;
56
use PHPStan\Rules\Rule as TRule;
67
use PHPStan\Testing\RuleTestCase;
78
use const PHP_VERSION_ID;
@@ -14,7 +15,7 @@ class SetPropertyHookParameterRuleTest extends RuleTestCase
1415

1516
protected function getRule(): TRule
1617
{
17-
return new SetPropertyHookParameterRule(true);
18+
return new SetPropertyHookParameterRule(new MissingTypehintCheck(true, []), true, true);
1819
}
1920

2021
public function testRule(): void
@@ -48,6 +49,19 @@ public function testRule(): void
4849
'Type array<string>|int<1, max> of set hook parameter $v is not contravariant with type int of property SetPropertyHookParameter\Bar::$f.',
4950
73,
5051
],
52+
[
53+
'Set hook for property SetPropertyHookParameter\MissingTypes::$f has parameter $v with no value type specified in iterable type array.',
54+
123,
55+
'See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type',
56+
],
57+
[
58+
'Set hook for property SetPropertyHookParameter\MissingTypes::$g has parameter $value with generic class SetPropertyHookParameter\GenericFoo but does not specify its types: T',
59+
129,
60+
],
61+
[
62+
'Set hook for property SetPropertyHookParameter\MissingTypes::$h has parameter $value with no signature specified for callable.',
63+
135,
64+
],
5165
]);
5266
}
5367

tests/PHPStan/Rules/Properties/data/set-property-hook-parameter.php

+63-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class Foo
2020

2121
/** @var positive-int */
2222
public int $ok3 {
23-
/** @param positive-int|array<string> */
23+
/** @param positive-int|array<string> $v */
2424
set (int|array $v) {
2525

2626
}
@@ -76,3 +76,65 @@ class Bar
7676
}
7777

7878
}
79+
80+
/**
81+
* @template T
82+
*/
83+
class GenericFoo
84+
{
85+
86+
}
87+
88+
class MissingTypes
89+
{
90+
91+
public array $a {
92+
set { // do not report, taken care of above the property
93+
}
94+
}
95+
96+
/** @var array<string> */
97+
public array $b {
98+
set { // do not report, inherited from property
99+
}
100+
}
101+
102+
public array $c {
103+
set (array $v) { // do not report, taken care of above the property
104+
105+
}
106+
}
107+
108+
/** @var array<string> */
109+
public array $d {
110+
set (array $v) { // do not report, inherited from property
111+
112+
}
113+
}
114+
115+
public int $e {
116+
/** @param array<string> $v */
117+
set (int|array $v) { // do not report, type specified
118+
119+
}
120+
}
121+
122+
public int $f {
123+
set (int|array $v) { // report
124+
125+
}
126+
}
127+
128+
public int $g {
129+
set (int|GenericFoo $value) { // report
130+
131+
}
132+
}
133+
134+
public int $h {
135+
set (int|callable $value) { // report
136+
137+
}
138+
}
139+
140+
}

0 commit comments

Comments
 (0)