|  | 
|  | 1 | +<?php declare(strict_types = 1); | 
|  | 2 | + | 
|  | 3 | +namespace PHPStan\Type\Php; | 
|  | 4 | + | 
|  | 5 | +use DateTime; | 
|  | 6 | +use DateTimeImmutable; | 
|  | 7 | +use PhpParser\Node\Expr\MethodCall; | 
|  | 8 | +use PHPStan\Analyser\Scope; | 
|  | 9 | +use PHPStan\Php\PhpVersion; | 
|  | 10 | +use PHPStan\Reflection\MethodReflection; | 
|  | 11 | +use PHPStan\Type\DynamicMethodThrowTypeExtension; | 
|  | 12 | +use PHPStan\Type\NeverType; | 
|  | 13 | +use PHPStan\Type\ObjectType; | 
|  | 14 | +use PHPStan\Type\Type; | 
|  | 15 | +use PHPStan\Type\TypeCombinator; | 
|  | 16 | +use function count; | 
|  | 17 | +use function in_array; | 
|  | 18 | + | 
|  | 19 | +final class DateTimeModifyMethodThrowTypeExtension implements DynamicMethodThrowTypeExtension | 
|  | 20 | +{ | 
|  | 21 | + | 
|  | 22 | +	public function __construct(private PhpVersion $phpVersion) | 
|  | 23 | +	{ | 
|  | 24 | +	} | 
|  | 25 | + | 
|  | 26 | +	public function isMethodSupported(MethodReflection $methodReflection): bool | 
|  | 27 | +	{ | 
|  | 28 | +		return $methodReflection->getName() === 'modify' && in_array($methodReflection->getDeclaringClass()->getName(), [DateTime::class, DateTimeImmutable::class], true); | 
|  | 29 | +	} | 
|  | 30 | + | 
|  | 31 | +	public function getThrowTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type | 
|  | 32 | +	{ | 
|  | 33 | +		if (count($methodCall->getArgs()) === 0) { | 
|  | 34 | +			return null; | 
|  | 35 | +		} | 
|  | 36 | + | 
|  | 37 | +		if (!$this->phpVersion->hasDateTimeExceptions()) { | 
|  | 38 | +			return null; | 
|  | 39 | +		} | 
|  | 40 | + | 
|  | 41 | +		$valueType = $scope->getType($methodCall->getArgs()[0]->value); | 
|  | 42 | +		$constantStrings = $valueType->getConstantStrings(); | 
|  | 43 | + | 
|  | 44 | +		foreach ($constantStrings as $constantString) { | 
|  | 45 | +			try { | 
|  | 46 | +				$dateTime = new DateTime(); | 
|  | 47 | +				$dateTime->modify($constantString->getValue()); | 
|  | 48 | +			} catch (\Exception $e) { // phpcs:ignore | 
|  | 49 | +				return $this->exceptionType(); | 
|  | 50 | +			} | 
|  | 51 | + | 
|  | 52 | +			$valueType = TypeCombinator::remove($valueType, $constantString); | 
|  | 53 | +		} | 
|  | 54 | + | 
|  | 55 | +		if (!$valueType instanceof NeverType) { | 
|  | 56 | +			return $this->exceptionType(); | 
|  | 57 | +		} | 
|  | 58 | + | 
|  | 59 | +		return null; | 
|  | 60 | +	} | 
|  | 61 | + | 
|  | 62 | +	private function exceptionType(): Type | 
|  | 63 | +	{ | 
|  | 64 | +		if ($this->phpVersion->hasDateTimeExceptions()) { | 
|  | 65 | +			return new ObjectType('DateMalformedStringException'); | 
|  | 66 | +		} | 
|  | 67 | + | 
|  | 68 | +		return new ObjectType('Exception'); | 
|  | 69 | +	} | 
|  | 70 | + | 
|  | 71 | +} | 
0 commit comments