Skip to content

Commit e1f64f2

Browse files
authored
Ignore non-explicit NeverType in purity check
1 parent 14d0951 commit e1f64f2

File tree

4 files changed

+78
-4
lines changed

4 files changed

+78
-4
lines changed

src/Analyser/NodeScopeResolver.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4330,7 +4330,13 @@ public function createCallableParameters(Scope $scope, Expr $closureExpr, ?array
43304330
{
43314331
$callableParameters = null;
43324332
if ($args !== null) {
4333-
$acceptors = $scope->getType($closureExpr)->getCallableParametersAcceptors($scope);
4333+
$closureType = $scope->getType($closureExpr);
4334+
4335+
if ($closureType->isCallable()->no()) {
4336+
return null;
4337+
}
4338+
4339+
$acceptors = $closureType->getCallableParametersAcceptors($scope);
43344340
if (count($acceptors) === 1) {
43354341
$callableParameters = $acceptors[0]->getParameters();
43364342

@@ -4356,6 +4362,10 @@ public function createCallableParameters(Scope $scope, Expr $closureExpr, ?array
43564362
$passedToType->getTypes(),
43574363
static fn (Type $type) => $type->isCallable()->yes(),
43584364
));
4365+
4366+
if ($passedToType->isCallable()->no()) {
4367+
return null;
4368+
}
43594369
}
43604370

43614371
$acceptors = $passedToType->getCallableParametersAcceptors($scope);

src/Type/NeverType.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
use PHPStan\Reflection\ConstantReflection;
1010
use PHPStan\Reflection\ExtendedMethodReflection;
1111
use PHPStan\Reflection\PropertyReflection;
12-
use PHPStan\Reflection\TrivialParametersAcceptor;
1312
use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection;
1413
use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection;
1514
use PHPStan\ShouldNotHappenException;
@@ -334,12 +333,12 @@ public function shuffleArray(): Type
334333

335334
public function isCallable(): TrinaryLogic
336335
{
337-
return TrinaryLogic::createYes();
336+
return TrinaryLogic::createNo();
338337
}
339338

340339
public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array
341340
{
342-
return [new TrivialParametersAcceptor()];
341+
throw new ShouldNotHappenException();
343342
}
344343

345344
public function isCloneable(): TrinaryLogic

tests/PHPStan/Rules/Pure/PureMethodRuleTest.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,21 @@
1212
class PureMethodRuleTest extends RuleTestCase
1313
{
1414

15+
private bool $treatPhpDocTypesAsCertain;
16+
1517
public function getRule(): Rule
1618
{
1719
return new PureMethodRule(new FunctionPurityCheck());
1820
}
1921

22+
protected function shouldTreatPhpDocTypesAsCertain(): bool
23+
{
24+
return $this->treatPhpDocTypesAsCertain;
25+
}
26+
2027
public function testRule(): void
2128
{
29+
$this->treatPhpDocTypesAsCertain = true;
2230
$this->analyse([__DIR__ . '/data/pure-method.php'], [
2331
[
2432
'Method PureMethod\Foo::doFoo() is marked as pure but parameter $p is passed by reference.',
@@ -141,6 +149,7 @@ public function testPureConstructor(): void
141149
$this->markTestSkipped('Test requires PHP 8.0.');
142150
}
143151

152+
$this->treatPhpDocTypesAsCertain = true;
144153
$this->analyse([__DIR__ . '/data/pure-constructor.php'], [
145154
[
146155
'Impure property assignment in pure method PureConstructor\Foo::__construct().',
@@ -159,6 +168,7 @@ public function testPureConstructor(): void
159168

160169
public function testImpureAssignRef(): void
161170
{
171+
$this->treatPhpDocTypesAsCertain = true;
162172
$this->analyse([__DIR__ . '/data/impure-assign-ref.php'], [
163173
[
164174
'Possibly impure property assignment by reference in pure method ImpureAssignRef\HelloWorld::bar6().',
@@ -167,4 +177,21 @@ public function testImpureAssignRef(): void
167177
]);
168178
}
169179

180+
/**
181+
* @dataProvider dataBug11207
182+
*/
183+
public function testBug11207(bool $treatPhpDocTypesAsCertain): void
184+
{
185+
$this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain;
186+
$this->analyse([__DIR__ . '/data/bug-11207.php'], []);
187+
}
188+
189+
public function dataBug11207(): array
190+
{
191+
return [
192+
[true],
193+
[false],
194+
];
195+
}
196+
170197
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php // lint >= 8.0
2+
3+
namespace Bug11207;
4+
5+
final class FilterData
6+
{
7+
/**
8+
* @phpstan-pure
9+
*/
10+
private function __construct(
11+
public ?int $type,
12+
public bool $hasValue,
13+
public mixed $value = null
14+
) {
15+
}
16+
17+
/**
18+
* @param array{type?: int|numeric-string|null, value?: mixed} $data
19+
* @phpstan-pure
20+
*/
21+
public static function fromArray(array $data): self
22+
{
23+
if (isset($data['type'])) {
24+
if (!\is_int($data['type']) && (!\is_string($data['type']) || !is_numeric($data['type']))) {
25+
throw new \InvalidArgumentException(sprintf(
26+
'The "type" parameter MUST be of type "integer" or "null", "%s" given.',
27+
\gettype($data['type'])
28+
));
29+
}
30+
31+
$type = (int) $data['type'];
32+
} else {
33+
$type = null;
34+
}
35+
36+
return new self($type, \array_key_exists('value', $data), $data['value'] ?? null);
37+
}
38+
}

0 commit comments

Comments
 (0)