From d01431c5c2c72a450335277cc443aee095d1df3a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 15 Mar 2021 16:54:14 +0100 Subject: [PATCH] Regression tests Closes https://github.com/phpstan/phpstan/issues/2231 Closes https://github.com/phpstan/phpstan/issues/3558 Closes https://github.com/phpstan/phpstan/issues/4671 Closes https://github.com/phpstan/phpstan/issues/3351 Closes https://github.com/phpstan/phpstan/issues/4648 Closes https://github.com/phpstan/phpstan/issues/4213 Closes https://github.com/phpstan/phpstan/issues/3523 Closes https://github.com/phpstan/phpstan/issues/3120 Closes https://github.com/phpstan/phpstan/issues/1652 Closes https://github.com/phpstan/phpstan/issues/1843 --- src/Analyser/MutatingScope.php | 12 +++- src/Type/StaticType.php | 8 ++- .../Analyser/AnalyserIntegrationTest.php | 6 ++ .../Analyser/NodeScopeResolverTest.php | 24 +++++++ tests/PHPStan/Analyser/data/bug-1843.php | 24 +++++++ tests/PHPStan/Analyser/data/bug-2231.php | 39 +++++++++++ tests/PHPStan/Analyser/data/bug-3351.php | 30 +++++++++ tests/PHPStan/Analyser/data/bug-3558.php | 33 ++++++++++ tests/PHPStan/Analyser/data/bug-4213.php | 64 +++++++++++++++++++ tests/PHPStan/Reflection/StaticTypeTest.php | 28 -------- .../BooleanAndConstantConditionRuleTest.php | 11 ++++ .../Rules/Methods/MethodSignatureRuleTest.php | 7 ++ .../Rules/Methods/ReturnTypeRuleTest.php | 15 +++++ tests/PHPStan/Rules/Methods/data/bug-3120.php | 42 ++++++++++++ tests/PHPStan/Rules/Methods/data/bug-3523.php | 22 +++++++ tests/PHPStan/Rules/Methods/data/bug-4648.php | 27 ++++++++ .../PHPStan/Rules/Variables/IssetRuleTest.php | 8 +++ .../PHPStan/Rules/Variables/data/bug-4671.php | 17 +++++ tests/PHPStan/Type/StaticTypeTest.php | 23 +++++++ tests/PHPStan/Type/data/static-type-test.php | 18 ++++++ 20 files changed, 427 insertions(+), 31 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/bug-1843.php create mode 100644 tests/PHPStan/Analyser/data/bug-2231.php create mode 100644 tests/PHPStan/Analyser/data/bug-3351.php create mode 100644 tests/PHPStan/Analyser/data/bug-3558.php create mode 100644 tests/PHPStan/Analyser/data/bug-4213.php delete mode 100644 tests/PHPStan/Reflection/StaticTypeTest.php create mode 100644 tests/PHPStan/Rules/Methods/data/bug-3120.php create mode 100644 tests/PHPStan/Rules/Methods/data/bug-3523.php create mode 100644 tests/PHPStan/Rules/Methods/data/bug-4648.php create mode 100644 tests/PHPStan/Rules/Variables/data/bug-4671.php create mode 100644 tests/PHPStan/Type/data/static-type-test.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 830f628030..92b84d33ab 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -854,8 +854,16 @@ private function resolveType(Expr $node): Type $uncertainty = false; if ($node->class instanceof Node\Name) { - $className = $this->resolveName($node->class); - $classType = new ObjectType($className); + $unresolvedClassName = $node->class->toString(); + if ( + strtolower($unresolvedClassName) === 'static' + && $this->isInClass() + ) { + $classType = new StaticType($this->getClassReflection()); + } else { + $className = $this->resolveName($node->class); + $classType = new ObjectType($className); + } } else { $classType = $this->getType($node->class); $classType = TypeTraverser::map($classType, static function (Type $type, callable $traverse) use (&$uncertainty): Type { diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php index 82b7fb9fda..d4078ae593 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -129,7 +129,13 @@ public function isSuperTypeOf(Type $type): TrinaryLogic } if ($type instanceof ObjectType) { - return TrinaryLogic::createMaybe()->and($this->getStaticObjectType()->isSuperTypeOf($type)); + $result = $this->getStaticObjectType()->isSuperTypeOf($type); + $classReflection = $type->getClassReflection(); + if ($result->yes() && $classReflection !== null && $classReflection->isFinal()) { + return $result; + } + + return TrinaryLogic::createMaybe()->and($result); } if ($type instanceof CompoundType) { diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 3a53f4e223..ac2e05306e 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -330,6 +330,12 @@ public function testBug3922(): void $this->assertCount(0, $errors); } + public function testBug1843(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-1843.php'); + $this->assertCount(0, $errors); + } + /** * @param string $file * @return \PHPStan\Analyser\Error[] diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index aa36f9484e..67e16bb2ce 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -5666,6 +5666,26 @@ public function dataBug4267(): array return $this->gatherAssertTypes(__DIR__ . '/data/bug-4267.php'); } + public function dataBug2231(): array + { + return $this->gatherAssertTypes(__DIR__ . '/data/bug-2231.php'); + } + + public function dataBug3558(): array + { + return $this->gatherAssertTypes(__DIR__ . '/data/bug-3558.php'); + } + + public function dataBug3351(): array + { + return $this->gatherAssertTypes(__DIR__ . '/data/bug-3351.php'); + } + + public function dataBug4213(): array + { + return $this->gatherAssertTypes(__DIR__ . '/data/bug-4213.php'); + } + /** * @dataProvider dataArrayFunctions * @param string $description @@ -11282,6 +11302,10 @@ private function gatherAssertTypes(string $file): array * @dataProvider dataGenericParent * @dataProvider dataBug4247 * @dataProvider dataBug4267 + * @dataProvider dataBug2231 + * @dataProvider dataBug3558 + * @dataProvider dataBug3351 + * @dataProvider dataBug4213 * @param string $assertType * @param string $file * @param mixed ...$args diff --git a/tests/PHPStan/Analyser/data/bug-1843.php b/tests/PHPStan/Analyser/data/bug-1843.php new file mode 100644 index 0000000000..7e2b1f201b --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-1843.php @@ -0,0 +1,24 @@ + [ + 'A' => '2', + 'B' => '3', + 'C' => '4', + 'D' => '5', + 'E' => '6', + 'F' => '7', + ], + ]; + + public function sayHello(): void + { + echo self::P[self::W]['A']; + } +} diff --git a/tests/PHPStan/Analyser/data/bug-2231.php b/tests/PHPStan/Analyser/data/bug-2231.php new file mode 100644 index 0000000000..b1deb0d6fc --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-2231.php @@ -0,0 +1,39 @@ +combine($a, $b); + assertType('array|false', $c); + + assertType('array(\'a\' => 1, \'b\' => 2, \'c\' => 3)', array_combine($a, $b)); + } + + /** + * @template TKey + * @template TValue + * @param array $keys + * @param array $values + * + * @return array|false + */ + private function combine(array $keys, array $values) + { + return array_combine($keys, $values); + } +} diff --git a/tests/PHPStan/Analyser/data/bug-3558.php b/tests/PHPStan/Analyser/data/bug-3558.php new file mode 100644 index 0000000000..178de4eb37 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-3558.php @@ -0,0 +1,33 @@ + 3){ + $idGroups[] = [1,2]; + $idGroups[] = [1,2]; + $idGroups[] = [1,2]; + } + + if(count($idGroups) > 0){ + assertType('array(array(1, 2), array(1, 2), array(1, 2))', $idGroups); + } +}; + +function (): void { + $idGroups = [1]; + + if(time() > 3){ + $idGroups[] = [1,2]; + $idGroups[] = [1,2]; + $idGroups[] = [1,2]; + } + + if(count($idGroups) > 1){ + assertType('array(0 => 1, ?1 => array(1, 2), ?2 => array(1, 2), ?3 => array(1, 2))', $idGroups); + } +}; diff --git a/tests/PHPStan/Analyser/data/bug-4213.php b/tests/PHPStan/Analyser/data/bug-4213.php new file mode 100644 index 0000000000..8cbfc39c21 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-4213.php @@ -0,0 +1,64 @@ +value = $value; + } + /** + * @return static + */ + public static function get(string $value): self { + return new static($value); + } +} + +final class Enum extends BaseEnum +{ +} + +final class Entity { + public function setEnums(Enum ...$enums): void { + } + /** + * @param Enum[] $enums + */ + public function setEnumsWithoutSplat(array $enums): void { + } +} + +function (): void { + assertType('Bug4213\Enum', Enum::get('test')); + assertType('array(Bug4213\Enum)', array_map([Enum::class, 'get'], ['test'])); +}; + + +class Foo +{ + /** + * @return static + */ + public static function create() : Foo + { + return new static(); + } +} + + +class Bar extends Foo +{ +} + +function (): void { + $cbFoo = [Foo::class, 'create']; + $cbBar = [Bar::class, 'create']; + assertType('Bug4213\Foo', $cbFoo()); + assertType('Bug4213\Bar', $cbBar()); +}; diff --git a/tests/PHPStan/Reflection/StaticTypeTest.php b/tests/PHPStan/Reflection/StaticTypeTest.php deleted file mode 100644 index e4badb9f86..0000000000 --- a/tests/PHPStan/Reflection/StaticTypeTest.php +++ /dev/null @@ -1,28 +0,0 @@ -markTestSkipped('Test requires PHP 8.0'); - } - - $reflectionProvider = $this->createBroker(); - $class = $reflectionProvider->getClass(Foo::class); - - $method = $class->getNativeMethod('doFoo'); - $methodVariant = ParametersAcceptorSelector::selectSingle($method->getVariants()); - $methodReturnType = $methodVariant->getReturnType(); - $this->assertInstanceOf(StaticType::class, $methodReturnType); - $this->assertSame(Foo::class, $methodReturnType->getClassName()); - } - -} diff --git a/tests/PHPStan/Rules/Comparison/BooleanAndConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/BooleanAndConstantConditionRuleTest.php index 1887e70627..26c45e4313 100644 --- a/tests/PHPStan/Rules/Comparison/BooleanAndConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/BooleanAndConstantConditionRuleTest.php @@ -190,4 +190,15 @@ public function testBugComposerDependentVariables(): void $this->analyse([__DIR__ . '/data/bug-composer-dependent-variables.php'], []); } + public function testBug2231(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/../../Analyser/data/bug-2231.php'], [ + [ + 'Result of && is always false.', + 21, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php b/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php index 16b194d772..13ab858451 100644 --- a/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php @@ -290,4 +290,11 @@ public function testBug4084(): void $this->analyse([__DIR__ . '/data/bug-4084.php'], []); } + public function testBug3523(): void + { + $this->reportMaybes = true; + $this->reportStatic = true; + $this->analyse([__DIR__ . '/data/bug-3523.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index f32bd56951..c622bf311e 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -459,4 +459,19 @@ public function testReturnStatic(): void $this->analyse([__DIR__ . '/data/return-static.php'], []); } + public function testBug4648(): void + { + $this->analyse([__DIR__ . '/data/bug-4648.php'], []); + } + + public function testBug3523(): void + { + $this->analyse([__DIR__ . '/data/bug-3523.php'], []); + } + + public function testBug3120(): void + { + $this->analyse([__DIR__ . '/data/bug-3120.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-3120.php b/tests/PHPStan/Rules/Methods/data/bug-3120.php new file mode 100644 index 0000000000..41039c47aa --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-3120.php @@ -0,0 +1,42 @@ +analyse([__DIR__ . '/data/bug-4290.php'], []); } + public function testBug4671(): void + { + $this->analyse([__DIR__ . '/data/bug-4671.php'], [[ + 'Offset string&numeric on array in isset() does not exist.', + 13, + ]]); + } + } diff --git a/tests/PHPStan/Rules/Variables/data/bug-4671.php b/tests/PHPStan/Rules/Variables/data/bug-4671.php new file mode 100644 index 0000000000..beba71f7a5 --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/bug-4671.php @@ -0,0 +1,17 @@ + $strings + */ + public function doFoo(int $intput, array $strings): void + { + if (isset($strings[(string) $intput])) { + } + } + +} diff --git a/tests/PHPStan/Type/StaticTypeTest.php b/tests/PHPStan/Type/StaticTypeTest.php index 01bd796306..993b9c74c2 100644 --- a/tests/PHPStan/Type/StaticTypeTest.php +++ b/tests/PHPStan/Type/StaticTypeTest.php @@ -4,6 +4,9 @@ use PHPStan\Broker\Broker; use PHPStan\TrinaryLogic; +use StaticTypeTest\Base; +use StaticTypeTest\Child; +use StaticTypeTest\FinalChild; class StaticTypeTest extends \PHPStan\Testing\TestCase { @@ -226,6 +229,26 @@ public function dataIsSuperTypeOf(): array new ThisType($broker->getClass(\stdClass::class)), TrinaryLogic::createYes(), ], + [ + new StaticType(Base::class), + new ObjectType(Child::class), + TrinaryLogic::createMaybe(), + ], + [ + new StaticType(Base::class), + new StaticType(FinalChild::class), + TrinaryLogic::createYes(), + ], + [ + new StaticType(Base::class), + new StaticType(Child::class), + TrinaryLogic::createYes(), + ], + [ + new StaticType(Base::class), + new ObjectType(FinalChild::class), + TrinaryLogic::createYes(), + ], ]; } diff --git a/tests/PHPStan/Type/data/static-type-test.php b/tests/PHPStan/Type/data/static-type-test.php new file mode 100644 index 0000000000..76a67956ec --- /dev/null +++ b/tests/PHPStan/Type/data/static-type-test.php @@ -0,0 +1,18 @@ +