From bbc4b5ea1ed9be2667d218f9d76252454a3b412d Mon Sep 17 00:00:00 2001 From: Alban Baixas Date: Sat, 29 Jan 2022 13:55:24 +0100 Subject: [PATCH 1/8] Add DatePeriod dynamic return type --- conf/config.neon | 4 ++ ...tePeriodConstructorReturnTypeExtension.php | 54 +++++++++++++++++++ stubs/date.stub | 24 +++++++++ .../Analyser/NodeScopeResolverTest.php | 2 +- .../data/date-period-return-types.php | 21 ++++++++ 5 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 src/Type/Php/DatePeriodConstructorReturnTypeExtension.php create mode 100644 tests/PHPStan/Analyser/data/date-period-return-types.php diff --git a/conf/config.neon b/conf/config.neon index 1d7e5a19bc..9b6848b32e 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1528,6 +1528,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..4ac8839843 --- /dev/null +++ b/src/Type/Php/DatePeriodConstructorReturnTypeExtension.php @@ -0,0 +1,54 @@ +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 ObjectType + && $thirdArgType->isInstanceOf(DateTimeInterface::class) + ) { + + return new GenericObjectType(DatePeriod::class, [ + new ObjectType(DateTimeInterface::class), + new NullType(), + ]); + } + + return new GenericObjectType(DatePeriod::class, [ + new NullType(), + new IntegerType(), + ]); + } + +} 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..7d41f15e4d 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -645,9 +645,9 @@ public function dataFileAsserts(): iterable 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'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/date-period-return-types.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..1acc7df2f8 --- /dev/null +++ b/tests/PHPStan/Analyser/data/date-period-return-types.php @@ -0,0 +1,21 @@ +getEndDate()); +assertType('null', $datePeriod->getRecurrences()); + +$datePeriod = new DatePeriod($start, $interval, $recurrences); +assertType('null', $datePeriod->getEndDate()); +assertType('int', $datePeriod->getRecurrences()); + +$datePeriod = new DatePeriod($iso); +assertType('null', $datePeriod->getEndDate()); +assertType('int', $datePeriod->getRecurrences()); From dcff28153383cda7d28e3e4c42adcb36d2a6d068 Mon Sep 17 00:00:00 2001 From: Alban Baixas Date: Sat, 29 Jan 2022 18:39:45 +0100 Subject: [PATCH 2/8] Update src/Type/Php/DatePeriodConstructorReturnTypeExtension.php Co-authored-by: Markus Staab --- src/Type/Php/DatePeriodConstructorReturnTypeExtension.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Type/Php/DatePeriodConstructorReturnTypeExtension.php b/src/Type/Php/DatePeriodConstructorReturnTypeExtension.php index 4ac8839843..7e22825d15 100644 --- a/src/Type/Php/DatePeriodConstructorReturnTypeExtension.php +++ b/src/Type/Php/DatePeriodConstructorReturnTypeExtension.php @@ -36,7 +36,7 @@ public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, if ( $thirdArgType instanceof ObjectType - && $thirdArgType->isInstanceOf(DateTimeInterface::class) + && $thirdArgType->isInstanceOf(DateTimeInterface::class)->yes() ) { return new GenericObjectType(DatePeriod::class, [ From 2078cab403030293ebc66785db3b6f8f7f96ae1b Mon Sep 17 00:00:00 2001 From: Alban Baixas Date: Sat, 29 Jan 2022 19:13:26 +0100 Subject: [PATCH 3/8] Remove template type definition --- stubs/date.stub | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stubs/date.stub b/stubs/date.stub index 8704a65061..8f65630b53 100644 --- a/stubs/date.stub +++ b/stubs/date.stub @@ -11,8 +11,8 @@ class DateInterval } /** - * @template TEnd of ?DateTimeInterface - * @template TRecurrences of ?int + * @template TEnd + * @template TRecurrences */ class DatePeriod { From c91556d685e7cfad8b79a56007168f62cada60e7 Mon Sep 17 00:00:00 2001 From: Alban Baixas Date: Sun, 30 Jan 2022 09:21:04 +0100 Subject: [PATCH 4/8] Add DatePeriod test --- stubs/date.stub | 4 ++-- .../PHPStan/Analyser/data/date-period-return-types.php | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/stubs/date.stub b/stubs/date.stub index 8f65630b53..1c4b95dcae 100644 --- a/stubs/date.stub +++ b/stubs/date.stub @@ -20,7 +20,7 @@ class DatePeriod /** * @return TEnd */ - public function getEndDate() + public function getEndDate(): ?DateTimeInterface { } @@ -28,7 +28,7 @@ class DatePeriod /** * @return TRecurrences */ - public function getRecurrences() + public function getRecurrences(): ?int { } diff --git a/tests/PHPStan/Analyser/data/date-period-return-types.php b/tests/PHPStan/Analyser/data/date-period-return-types.php index 1acc7df2f8..d8bf035d84 100644 --- a/tests/PHPStan/Analyser/data/date-period-return-types.php +++ b/tests/PHPStan/Analyser/data/date-period-return-types.php @@ -8,14 +8,24 @@ $recurrences = 4; $iso = 'R4/2012-07-01T00:00:00Z/P7D'; +$datePeriodList = []; + $datePeriod = new DatePeriod($start, $interval, $end); assertType(\DateTimeInterface::class, $datePeriod->getEndDate()); assertType('null', $datePeriod->getRecurrences()); +$datePeriodList[] = $datePeriod; $datePeriod = new DatePeriod($start, $interval, $recurrences); assertType('null', $datePeriod->getEndDate()); assertType('int', $datePeriod->getRecurrences()); +$datePeriodList[] = $datePeriod; $datePeriod = new DatePeriod($iso); assertType('null', $datePeriod->getEndDate()); assertType('int', $datePeriod->getRecurrences()); +$datePeriodList[] = $datePeriod; + +/** @var DatePeriod $datePeriod */ +$datePeriod = $datePeriodList[random_int(0, 2)]; +assertType(\DateTimeInterface::class . '|null', $datePeriod->getEndDate()); +assertType('int|null', $datePeriod->getRecurrences()); From 51717807e78c3a55c18f5e97119cc9043a81c1e1 Mon Sep 17 00:00:00 2001 From: Alban Baixas Date: Sun, 30 Jan 2022 10:46:54 +0100 Subject: [PATCH 5/8] Restore previous type definition --- stubs/date.stub | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/stubs/date.stub b/stubs/date.stub index 1c4b95dcae..8704a65061 100644 --- a/stubs/date.stub +++ b/stubs/date.stub @@ -11,8 +11,8 @@ class DateInterval } /** - * @template TEnd - * @template TRecurrences + * @template TEnd of ?DateTimeInterface + * @template TRecurrences of ?int */ class DatePeriod { @@ -20,7 +20,7 @@ class DatePeriod /** * @return TEnd */ - public function getEndDate(): ?DateTimeInterface + public function getEndDate() { } @@ -28,7 +28,7 @@ class DatePeriod /** * @return TRecurrences */ - public function getRecurrences(): ?int + public function getRecurrences() { } From 362f9bfafae008b50949d63ca0fda98d31c9f550 Mon Sep 17 00:00:00 2001 From: Alban Baixas Date: Sun, 30 Jan 2022 12:18:28 +0100 Subject: [PATCH 6/8] Include test only on PHP >= 7.3.4 --- src/Type/Php/DatePeriodConstructorReturnTypeExtension.php | 2 +- tests/PHPStan/Analyser/NodeScopeResolverTest.php | 5 ++++- tests/PHPStan/Analyser/data/date-period-return-types.php | 4 ++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Type/Php/DatePeriodConstructorReturnTypeExtension.php b/src/Type/Php/DatePeriodConstructorReturnTypeExtension.php index 7e22825d15..180b5481f2 100644 --- a/src/Type/Php/DatePeriodConstructorReturnTypeExtension.php +++ b/src/Type/Php/DatePeriodConstructorReturnTypeExtension.php @@ -36,7 +36,7 @@ public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, if ( $thirdArgType instanceof ObjectType - && $thirdArgType->isInstanceOf(DateTimeInterface::class)->yes() + && (new ObjectType(DateTimeInterface::class))->isSuperTypeOf($thirdArgType)->yes() ) { return new GenericObjectType(DatePeriod::class, [ diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 7d41f15e4d..58d0bfa0d6 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -641,13 +641,16 @@ 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'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/date-period-return-types.php'); } /** diff --git a/tests/PHPStan/Analyser/data/date-period-return-types.php b/tests/PHPStan/Analyser/data/date-period-return-types.php index d8bf035d84..24654c00b8 100644 --- a/tests/PHPStan/Analyser/data/date-period-return-types.php +++ b/tests/PHPStan/Analyser/data/date-period-return-types.php @@ -11,21 +11,25 @@ $datePeriodList = []; $datePeriod = new DatePeriod($start, $interval, $end); +assertType(\DatePeriod::class, $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()); From 5c0732aa6bd595a68bc8060a7ae62d03766cfc39 Mon Sep 17 00:00:00 2001 From: Alban Baixas Date: Sun, 30 Jan 2022 13:25:13 +0100 Subject: [PATCH 7/8] review --- ...tePeriodConstructorReturnTypeExtension.php | 23 ++++++++++++------- .../data/date-period-return-types.php | 6 ++--- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/Type/Php/DatePeriodConstructorReturnTypeExtension.php b/src/Type/Php/DatePeriodConstructorReturnTypeExtension.php index 180b5481f2..e9aa46a813 100644 --- a/src/Type/Php/DatePeriodConstructorReturnTypeExtension.php +++ b/src/Type/Php/DatePeriodConstructorReturnTypeExtension.php @@ -34,21 +34,28 @@ public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, $thirdArgType = $scope->getType($methodCall->getArgs()[2]->value); } - if ( - $thirdArgType instanceof ObjectType - && (new ObjectType(DateTimeInterface::class))->isSuperTypeOf($thirdArgType)->yes() - ) { + 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(), ]); } - return new GenericObjectType(DatePeriod::class, [ - new NullType(), - new IntegerType(), - ]); + if ((new IntegerType())->isSuperTypeOf($thirdArgType)->yes()) { + return new GenericObjectType(DatePeriod::class, [ + new NullType(), + new IntegerType(), + ]); + } + + return new ObjectType(DatePeriod::class); } } diff --git a/tests/PHPStan/Analyser/data/date-period-return-types.php b/tests/PHPStan/Analyser/data/date-period-return-types.php index 24654c00b8..2694c55635 100644 --- a/tests/PHPStan/Analyser/data/date-period-return-types.php +++ b/tests/PHPStan/Analyser/data/date-period-return-types.php @@ -11,19 +11,19 @@ $datePeriodList = []; $datePeriod = new DatePeriod($start, $interval, $end); -assertType(\DatePeriod::class, $datePeriod); +assertType(\DatePeriod::class . '', $datePeriod); assertType(\DateTimeInterface::class, $datePeriod->getEndDate()); assertType('null', $datePeriod->getRecurrences()); $datePeriodList[] = $datePeriod; $datePeriod = new DatePeriod($start, $interval, $recurrences); -assertType(\DatePeriod::class, $datePeriod); +assertType(\DatePeriod::class . '', $datePeriod); assertType('null', $datePeriod->getEndDate()); assertType('int', $datePeriod->getRecurrences()); $datePeriodList[] = $datePeriod; $datePeriod = new DatePeriod($iso); -assertType(\DatePeriod::class, $datePeriod); +assertType(\DatePeriod::class . '', $datePeriod); assertType('null', $datePeriod->getEndDate()); assertType('int', $datePeriod->getRecurrences()); $datePeriodList[] = $datePeriod; From 1676836307fb7d1e911593c1c41c25259df3451d Mon Sep 17 00:00:00 2001 From: Alban Baixas Date: Sun, 30 Jan 2022 16:29:28 +0100 Subject: [PATCH 8/8] Add DatePeriod in featureToggles skipCheckGenericClasses --- conf/config.neon | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/conf/config.neon b/conf/config.neon index 9b6848b32e..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