Skip to content

Commit 22eef6d

Browse files
committed
Bleeding edge - consider implicit throw points when the only explicit one is Throw_
1 parent 1fcae1c commit 22eef6d

10 files changed

+80
-7
lines changed

conf/bleedingEdge.neon

+1
Original file line numberDiff line numberDiff line change
@@ -60,5 +60,6 @@ parameters:
6060
validatePregQuote: true
6161
noImplicitWildcard: true
6262
tooWidePropertyType: true
63+
explicitThrow: true
6364
stubFiles:
6465
- ../stubs/bleedingEdge/Rule.stub

conf/config.neon

+2
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ parameters:
9696
noImplicitWildcard: false
9797
narrowPregMatches: true
9898
tooWidePropertyType: false
99+
explicitThrow: false
99100
fileExtensions:
100101
- php
101102
checkAdvancedIsset: false
@@ -538,6 +539,7 @@ services:
538539
universalObjectCratesClasses: %universalObjectCratesClasses%
539540
paramOutType: %featureToggles.paramOutType%
540541
preciseMissingReturn: %featureToggles.preciseMissingReturn%
542+
explicitThrow: %featureToggles.explicitThrow%
541543

542544
-
543545
class: PHPStan\Analyser\ConstantResolver

conf/parametersSchema.neon

+1
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ parametersSchema:
9191
noImplicitWildcard: bool()
9292
narrowPregMatches: bool()
9393
tooWidePropertyType: bool()
94+
explicitThrow: bool()
9495
])
9596
fileExtensions: listOf(string())
9697
checkAdvancedIsset: bool()

src/Analyser/NodeScopeResolver.php

