Skip to content

Commit 181f75c

Browse files
committed
DateTime and DateTimeImmutable constructor does not always throw Exception
1 parent 40d93a1 commit 181f75c

8 files changed

Lines changed: 128 additions & 13 deletions

File tree

build/phpstan.neon

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,6 @@ parameters:
3434
- ../tests/PHPStan/Command/IgnoredRegexValidatorTest.php
3535
- ../src/Command/IgnoredRegexValidator.php
3636
exceptions:
37-
uncheckedExceptionRegexes:
38-
- '~^Exception$~'
3937
uncheckedExceptionClasses:
4038
- 'PHPStan\ShouldNotHappenException'
4139
- 'Symfony\Component\Console\Exception\InvalidArgumentException'

conf/config.neon

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1025,6 +1025,11 @@ services:
10251025
tags:
10261026
- phpstan.broker.dynamicFunctionReturnTypeExtension
10271027

1028+
-
1029+
class: PHPStan\Type\Php\DateTimeConstructorThrowTypeExtension
1030+
tags:
1031+
- phpstan.dynamicStaticMethodThrowTypeExtension
1032+
10281033
-
10291034
class: PHPStan\Type\Php\DsMapDynamicReturnTypeExtension
10301035
tags:

src/Analyser/NodeScopeResolver.php

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2458,15 +2458,9 @@ static function () use ($expr, $rightResult): MutatingScope {
24582458
$expr->args,
24592459
$constructorReflection->getVariants()
24602460
);
2461-
if ($constructorReflection->getThrowType() !== null) {
2462-
$throwType = $constructorReflection->getThrowType();
2463-
if (!$throwType instanceof VoidType) {
2464-
$throwPoints[] = ThrowPoint::createExplicit($scope, $throwType, $expr, true);
2465-
}
2466-
} elseif ($this->implicitThrows) {
2467-
if ($classReflection->getName() !== \Throwable::class && !$classReflection->isSubclassOf(\Throwable::class)) {
2468-
$throwPoints[] = ThrowPoint::createImplicit($scope, $expr);
2469-
}
2461+
$constructorThrowPoint = $this->getConstructorThrowPoint($constructorReflection, $classReflection, $expr, $expr->class, $expr->args, $scope);
2462+
if ($constructorThrowPoint !== null) {
2463+
$throwPoints[] = $constructorThrowPoint;
24702464
}
24712465
}
24722466
} else {
@@ -2720,6 +2714,39 @@ private function getMethodThrowPoint(MethodReflection $methodReflection, MethodC
27202714
return null;
27212715
}
27222716

2717+
/**
2718+
* @param Node\Arg[] $args
2719+
*/
2720+
private function getConstructorThrowPoint(MethodReflection $constructorReflection, ClassReflection $classReflection, New_ $new, Name $className, array $args, MutatingScope $scope): ?ThrowPoint
2721+
{
2722+
$methodCall = new StaticCall($className, $constructorReflection->getName(), $args);
2723+
foreach ($this->dynamicThrowTypeExtensionProvider->getDynamicStaticMethodThrowTypeExtensions() as $extension) {
2724+
if (!$extension->isStaticMethodSupported($constructorReflection)) {
2725+
continue;
2726+
}
2727+
2728+
$throwType = $extension->getThrowTypeFromStaticMethodCall($constructorReflection, $methodCall, $scope);
2729+
if ($throwType === null) {
2730+
return null;
2731+
}
2732+
2733+
return ThrowPoint::createExplicit($scope, $throwType, $new, false);
2734+
}
2735+
2736+
if ($constructorReflection->getThrowType() !== null) {
2737+
$throwType = $constructorReflection->getThrowType();
2738+
if (!$throwType instanceof VoidType) {
2739+
return ThrowPoint::createExplicit($scope, $throwType, $new, true);
2740+
}
2741+
} elseif ($this->implicitThrows) {
2742+
if ($classReflection->getName() !== \Throwable::class && !$classReflection->isSubclassOf(\Throwable::class)) {
2743+
return ThrowPoint::createImplicit($scope, $methodCall);
2744+
}
2745+
}
2746+
2747+
return null;
2748+
}
2749+
27232750
private function getStaticMethodThrowPoint(MethodReflection $methodReflection, StaticCall $methodCall, MutatingScope $scope): ?ThrowPoint
27242751
{
27252752
foreach ($this->dynamicThrowTypeExtensionProvider->getDynamicStaticMethodThrowTypeExtensions() as $extension) {

src/Command/FixerApplication.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,7 @@ private function downloadPhar(
387387
);
388388

389389
/** @var array{url: string, version: string} $latestInfo */
390-
$latestInfo = Json::decode((string) await($client->get('https://fixer-download-api.phpstan.com/latest'), $loop, 5.0)->getBody(), Json::FORCE_ARRAY);
390+
$latestInfo = Json::decode((string) await($client->get('https://fixer-download-api.phpstan.com/latest'), $loop, 5.0)->getBody(), Json::FORCE_ARRAY); // @phpstan-ignore-line
391391
if ($currentVersion !== null && $latestInfo['version'] === $currentVersion) {
392392
$this->writeInfoFile($infoPath, $latestInfo['version']);
393393
$output->writeln('<fg=green>You\'re running the latest PHPStan Pro!</>');
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Php;
4+
5+
use DateTime;
6+
use DateTimeImmutable;
7+
use PhpParser\Node\Expr\StaticCall;
8+
use PHPStan\Analyser\Scope;
9+
use PHPStan\Reflection\MethodReflection;
10+
use PHPStan\Type\DynamicStaticMethodThrowTypeExtension;
11+
use PHPStan\Type\Type;
12+
use PHPStan\Type\TypeUtils;
13+
14+
class DateTimeConstructorThrowTypeExtension implements DynamicStaticMethodThrowTypeExtension
15+
{
16+
17+
public function isStaticMethodSupported(MethodReflection $methodReflection): bool
18+
{
19+
return $methodReflection->getName() === '__construct' && in_array($methodReflection->getDeclaringClass()->getName(), [DateTime::class, DateTimeImmutable::class], true);
20+
}
21+
22+
public function getThrowTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): ?Type
23+
{
24+
if (count($methodCall->args) === 0) {
25+
return null;
26+
}
27+
28+
$arg = $methodCall->args[0]->value;
29+
$constantStrings = TypeUtils::getConstantStrings($scope->getType($arg));
30+
if (count($constantStrings) === 0) {
31+
return $methodReflection->getThrowType();
32+
}
33+
34+
foreach ($constantStrings as $constantString) {
35+
try {
36+
new \DateTime($constantString->getValue());
37+
} catch (\Exception $e) { // phpcs:ignore
38+
return $methodReflection->getThrowType();
39+
}
40+
}
41+
42+
return null;
43+
}
44+
45+
}

src/Type/Php/SimpleXMLElementXpathMethodReturnTypeExtension.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method
3434

3535
$argType = $scope->getType($methodCall->args[0]->value);
3636

37-
$xmlElement = new \SimpleXMLElement('<foo />');
37+
$xmlElement = new \SimpleXMLElement('<foo />'); // @phpstan-ignore-line
3838

3939
foreach (TypeUtils::getConstantStrings($argType) as $constantString) {
4040
$result = @$xmlElement->xpath($constantString->getValue());

tests/PHPStan/Rules/Exceptions/CatchWithUnthrownExceptionRuleTest.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,14 @@ public function testRule(): void
5151
'Dead catch - Throwable is never thrown in the try block.',
5252
119,
5353
],
54+
[
55+
'Dead catch - Exception is never thrown in the try block.',
56+
171,
57+
],
58+
[
59+
'Dead catch - Exception is never thrown in the try block.',
60+
180,
61+
],
5462
]);
5563
}
5664

tests/PHPStan/Rules/Exceptions/data/unthrown-exception.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,3 +160,35 @@ public function doBar()
160160
}
161161

162162
}
163+
164+
class TestDateTime
165+
{
166+
167+
public function doFoo(): void
168+
{
169+
try {
170+
new \DateTime();
171+
} catch (\Exception $e) {
172+
173+
}
174+
}
175+
176+
public function doBar(): void
177+
{
178+
try {
179+
new \DateTime('now');
180+
} catch (\Exception $e) {
181+
182+
}
183+
}
184+
185+
public function doBaz(string $s): void
186+
{
187+
try {
188+
new \DateTime($s);
189+
} catch (\Exception $e) {
190+
191+
}
192+
}
193+
194+
}

0 commit comments

Comments
 (0)