diff --git a/conf/config.neon b/conf/config.neon index 1d7e5a19bc..7698bb7252 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -22,7 +22,8 @@ parameters: featureToggles: bleedingEdge: false disableRuntimeReflectionProvider: false - skipCheckGenericClasses: [] + skipCheckGenericClasses: + - DatePeriod fileExtensions: - php checkAdvancedIsset: false @@ -1528,6 +1529,10 @@ services: tags: - phpstan.broker.dynamicMethodReturnTypeExtension + - + class: PHPStan\Type\Php\DatePeriodConstructorReturnTypeExtension + tags: + - phpstan.broker.dynamicStaticMethodReturnTypeExtension exceptionTypeResolver: class: PHPStan\Rules\Exceptions\ExceptionTypeResolver diff --git a/src/Type/Php/DatePeriodConstructorReturnTypeExtension.php b/src/Type/Php/DatePeriodConstructorReturnTypeExtension.php new file mode 100644 index 0000000000..e9aa46a813 --- /dev/null +++ b/src/Type/Php/DatePeriodConstructorReturnTypeExtension.php @@ -0,0 +1,61 @@ +getName() === '__construct'; + } + + public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): Type + { + $thirdArgType = null; + if (isset($methodCall->getArgs()[2])) { + $thirdArgType = $scope->getType($methodCall->getArgs()[2]->value); + } + + if (!$thirdArgType instanceof Type) { + return new GenericObjectType(DatePeriod::class, [ + new NullType(), + new IntegerType(), + ]); + } + + if ((new ObjectType(DateTimeInterface::class))->isSuperTypeOf($thirdArgType)->yes()) { + return new GenericObjectType(DatePeriod::class, [ + new ObjectType(DateTimeInterface::class), + new NullType(), + ]); + } + + if ((new IntegerType())->isSuperTypeOf($thirdArgType)->yes()) { + return new GenericObjectType(DatePeriod::class, [ + new NullType(), + new IntegerType(), + ]); + } + + return new ObjectType(DatePeriod::class); + } + +} diff --git a/stubs/date.stub b/stubs/date.stub index 038d0fcb79..8704a65061 100644 --- a/stubs/date.stub +++ b/stubs/date.stub @@ -9,3 +9,27 @@ class DateInterval public $days; } + +/** + * @template TEnd of ?DateTimeInterface + * @template TRecurrences of ?int + */ +class DatePeriod +{ + + /** + * @return TEnd + */ + public function getEndDate() + { + + } + + /** + * @return TRecurrences + */ + public function getRecurrences() + { + + } +} diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 44972815ef..58d0bfa0d6 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -641,11 +641,14 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5698-php7.php'); } + if (PHP_VERSION_ID >= 70304) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/date-period-return-types.php'); + } + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6404.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6399.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4357.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5817.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/array-column.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/template-null-bound.php'); } diff --git a/tests/PHPStan/Analyser/data/date-period-return-types.php b/tests/PHPStan/Analyser/data/date-period-return-types.php new file mode 100644 index 0000000000..2694c55635 --- /dev/null +++ b/tests/PHPStan/Analyser/data/date-period-return-types.php @@ -0,0 +1,35 @@ +', $datePeriod); +assertType(\DateTimeInterface::class, $datePeriod->getEndDate()); +assertType('null', $datePeriod->getRecurrences()); +$datePeriodList[] = $datePeriod; + +$datePeriod = new DatePeriod($start, $interval, $recurrences); +assertType(\DatePeriod::class . '', $datePeriod); +assertType('null', $datePeriod->getEndDate()); +assertType('int', $datePeriod->getRecurrences()); +$datePeriodList[] = $datePeriod; + +$datePeriod = new DatePeriod($iso); +assertType(\DatePeriod::class . '', $datePeriod); +assertType('null', $datePeriod->getEndDate()); +assertType('int', $datePeriod->getRecurrences()); +$datePeriodList[] = $datePeriod; + +/** @var DatePeriod $datePeriod */ +$datePeriod = $datePeriodList[random_int(0, 2)]; +assertType(\DatePeriod::class, $datePeriod); +assertType(\DateTimeInterface::class . '|null', $datePeriod->getEndDate()); +assertType('int|null', $datePeriod->getRecurrences());