+14-1
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ public function __construct(
262262
private readonly bool $detectDeadTypeInMultiCatch,
263263
private readonly bool $paramOutType,
264264
private readonly bool $preciseMissingReturn,
265+
private readonly bool $explicitThrow,
265266
)
266267
{
267268
$earlyTerminatingMethodNames = [];
@@ -1545,6 +1546,7 @@ private function processStmtNode(
15451546
}
15461547

15471548
// explicit only
1549+
$onlyExplicitIsThrow = true;
15481550
if (count($matchingThrowPoints) === 0) {
15491551
foreach ($throwPoints as $throwPointIndex => $throwPoint) {
15501552
foreach ($catchTypes as $catchTypeIndex => $catchTypeItem) {
@@ -1556,13 +1558,24 @@ private function processStmtNode(
15561558
if (!$throwPoint->isExplicit()) {
15571559
continue;
15581560
}
1561+
$throwNode = $throwPoint->getNode();
1562+
if (
1563+
!$throwNode instanceof Throw_
1564+
&& !$throwNode instanceof Expr\Throw_
1565+
&& !($throwNode instanceof Node\Stmt\Expression && $throwNode->expr instanceof Expr\Throw_)
1566+
) {
1567+
$onlyExplicitIsThrow = false;
1568+
}
15591569
$matchingThrowPoints[$throwPointIndex] = $throwPoint;
15601570
}
15611571
}
15621572
}
15631573

15641574
// implicit only
1565-
if (count($matchingThrowPoints) === 0) {
1575+
if (
1576+
count($matchingThrowPoints) === 0
1577+
|| ($this->explicitThrow && $onlyExplicitIsThrow)
1578+
) {
15661579
foreach ($throwPoints as $throwPointIndex => $throwPoint) {
15671580
if ($throwPoint->isExplicit()) {
15681581
continue;

src/Testing/RuleTestCase.php

+1
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ private function getAnalyser(DirectRuleRegistry $ruleRegistry): Analyser
107107
self::getContainer()->getParameter('featureToggles')['detectDeadTypeInMultiCatch'],
108108
self::getContainer()->getParameter('featureToggles')['paramOutType'],
109109
self::getContainer()->getParameter('featureToggles')['preciseMissingReturn'],
110+
self::getContainer()->getParameter('featureToggles')['explicitThrow'],
110111
);
111112
$fileAnalyser = new FileAnalyser(
112113
$this->createScopeFactory($reflectionProvider, $typeSpecifier),

src/Testing/TypeInferenceTestCase.php

+1
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ public static function processFile(
8787
self::getContainer()->getParameter('featureToggles')['detectDeadTypeInMultiCatch'],
8888
self::getContainer()->getParameter('featureToggles')['paramOutType'],
8989
self::getContainer()->getParameter('featureToggles')['preciseMissingReturn'],
90+
self::getContainer()->getParameter('featureToggles')['explicitThrow'],
9091
);
9192
$resolver->setAnalysedFiles(array_map(static fn (string $file): string => $fileHelper->normalizePath($file), array_merge([$file], static::getAdditionalAnalysedFiles())));
9293

tests/PHPStan/Analyser/AnalyserTest.php

+1
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,7 @@ private function createAnalyser(bool $enableIgnoreErrorsWithinPhpDocs): Analyser
743743
self::getContainer()->getParameter('featureToggles')['detectDeadTypeInMultiCatch'],
744744
self::getContainer()->getParameter('featureToggles')['paramOutType'],
745745
self::getContainer()->getParameter('featureToggles')['preciseMissingReturn'],
746+
self::getContainer()->getParameter('featureToggles')['explicitThrow'],
746747
);
747748
$lexer = new Lexer(['usedAttributes' => ['comments', 'startLine', 'endLine', 'startTokenPos', 'endTokenPos']]);
748749
$fileAnalyser = new FileAnalyser(

tests/PHPStan/Analyser/nsrt/bug-4879.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public function sayHello2(bool $bool1): void
3333

3434
$this->test();
3535
} catch (\Exception $ex) {
36-
assertVariableCertainty(TrinaryLogic::createNo(), $var);
36+
assertVariableCertainty(TrinaryLogic::createMaybe(), $var);
3737
}
3838
}
3939

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
namespace ExplicitThrows;
4+
5+
use PHPStan\TrinaryLogic;
6+
use function PHPStan\Testing\assertVariableCertainty;
7+
8+
class Foo
9+
{
10+
11+
public function doFoo(): void
12+
{
13+
try {
14+
doFoo();
15+
$a = 1;
16+
throw new \InvalidArgumentException();
17+
} catch (\InvalidArgumentException $e) {
18+
assertVariableCertainty(TrinaryLogic::createMaybe(), $a);
19+
}
20+
}
21+
22+
public function doBar(): void
23+
{
24+
try {
25+
doFoo();
26+
$a = 1;
27+
$this->throwInvalidArgument();
28+
} catch (\InvalidArgumentException $e) {
29+
assertVariableCertainty(TrinaryLogic::createYes(), $a);
30+
}
31+
}
32+
33+
public function doBaz(): void
34+
{
35+
try {
36+
doFoo();
37+
$a = 1;
38+
$this->throwInvalidArgument();
39+
throw new \InvalidArgumentException();
40+
} catch (\InvalidArgumentException $e) {
41+
assertVariableCertainty(TrinaryLogic::createYes(), $a);
42+
}
43+
}
44+
45+
/**
46+
* @throws \InvalidArgumentException
47+
*/
48+
private function throwInvalidArgument(): void
49+
{
50+
51+
}
52+
53+
}

tests/PHPStan/Analyser/nsrt/throw-points/try-catch.php

+5-5
Original file line numberDiff line numberDiff line change
@@ -65,15 +65,15 @@ function (): void {
6565
$bar = 1;
6666
maybeThrows();
6767
} catch (\InvalidArgumentException $e) {
68-
assertVariableCertainty(TrinaryLogic::createYes(), $foo);
68+
assertVariableCertainty(TrinaryLogic::createMaybe(), $foo);
6969
assertType('1|2', $foo);
7070

71-
assertVariableCertainty(TrinaryLogic::createNo(), $bar);
71+
assertVariableCertainty(TrinaryLogic::createMaybe(), $bar);
7272
assertVariableCertainty(TrinaryLogic::createNo(), $baz);
7373
} catch (\RuntimeException $e) {
7474
assertVariableCertainty(TrinaryLogic::createNo(), $foo);
75-
assertVariableCertainty(TrinaryLogic::createNo(), $bar);
76-
assertVariableCertainty(TrinaryLogic::createYes(), $baz);
75+
assertVariableCertainty(TrinaryLogic::createMaybe(), $bar);
76+
assertVariableCertainty(TrinaryLogic::createMaybe(), $baz);
7777
assertType('1|2', $baz);
7878
} catch (\Throwable $e) {
7979
assertType('Throwable~InvalidArgumentException|RuntimeException', $e);
@@ -99,7 +99,7 @@ function (): void {
9999
throw new \InvalidArgumentException();
100100
} catch (\InvalidArgumentException $e) {
101101
assertType('1', $foo);
102-
assertVariableCertainty(TrinaryLogic::createYes(), $foo);
102+
assertVariableCertainty(TrinaryLogic::createMaybe(), $foo);
103103
}
104104
};
105105

0 commit comments

Comments
 (0)