From c1bc7c1040e3dc4f3881ce68624b25a657a857e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=B2=A0=5F=E0=B2=A0?= Date: Mon, 12 Dec 2022 10:20:21 +0100 Subject: [PATCH 01/14] implement DateIntervalDynamicReturnTypeExtension --- conf/config.neon | 5 +++ resources/functionMap.php | 2 +- ...DateIntervalDynamicReturnTypeExtension.php | 45 +++++++++++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 src/Type/Php/DateIntervalDynamicReturnTypeExtension.php diff --git a/conf/config.neon b/conf/config.neon index bd50802659..e7ea745f74 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1324,6 +1324,11 @@ services: tags: - phpstan.dynamicStaticMethodThrowTypeExtension + - + class: PHPStan\Type\Php\DateIntervalDynamicReturnTypeExtension + tags: + - phpstan.broker.dynamicStaticMethodReturnTypeExtension + - class: PHPStan\Type\Php\DateTimeCreateDynamicReturnTypeExtension tags: diff --git a/resources/functionMap.php b/resources/functionMap.php index 54ab61ed33..0b25c6784b 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -1583,7 +1583,7 @@ 'DateInterval::__construct' => ['void', 'spec'=>'string'], 'DateInterval::__set_state' => ['DateInterval', 'array'=>'array'], 'DateInterval::__wakeup' => ['void'], -'DateInterval::createFromDateString' => ['DateInterval', 'time'=>'string'], +'DateInterval::createFromDateString' => ['DateInterval|false', 'time'=>'string'], 'DateInterval::format' => ['string', 'format'=>'string'], 'DatePeriod::__construct' => ['void', 'start'=>'DateTimeInterface', 'interval'=>'DateInterval', 'recur'=>'int', 'options='=>'int'], 'DatePeriod::__construct\'1' => ['void', 'start'=>'DateTimeInterface', 'interval'=>'DateInterval', 'end'=>'DateTimeInterface', 'options='=>'int'], diff --git a/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php b/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php new file mode 100644 index 0000000000..73f85f39cb --- /dev/null +++ b/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php @@ -0,0 +1,45 @@ +getName() === 'createFromDateString'; + } + + public function getTypeFromStaticMethodCall( MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope ): ?\PHPStan\Type\Type + { + $defaultReturnType = ParametersAcceptorSelector::selectFromArgs( + $scope, + $methodCall->getArgs(), + $methodReflection->getVariants(), + )->getReturnType(); + + $dateTimeString = $scope->getType($methodCall->getArgs()[0]->value); + + if (!($dateTimeString instanceof ConstantStringType)) { + return $defaultReturnType; + } + + $isValid = DateInterval::createFromDateString($dateTimeString->getValue()) !== false; + + return $isValid ? new ObjectType(DateInterval::class) : new ConstantBooleanType(false); + } +} From f2ffb9c6adb0b04a5c227b06985a471b9e0e6ec4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=B2=A0=5F=E0=B2=A0?= Date: Mon, 12 Dec 2022 10:33:23 +0100 Subject: [PATCH 02/14] add testcase --- tests/PHPStan/Analyser/NodeScopeResolverTest.php | 1 + tests/PHPStan/Analyser/data/bug-8442.php | 8 ++++++++ 2 files changed, 9 insertions(+) create mode 100644 tests/PHPStan/Analyser/data/bug-8442.php diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index f5c57d2bdd..e4b753ad5d 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -1136,6 +1136,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/imagick-pixel.php'); yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Arrays/data/bug-8467a.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8467b.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8442.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/PDOStatement.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/discussion-8447.php'); } diff --git a/tests/PHPStan/Analyser/data/bug-8442.php b/tests/PHPStan/Analyser/data/bug-8442.php new file mode 100644 index 0000000000..a6dd50bad7 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-8442.php @@ -0,0 +1,8 @@ + Date: Mon, 12 Dec 2022 10:44:06 +0100 Subject: [PATCH 03/14] cs-fix --- src/Type/Php/DateIntervalDynamicReturnTypeExtension.php | 5 ++++- tests/PHPStan/Analyser/data/bug-8442.php | 5 +++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php b/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php index 73f85f39cb..1e5d4e7868 100644 --- a/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php +++ b/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php @@ -11,9 +11,11 @@ use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\DynamicStaticMethodReturnTypeExtension; use PHPStan\Type\ObjectType; +use PHPStan\Type\Type; class DateIntervalDynamicReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension { + public function getClass(): string { return DateInterval::class; @@ -24,7 +26,7 @@ public function isStaticMethodSupported(MethodReflection $methodReflection): boo return $methodReflection->getName() === 'createFromDateString'; } - public function getTypeFromStaticMethodCall( MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope ): ?\PHPStan\Type\Type + public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): ?Type { $defaultReturnType = ParametersAcceptorSelector::selectFromArgs( $scope, @@ -42,4 +44,5 @@ public function getTypeFromStaticMethodCall( MethodReflection $methodReflection, return $isValid ? new ObjectType(DateInterval::class) : new ConstantBooleanType(false); } + } diff --git a/tests/PHPStan/Analyser/data/bug-8442.php b/tests/PHPStan/Analyser/data/bug-8442.php index a6dd50bad7..a8935faee2 100644 --- a/tests/PHPStan/Analyser/data/bug-8442.php +++ b/tests/PHPStan/Analyser/data/bug-8442.php @@ -3,6 +3,7 @@ namespace Bug8442; use function PHPStan\Testing\assertType; +use DateInterval; -assertType('false', \DateInterval::createFromDateString('foo')); -assertType('DateInterval', \DateInterval::createFromDateString('1 Day')); +assertType('false', DateInterval::createFromDateString('foo')); +assertType('DateInterval', DateInterval::createFromDateString('1 Day')); From fba9dbb38c488782ef63d33440980210995035a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=B2=A0=5F=E0=B2=A0?= Date: Mon, 12 Dec 2022 14:12:08 +0100 Subject: [PATCH 04/14] wrap test in function --- tests/PHPStan/Analyser/data/bug-8442.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/PHPStan/Analyser/data/bug-8442.php b/tests/PHPStan/Analyser/data/bug-8442.php index a8935faee2..71449d86b6 100644 --- a/tests/PHPStan/Analyser/data/bug-8442.php +++ b/tests/PHPStan/Analyser/data/bug-8442.php @@ -5,5 +5,8 @@ use function PHPStan\Testing\assertType; use DateInterval; -assertType('false', DateInterval::createFromDateString('foo')); -assertType('DateInterval', DateInterval::createFromDateString('1 Day')); +function () { + assertType('false', DateInterval::createFromDateString('foo')); + assertType('DateInterval', DateInterval::createFromDateString('1 Day')); +}; + From 19345a658658d94a3fbbb391d3a53447479dbef7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=B2=A0=5F=E0=B2=A0?= Date: Mon, 12 Dec 2022 18:04:58 +0100 Subject: [PATCH 05/14] just return null instead of the default type --- .../Php/DateIntervalDynamicReturnTypeExtension.php | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php b/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php index 1e5d4e7868..da06b280bc 100644 --- a/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php +++ b/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php @@ -6,12 +6,12 @@ use PhpParser\Node\Expr\StaticCall; use PHPStan\Analyser\Scope; use PHPStan\Reflection\MethodReflection; -use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\DynamicStaticMethodReturnTypeExtension; use PHPStan\Type\ObjectType; use PHPStan\Type\Type; +use PHPStan\Type\TypeUtils; class DateIntervalDynamicReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension { @@ -28,16 +28,10 @@ public function isStaticMethodSupported(MethodReflection $methodReflection): boo public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): ?Type { - $defaultReturnType = ParametersAcceptorSelector::selectFromArgs( - $scope, - $methodCall->getArgs(), - $methodReflection->getVariants(), - )->getReturnType(); - - $dateTimeString = $scope->getType($methodCall->getArgs()[0]->value); + $dateTimeString = TypeUtils::getConstantStrings($scope->getType($methodCall->getArgs()[0]->value)); if (!($dateTimeString instanceof ConstantStringType)) { - return $defaultReturnType; + return null; } $isValid = DateInterval::createFromDateString($dateTimeString->getValue()) !== false; From ee694f09304060cf328fb8da5340cd0f861bf3e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=B2=A0=5F=E0=B2=A0?= Date: Wed, 14 Dec 2022 08:00:31 +0100 Subject: [PATCH 06/14] Update src/Type/Php/DateIntervalDynamicReturnTypeExtension.php Co-authored-by: Markus Staab --- ...DateIntervalDynamicReturnTypeExtension.php | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php b/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php index da06b280bc..32710f2a1a 100644 --- a/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php +++ b/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php @@ -28,15 +28,26 @@ public function isStaticMethodSupported(MethodReflection $methodReflection): boo public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): ?Type { - $dateTimeString = TypeUtils::getConstantStrings($scope->getType($methodCall->getArgs()[0]->value)); - - if (!($dateTimeString instanceof ConstantStringType)) { - return null; + $strings = $scope->getType($methodCall->getArgs()[0]->value)->getConstantStrings(); + + if ($strings === []) { + return null; + } + + if (count($strings) === 1) { + if (DateInterval::createFromDateString($dateTimeString->getValue()) === false) { + return new ConstantBooleanType(false); + } + return new ObjectType(DateInterval::class); + } + + foreach($strings as $string) { + if (DateInterval::createFromDateString($dateTimeString->getValue()) === false) { + return null; + } } - $isValid = DateInterval::createFromDateString($dateTimeString->getValue()) !== false; - - return $isValid ? new ObjectType(DateInterval::class) : new ConstantBooleanType(false); + return new ObjectType(DateInterval::class); } } From 7f35be720d7771fcc448010a4cc7406b494a9998 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=B2=A0=5F=E0=B2=A0?= Date: Wed, 14 Dec 2022 08:00:54 +0100 Subject: [PATCH 07/14] add another test case --- tests/PHPStan/Analyser/data/bug-8442.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/PHPStan/Analyser/data/bug-8442.php b/tests/PHPStan/Analyser/data/bug-8442.php index 71449d86b6..0b654ca2fe 100644 --- a/tests/PHPStan/Analyser/data/bug-8442.php +++ b/tests/PHPStan/Analyser/data/bug-8442.php @@ -8,5 +8,13 @@ function () { assertType('false', DateInterval::createFromDateString('foo')); assertType('DateInterval', DateInterval::createFromDateString('1 Day')); + + if (rand(0,1)) { + $interval = 'P1Y'; + } else { + $interval = 'P2Y'; + } + + assertType('DateInterval', DateInterval::createFromDateString($interval)); }; From aef572f9b8d23001bfe2541bd6520de5012744c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=B2=A0=5F=E0=B2=A0?= Date: Wed, 14 Dec 2022 08:14:18 +0100 Subject: [PATCH 08/14] add more testcases and cleanup --- ...DateIntervalDynamicReturnTypeExtension.php | 42 ++++++++++--------- tests/PHPStan/Analyser/data/bug-8442.php | 12 +++++- 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php b/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php index 32710f2a1a..4bc66928d8 100644 --- a/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php +++ b/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php @@ -1,4 +1,4 @@ -getName() === 'createFromDateString'; } - public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): ?Type + public function getTypeFromStaticMethodCall( MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope ): ?Type { - $strings = $scope->getType($methodCall->getArgs()[0]->value)->getConstantStrings(); - - if ($strings === []) { - return null; + $strings = $scope->getType( $methodCall->getArgs()[ 0 ]->value )->getConstantStrings(); + + if ( $strings === [] ) { + return null; } - - if (count($strings) === 1) { - if (DateInterval::createFromDateString($dateTimeString->getValue()) === false) { - return new ConstantBooleanType(false); - } - return new ObjectType(DateInterval::class); + + if ( count( $strings ) === 1 ) { + if ( DateInterval::createFromDateString( $strings[ 0 ]->getValue() ) === false ) { + return new ConstantBooleanType( false ); + } + + return new ObjectType( DateInterval::class ); } - - foreach($strings as $string) { - if (DateInterval::createFromDateString($dateTimeString->getValue()) === false) { - return null; - } + + foreach ( $strings as $string ) { + if ( DateInterval::createFromDateString( $string->getValue() ) === false ) { + return null; + } } - return new ObjectType(DateInterval::class); + return new ObjectType( DateInterval::class ); } } diff --git a/tests/PHPStan/Analyser/data/bug-8442.php b/tests/PHPStan/Analyser/data/bug-8442.php index 0b654ca2fe..f3733a03ad 100644 --- a/tests/PHPStan/Analyser/data/bug-8442.php +++ b/tests/PHPStan/Analyser/data/bug-8442.php @@ -10,11 +10,19 @@ function () { assertType('DateInterval', DateInterval::createFromDateString('1 Day')); if (rand(0,1)) { - $interval = 'P1Y'; + $interval = '1 day'; } else { - $interval = 'P2Y'; + $interval = '2 day'; } assertType('DateInterval', DateInterval::createFromDateString($interval)); + + if (rand(0,1)) { + $interval = 'foo'; + } else { + $interval = '2 day'; + } + + assertType('DateInterval|false', DateInterval::createFromDateString($interval)); }; From c5ae2b352d2d22bf3387be08052d09cb467126a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=B2=A0=5F=E0=B2=A0?= Date: Wed, 14 Dec 2022 08:20:13 +0100 Subject: [PATCH 09/14] csfix --- ...DateIntervalDynamicReturnTypeExtension.php | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php b/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php index 4bc66928d8..c852be3f88 100644 --- a/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php +++ b/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php @@ -7,11 +7,10 @@ use PHPStan\Analyser\Scope; use PHPStan\Reflection\MethodReflection; use PHPStan\Type\Constant\ConstantBooleanType; -use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\DynamicStaticMethodReturnTypeExtension; use PHPStan\Type\ObjectType; use PHPStan\Type\Type; -use PHPStan\Type\TypeUtils; +use function count; class DateIntervalDynamicReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension @@ -22,34 +21,34 @@ public function getClass(): string return DateInterval::class; } - public function isStaticMethodSupported( MethodReflection $methodReflection ): bool + public function isStaticMethodSupported(MethodReflection $methodReflection): bool { return $methodReflection->getName() === 'createFromDateString'; } - public function getTypeFromStaticMethodCall( MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope ): ?Type + public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): ?Type { - $strings = $scope->getType( $methodCall->getArgs()[ 0 ]->value )->getConstantStrings(); + $strings = $scope->getType($methodCall->getArgs()[0 ]->value)->getConstantStrings(); - if ( $strings === [] ) { + if ( $strings === []) { return null; } - if ( count( $strings ) === 1 ) { - if ( DateInterval::createFromDateString( $strings[ 0 ]->getValue() ) === false ) { - return new ConstantBooleanType( false ); + if ( count($strings) === 1) { + if ( DateInterval::createFromDateString($strings[0 ]->getValue()) === false) { + return new ConstantBooleanType(false); } - return new ObjectType( DateInterval::class ); + return new ObjectType(DateInterval::class); } - foreach ( $strings as $string ) { - if ( DateInterval::createFromDateString( $string->getValue() ) === false ) { + foreach ($strings as $string) { + if ( DateInterval::createFromDateString($string->getValue()) === false) { return null; } } - return new ObjectType( DateInterval::class ); + return new ObjectType(DateInterval::class); } } From 93e479e96f556e784c65a834511644562c7bb57d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=B2=A0=5F=E0=B2=A0?= Date: Thu, 15 Dec 2022 08:02:55 +0100 Subject: [PATCH 10/14] improve implementation --- ...DateIntervalDynamicReturnTypeExtension.php | 30 +++++++++---------- tests/PHPStan/Analyser/data/bug-8442.php | 8 +++++ 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php b/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php index c852be3f88..e2de43a201 100644 --- a/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php +++ b/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php @@ -7,13 +7,16 @@ use PHPStan\Analyser\Scope; use PHPStan\Reflection\MethodReflection; use PHPStan\Type\Constant\ConstantBooleanType; +use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\DynamicStaticMethodReturnTypeExtension; use PHPStan\Type\ObjectType; use PHPStan\Type\Type; -use function count; +use function array_map; +use function gettype; +use function in_array; class DateIntervalDynamicReturnTypeExtension - implements DynamicStaticMethodReturnTypeExtension +implements DynamicStaticMethodReturnTypeExtension { public function getClass(): string @@ -28,24 +31,19 @@ public function isStaticMethodSupported(MethodReflection $methodReflection): boo public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): ?Type { - $strings = $scope->getType($methodCall->getArgs()[0 ]->value)->getConstantStrings(); + $strings = $scope->getType($methodCall->getArgs()[0]->value)->getConstantStrings(); - if ( $strings === []) { - return null; - } + $possibleReturnTypes = array_map( + static fn (ConstantStringType $s): string => gettype(@DateInterval::createFromDateString($s->getValue())), + $strings, + ); - if ( count($strings) === 1) { - if ( DateInterval::createFromDateString($strings[0 ]->getValue()) === false) { - return new ConstantBooleanType(false); - } - - return new ObjectType(DateInterval::class); + if (in_array('boolean', $possibleReturnTypes, true) && in_array('object', $possibleReturnTypes, true)) { + return null; } - foreach ($strings as $string) { - if ( DateInterval::createFromDateString($string->getValue()) === false) { - return null; - } + if (in_array('boolean', $possibleReturnTypes, true)) { + return new ConstantBooleanType(false); } return new ObjectType(DateInterval::class); diff --git a/tests/PHPStan/Analyser/data/bug-8442.php b/tests/PHPStan/Analyser/data/bug-8442.php index f3733a03ad..f0c23c7000 100644 --- a/tests/PHPStan/Analyser/data/bug-8442.php +++ b/tests/PHPStan/Analyser/data/bug-8442.php @@ -24,5 +24,13 @@ function () { } assertType('DateInterval|false', DateInterval::createFromDateString($interval)); + + if (rand(0,1)) { + $interval = 'foo'; + } else { + $interval = 'foo'; + } + + assertType('false', DateInterval::createFromDateString($interval)); }; From e0aa795b84963ace5ba0127bbdc49e5e290e04fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=B2=A0=5F=E0=B2=A0?= Date: Thu, 15 Dec 2022 08:10:18 +0100 Subject: [PATCH 11/14] better handling of having arguments provided --- src/Type/Php/DateIntervalDynamicReturnTypeExtension.php | 8 +++++++- tests/PHPStan/Analyser/data/bug-8442.php | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php b/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php index e2de43a201..3e1fd40823 100644 --- a/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php +++ b/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php @@ -31,7 +31,13 @@ public function isStaticMethodSupported(MethodReflection $methodReflection): boo public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): ?Type { - $strings = $scope->getType($methodCall->getArgs()[0]->value)->getConstantStrings(); + $arguments = $methodCall->getArgs(); + + if (!isset($arguments[0])) { + return null; + } + + $strings = $scope->getType($arguments[0]->value)->getConstantStrings(); $possibleReturnTypes = array_map( static fn (ConstantStringType $s): string => gettype(@DateInterval::createFromDateString($s->getValue())), diff --git a/tests/PHPStan/Analyser/data/bug-8442.php b/tests/PHPStan/Analyser/data/bug-8442.php index f0c23c7000..e35ac0547f 100644 --- a/tests/PHPStan/Analyser/data/bug-8442.php +++ b/tests/PHPStan/Analyser/data/bug-8442.php @@ -32,5 +32,7 @@ function () { } assertType('false', DateInterval::createFromDateString($interval)); + + DateInterval::createFromDateString(); }; From f1aa6dd248982bb2ec2106a719a63b1afe5ca5cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=B2=A0=5F=E0=B2=A0?= Date: Thu, 15 Dec 2022 08:28:23 +0100 Subject: [PATCH 12/14] better handling of having wrong argumenttypes provided --- src/Type/Php/DateIntervalDynamicReturnTypeExtension.php | 6 ++++++ tests/PHPStan/Analyser/data/bug-8442.php | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php b/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php index 3e1fd40823..90842b37a0 100644 --- a/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php +++ b/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php @@ -12,6 +12,7 @@ use PHPStan\Type\ObjectType; use PHPStan\Type\Type; use function array_map; +use function count; use function gettype; use function in_array; @@ -44,6 +45,11 @@ public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, $strings, ); + // the error case, when wrong types are passed + if (count($possibleReturnTypes) === 0) { + return null; + } + if (in_array('boolean', $possibleReturnTypes, true) && in_array('object', $possibleReturnTypes, true)) { return null; } diff --git a/tests/PHPStan/Analyser/data/bug-8442.php b/tests/PHPStan/Analyser/data/bug-8442.php index e35ac0547f..64ed0e6653 100644 --- a/tests/PHPStan/Analyser/data/bug-8442.php +++ b/tests/PHPStan/Analyser/data/bug-8442.php @@ -2,6 +2,7 @@ namespace Bug8442; +use stdClass; use function PHPStan\Testing\assertType; use DateInterval; @@ -33,6 +34,7 @@ function () { assertType('false', DateInterval::createFromDateString($interval)); - DateInterval::createFromDateString(); + assertType('DateInterval|false',DateInterval::createFromDateString()); + assertType('DateInterval|false',DateInterval::createFromDateString(new stdClass())); }; From 4ef03b5fef3817145023d5a0460f062a58d74677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=B2=A0=5F=E0=B2=A0?= Date: Thu, 15 Dec 2022 08:48:31 +0100 Subject: [PATCH 13/14] typo --- src/Type/Php/DateIntervalDynamicReturnTypeExtension.php | 2 +- tests/PHPStan/Analyser/data/bug-8442.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php b/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php index 90842b37a0..a59b975746 100644 --- a/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php +++ b/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php @@ -1,4 +1,4 @@ - Date: Sat, 17 Dec 2022 22:32:42 +0100 Subject: [PATCH 14/14] simplify code --- .../DateIntervalDynamicReturnTypeExtension.php | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php b/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php index a59b975746..aa9dc2c1d5 100644 --- a/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php +++ b/src/Type/Php/DateIntervalDynamicReturnTypeExtension.php @@ -7,13 +7,10 @@ use PHPStan\Analyser\Scope; use PHPStan\Reflection\MethodReflection; use PHPStan\Type\Constant\ConstantBooleanType; -use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\DynamicStaticMethodReturnTypeExtension; use PHPStan\Type\ObjectType; use PHPStan\Type\Type; -use function array_map; use function count; -use function gettype; use function in_array; class DateIntervalDynamicReturnTypeExtension @@ -40,21 +37,21 @@ public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, $strings = $scope->getType($arguments[0]->value)->getConstantStrings(); - $possibleReturnTypes = array_map( - static fn (ConstantStringType $s): string => gettype(@DateInterval::createFromDateString($s->getValue())), - $strings, - ); + $possibleReturnTypes = []; + foreach ($strings as $string) { + $possibleReturnTypes[] = @DateInterval::createFromDateString($string->getValue()) instanceof DateInterval ? DateInterval::class : false; + } // the error case, when wrong types are passed if (count($possibleReturnTypes) === 0) { return null; } - if (in_array('boolean', $possibleReturnTypes, true) && in_array('object', $possibleReturnTypes, true)) { + if (in_array(false, $possibleReturnTypes, true) && in_array(DateInterval::class, $possibleReturnTypes, true)) { return null; } - if (in_array('boolean', $possibleReturnTypes, true)) { + if (in_array(false, $possibleReturnTypes, true)) { return new ConstantBooleanType(false); }