From 332f6b4fcd81f6267fb70b7d81d8b6ef5751b16b Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 15 Dec 2022 16:25:43 +0100 Subject: [PATCH] Keep NeverType isExplicit-flag in InitializerExprTypeResolver --- .../InitializerExprTypeResolver.php | 30 +++-- .../Analyser/AnalyserIntegrationTest.php | 116 ++++++++++++++++++ 2 files changed, 137 insertions(+), 9 deletions(-) diff --git a/src/Reflection/InitializerExprTypeResolver.php b/src/Reflection/InitializerExprTypeResolver.php index 0d56aeeb3fd..54294ce053f 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -498,7 +498,7 @@ public function getBitwiseAndType(Expr $left, Expr $right, callable $getTypeCall $rightType = $getTypeCallback($right); if ($leftType instanceof NeverType || $rightType instanceof NeverType) { - return new NeverType(); + return $this->getNeverType($leftType, $rightType); } $leftTypes = TypeUtils::getConstantScalars($leftType); @@ -565,7 +565,7 @@ public function getBitwiseOrType(Expr $left, Expr $right, callable $getTypeCallb $rightType = $getTypeCallback($right); if ($leftType instanceof NeverType || $rightType instanceof NeverType) { - return new NeverType(); + return $this->getNeverType($leftType, $rightType); } $leftTypes = TypeUtils::getConstantScalars($leftType); @@ -622,7 +622,7 @@ public function getBitwiseXorType(Expr $left, Expr $right, callable $getTypeCall $rightType = $getTypeCallback($right); if ($leftType instanceof NeverType || $rightType instanceof NeverType) { - return new NeverType(); + return $this->getNeverType($leftType, $rightType); } $leftTypes = TypeUtils::getConstantScalars($leftType); @@ -679,7 +679,7 @@ public function getSpaceshipType(Expr $left, Expr $right, callable $getTypeCallb $callbackRightType = $getTypeCallback($right); if ($callbackLeftType instanceof NeverType || $callbackRightType instanceof NeverType) { - return new NeverType(); + return $this->getNeverType($callbackLeftType, $callbackRightType); } $leftTypes = TypeUtils::getConstantScalars($callbackLeftType); @@ -772,7 +772,7 @@ public function getModType(Expr $left, Expr $right, callable $getTypeCallback): $rightType = $getTypeCallback($right); if ($leftType instanceof NeverType || $rightType instanceof NeverType) { - return new NeverType(); + return $this->getNeverType($leftType, $rightType); } $leftTypes = TypeUtils::getConstantScalars($leftType); @@ -873,7 +873,7 @@ public function getPlusType(Expr $left, Expr $right, callable $getTypeCallback): $rightType = $getTypeCallback($right); if ($leftType instanceof NeverType || $rightType instanceof NeverType) { - return new NeverType(); + return $this->getNeverType($leftType, $rightType); } $leftTypes = TypeUtils::getConstantScalars($leftType); @@ -1192,7 +1192,7 @@ public function getShiftLeftType(Expr $left, Expr $right, callable $getTypeCallb $rightType = $getTypeCallback($right); if ($leftType instanceof NeverType || $rightType instanceof NeverType) { - return new NeverType(); + return $this->getNeverType($leftType, $rightType); } $leftTypes = TypeUtils::getConstantScalars($leftType); @@ -1249,7 +1249,7 @@ public function getShiftRightType(Expr $left, Expr $right, callable $getTypeCall $rightType = $getTypeCallback($right); if ($leftType instanceof NeverType || $rightType instanceof NeverType) { - return new NeverType(); + return $this->getNeverType($leftType, $rightType); } $leftTypes = TypeUtils::getConstantScalars($leftType); @@ -1488,7 +1488,7 @@ private function resolveCommonMath(Expr\BinaryOp $expr, Type $leftType, Type $ri return new ErrorType(); } if ($leftNumberType instanceof NeverType || $rightNumberType instanceof NeverType) { - return new NeverType(); + return $this->getNeverType($leftNumberType, $rightNumberType); } if ( @@ -1982,4 +1982,16 @@ private function getReflectionProvider(): ReflectionProvider return $this->reflectionProviderProvider->getReflectionProvider(); } + private function getNeverType(Type $leftType, Type $rightType): Type + { + // make sure we don't lose the explicit flag in the process + if ($leftType instanceof NeverType && $leftType->isExplicit()) { + return $leftType; + } + if ($rightType instanceof NeverType && $rightType->isExplicit()) { + return $rightType; + } + return new NeverType(); + } + } diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 85fa14a9ca3..1068ec506d0 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -5,14 +5,20 @@ use Bug4288\MyClass; use Bug4713\Service; use ExtendingKnownClassWithCheck\Foo; +use PhpParser\Node\Expr; +use PhpParser\Node\Scalar\LNumber; +use PhpParser\Node\Scalar\String_; use PHPStan\File\FileHelper; use PHPStan\Reflection\InitializerExprContext; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Reflection\SignatureMap\SignatureMapProvider; +use PHPStan\ShouldNotHappenException; use PHPStan\Testing\PHPStanTestCase; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; +use PHPStan\Type\NeverType; +use PHPStan\Type\Type; use function extension_loaded; use function restore_error_handler; use function sprintf; @@ -1075,6 +1081,116 @@ public function testBug8503(): void $this->assertNoErrors($errors); } + /** + * @dataProvider dataExplicitNever + * + * @param class-string $resultClass + * @param callable(Expr): Type $callback + */ + public function testExplicitNever(Expr $left, Expr $right, callable $callback, string $resultClass, ?bool $resultIsExplicit = null): void + { + $initializerExprTypeResolver = self::getContainer()->getByType(InitializerExprTypeResolver::class); + + $result = $initializerExprTypeResolver->getPlusType( + $left, + $right, + $callback, + ); + $this->assertInstanceOf($resultClass, $result); + + if ($result instanceof NeverType) { + if ($resultIsExplicit === null) { + throw new ShouldNotHappenException(); + } + $this->assertSame($resultIsExplicit, $result->isExplicit()); + } + } + + public function dataExplicitNever(): iterable + { + yield [ + new LNumber(1), + new String_('foo'), + static function (Expr $expr): Type { + if ($expr instanceof LNumber) { + return new ConstantIntegerType(1); + } + return new NeverType(true); + }, + NeverType::class, + true, + ]; + yield [ + new String_('foo'), + new LNumber(1), + static function (Expr $expr): Type { + if ($expr instanceof LNumber) { + return new ConstantIntegerType(1); + } + return new NeverType(true); + }, + NeverType::class, + true, + ]; + + yield [ + new LNumber(1), + new String_('foo'), + static function (Expr $expr): Type { + if ($expr instanceof LNumber) { + return new ConstantIntegerType(1); + } + return new NeverType(false); + }, + NeverType::class, + false, + ]; + yield [ + new String_('foo'), + new LNumber(1), + static function (Expr $expr): Type { + if ($expr instanceof LNumber) { + return new ConstantIntegerType(1); + } + return new NeverType(false); + }, + NeverType::class, + false, + ]; + + yield [ + new String_('foo'), + new LNumber(1), + static function (Expr $expr): Type { + if ($expr instanceof LNumber) { + return new NeverType(true); + } + return new NeverType(false); + }, + NeverType::class, + true, + ]; + yield [ + new LNumber(1), + new String_('foo'), + static function (Expr $expr): Type { + if ($expr instanceof LNumber) { + return new NeverType(true); + } + return new NeverType(false); + }, + NeverType::class, + true, + ]; + + yield [ + new LNumber(1), + new LNumber(1), + static fn (Expr $expr): Type => new ConstantIntegerType(1), + ConstantIntegerType::class, + ]; + } + /** * @param string[]|null $allAnalysedFiles * @return Error[]