Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -1421,6 +1421,11 @@ services:
tags:
- phpstan.dynamicMethodThrowTypeExtension

-
class: PHPStan\Type\Php\DateTimeSubMethodThrowTypeExtension
tags:
- phpstan.dynamicMethodThrowTypeExtension

-
class: PHPStan\Type\Php\DateTimeZoneConstructorThrowTypeExtension
tags:
Expand Down
43 changes: 43 additions & 0 deletions src/Type/Php/DateTimeSubMethodThrowTypeExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Php;

use DateTime;
use DateTimeImmutable;
use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Php\PhpVersion;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Type\DynamicMethodThrowTypeExtension;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use function count;
use function in_array;

final class DateTimeSubMethodThrowTypeExtension implements DynamicMethodThrowTypeExtension
{

public function __construct(private PhpVersion $phpVersion)
{
}

public function isMethodSupported(MethodReflection $methodReflection): bool
{
return $methodReflection->getName() === 'sub'
&& in_array($methodReflection->getDeclaringClass()->getName(), [DateTime::class, DateTimeImmutable::class], true);
}

public function getThrowTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type
{
if (count($methodCall->getArgs()) === 0) {
return null;
}

if (!$this->phpVersion->hasDateTimeExceptions()) {
return null;
}

return new ObjectType('DateMalformedStringException');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to check the constant strings in the argument type and only throw this exception if some of them are invalid. Or if there are zero constant strings.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DateTime::sub does not accepts constant strings, the signature is

public DateTime::sub(DateInterval $interval): DateTime

It only throw an exception if the DateInterval is relative like

$dt->sub(new DateInterval('next weekday'))

but I didnt see a way to have something like getDateIntervalConstantStrings(). I wasn't sure

So as a first step, it improve the situation for all the PHP version lower than 8.3.

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleLevelHelper;
use PHPStan\Testing\RuleTestCase;
use function array_merge;
use const PHP_VERSION_ID;

/**
* @extends RuleTestCase<CallToMethodStatementWithoutSideEffectsRule>
Expand Down Expand Up @@ -89,6 +91,26 @@ public function testBug4455(): void
$this->analyse([__DIR__ . '/data/bug-4455.php'], []);
}

public function testBug11503(): void
{
$errors = [
['Call to method DateTimeImmutable::add() on a separate line has no effect.', 10],
['Call to method DateTimeImmutable::modify() on a separate line has no effect.', 11],
['Call to method DateTimeImmutable::setDate() on a separate line has no effect.', 12],
['Call to method DateTimeImmutable::setISODate() on a separate line has no effect.', 13],
['Call to method DateTimeImmutable::setTime() on a separate line has no effect.', 14],
['Call to method DateTimeImmutable::setTimestamp() on a separate line has no effect.', 15],
['Call to method DateTimeImmutable::setTimezone() on a separate line has no effect.', 17],
];
if (PHP_VERSION_ID < 80300) {
$errors = array_merge([
['Call to method DateTimeImmutable::sub() on a separate line has no effect.', 9],
], $errors);
}

$this->analyse([__DIR__ . '/data/bug-11503.php'], $errors);
}

public function testFirstClassCallables(): void
{
$this->analyse([__DIR__ . '/data/first-class-callable-method-without-side-effect.php'], [
Expand Down
19 changes: 19 additions & 0 deletions tests/PHPStan/Rules/Methods/data/bug-11503.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace Bug11503;

class Foo {
public function test() {
$date = new \DateTimeImmutable();
$interval = new \DateInterval('P1M');
$date->sub($interval);
$date->add($interval);
$date->modify('+1 day');
$date->setDate(2024, 8, 13);
$date->setISODate(2024, 1);
$date->setTime(0, 0, 0, 0);
$date->setTimestamp(1);
$zone = new \DateTimeZone('UTC');
$date->setTimezone($zone);
}
}
Loading