Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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('DateInvalidOperationException');
}

}
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