From d675602d1eae1cfae1c189eb5b14a6978c96b7df Mon Sep 17 00:00:00 2001 From: Artem Goutsoul Date: Fri, 2 Jul 2021 10:56:35 +0300 Subject: [PATCH 0001/1284] Fix for: Optional array key is not recognized with !empty --- src/Analyser/TypeSpecifier.php | 34 +++++++++---------- ...nexistentOffsetInArrayDimFetchRuleTest.php | 5 +++ tests/PHPStan/Rules/Arrays/data/bug-3297.php | 14 ++++++++ 3 files changed, 36 insertions(+), 17 deletions(-) create mode 100644 tests/PHPStan/Rules/Arrays/data/bug-3297.php diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 547b4f27ef..41efe48747 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -682,23 +682,7 @@ public function specifyTypesInCondition( } } if ($expr instanceof Expr\Isset_) { - if ( - $var instanceof ArrayDimFetch - && $var->dim !== null - && !$scope->getType($var->var) instanceof MixedType - ) { - $type = $this->create( - $var->var, - new HasOffsetType($scope->getType($var->dim)), - $context, - false, - $scope - )->unionWith( - $this->create($var, new NullType(), TypeSpecifierContext::createFalse(), false, $scope) - ); - } else { - $type = $this->create($var, new NullType(), TypeSpecifierContext::createFalse(), false, $scope); - } + $type = $this->create($var, new NullType(), TypeSpecifierContext::createFalse(), false, $scope); } else { $type = $this->create( $var, @@ -712,6 +696,22 @@ public function specifyTypesInCondition( ); } + if ( + $var instanceof ArrayDimFetch + && $var->dim !== null + && !$scope->getType($var->var) instanceof MixedType + ) { + $type = $this->create( + $var->var, + new HasOffsetType($scope->getType($var->dim)), + $context, + false, + $scope + )->unionWith( + $type + ); + } + if ( $var instanceof PropertyFetch && $var->name instanceof Node\Identifier diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index 31fcb1c1fa..9cc70f326b 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -278,4 +278,9 @@ public function testBug5169(): void ]); } + public function testBug3297(): void + { + $this->analyse([__DIR__ . '/data/bug-3297.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Arrays/data/bug-3297.php b/tests/PHPStan/Rules/Arrays/data/bug-3297.php new file mode 100644 index 0000000000..d35f67fc3a --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/bug-3297.php @@ -0,0 +1,14 @@ + Date: Sat, 3 Jul 2021 16:33:34 +0200 Subject: [PATCH 0002/1284] next() dynamic return type extension --- conf/config.neon | 5 ++ .../ArrayNextDynamicReturnTypeExtension.php | 38 +++++++++ .../Analyser/NodeScopeResolverTest.php | 1 + tests/PHPStan/Analyser/data/array-next.php | 77 +++++++++++++++++++ .../Rules/Methods/CallMethodsRuleTest.php | 8 ++ tests/PHPStan/Rules/Methods/data/bug-5253.php | 40 ++++++++++ 6 files changed, 169 insertions(+) create mode 100644 src/Type/Php/ArrayNextDynamicReturnTypeExtension.php create mode 100644 tests/PHPStan/Analyser/data/array-next.php create mode 100644 tests/PHPStan/Rules/Methods/data/bug-5253.php diff --git a/conf/config.neon b/conf/config.neon index 6ae19bb2db..88022bc064 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -953,6 +953,11 @@ services: tags: - phpstan.broker.dynamicFunctionReturnTypeExtension + - + class: PHPStan\Type\Php\ArrayNextDynamicReturnTypeExtension + tags: + - phpstan.broker.dynamicFunctionReturnTypeExtension + - class: PHPStan\Type\Php\ArrayPopFunctionReturnTypeExtension tags: diff --git a/src/Type/Php/ArrayNextDynamicReturnTypeExtension.php b/src/Type/Php/ArrayNextDynamicReturnTypeExtension.php new file mode 100644 index 0000000000..48645d9cde --- /dev/null +++ b/src/Type/Php/ArrayNextDynamicReturnTypeExtension.php @@ -0,0 +1,38 @@ +getName() === 'next'; + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + if (!isset($functionCall->args[0])) { + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } + + $argType = $scope->getType($functionCall->args[0]->value); + $iterableAtLeastOnce = $argType->isIterableAtLeastOnce(); + if ($iterableAtLeastOnce->no()) { + return new ConstantBooleanType(false); + } + + $valueType = $argType->getIterableValueType(); + + return TypeCombinator::union($valueType, new ConstantBooleanType(false)); + } + +} diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 5bfb578ccf..f993bdc58d 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -431,6 +431,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/closure-types.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5219.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/strval.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/array-next.php'); } /** diff --git a/tests/PHPStan/Analyser/data/array-next.php b/tests/PHPStan/Analyser/data/array-next.php new file mode 100644 index 0000000000..c1b0a1ecf9 --- /dev/null +++ b/tests/PHPStan/Analyser/data/array-next.php @@ -0,0 +1,77 @@ + $a + */ + public function doBaz(array $a) + { + assertType('string|false', next($a)); + } + +} + +interface HttpClientPoolItem +{ + public function isDisabled(): bool; +} + +final class RoundRobinClientPool +{ + /** + * @var HttpClientPoolItem[] + */ + protected $clientPool = []; + + protected function chooseHttpClient(): HttpClientPoolItem + { + $last = current($this->clientPool); + assertType(HttpClientPoolItem::class . '|false', $last); + + do { + $client = next($this->clientPool); + assertType(HttpClientPoolItem::class . '|false', $client); + + if (false === $client) { + $client = reset($this->clientPool); + assertType(HttpClientPoolItem::class . '|false', $client); + + if (false === $client) { + throw new \Exception(); + } + + assertType(HttpClientPoolItem::class, $client); + } + + assertType(HttpClientPoolItem::class, $client); + + // Case when there is only one and the last one has been disabled + if ($last === $client) { + assertType(HttpClientPoolItem::class, $client); + throw new \Exception(); + } + } while ($client->isDisabled()); + + return $client; + } +} diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index c4c98e929b..b21088473a 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -1971,4 +1971,12 @@ public function testBug4083(): void $this->analyse([__DIR__ . '/data/bug-4083.php'], []); } + public function testBug5253(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/bug-5253.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-5253.php b/tests/PHPStan/Rules/Methods/data/bug-5253.php new file mode 100644 index 0000000000..d34fd47189 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-5253.php @@ -0,0 +1,40 @@ +clientPool); + + do { + $client = next($this->clientPool); + + if (false === $client) { + $client = reset($this->clientPool); + + if (false === $client) { + throw new \Exception(); + } + } + + // Case when there is only one and the last one has been disabled + if ($last === $client && $client->isDisabled()) { + throw new \Exception(); + } + } while ($client->isDisabled()); + + return $client; + } +} From e42ae89604985a3861e4a62c1d932374e5f72956 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 3 Jul 2021 16:43:09 +0200 Subject: [PATCH 0003/1284] Update nikic/php-parser --- composer.json | 4 ++-- composer.lock | 28 ++++++++++++++-------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/composer.json b/composer.json index 22f869022e..ed82fc90d8 100644 --- a/composer.json +++ b/composer.json @@ -21,9 +21,9 @@ "nette/neon": "^3.0", "nette/schema": "^1.0", "nette/utils": "^3.1.3", - "nikic/php-parser": "4.10.5", + "nikic/php-parser": "4.11.0", "ondram/ci-detector": "^3.4.0", - "ondrejmirtes/better-reflection": "4.3.60", + "ondrejmirtes/better-reflection": "4.3.61", "phpstan/php-8-stubs": "^0.1.21", "phpstan/phpdoc-parser": "^0.5.5", "react/child-process": "^0.6.1", diff --git a/composer.lock b/composer.lock index d6f0ca8b96..843fa4c4df 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "232996096032e099e1ea42f579ec050d", + "content-hash": "8935821e0ae449b7a35345d12e270aa5", "packages": [ { "name": "clue/block-react", @@ -1946,16 +1946,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.10.5", + "version": "v4.11.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "4432ba399e47c66624bc73c8c0f811e5c109576f" + "reference": "fe14cf3672a149364fb66dfe11bf6549af899f94" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4432ba399e47c66624bc73c8c0f811e5c109576f", - "reference": "4432ba399e47c66624bc73c8c0f811e5c109576f", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/fe14cf3672a149364fb66dfe11bf6549af899f94", + "reference": "fe14cf3672a149364fb66dfe11bf6549af899f94", "shasum": "" }, "require": { @@ -1996,9 +1996,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.10.5" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.11.0" }, - "time": "2021-05-03T19:11:20+00:00" + "time": "2021-07-03T13:36:55+00:00" }, { "name": "ondram/ci-detector", @@ -2074,22 +2074,22 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "4.3.60", + "version": "4.3.61", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "0a94d3041d5801da091d0ecce0ef4e077f83467a" + "reference": "d624985e59112ae000d9456dd261c857d284bdc6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/0a94d3041d5801da091d0ecce0ef4e077f83467a", - "reference": "0a94d3041d5801da091d0ecce0ef4e077f83467a", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/d624985e59112ae000d9456dd261c857d284bdc6", + "reference": "d624985e59112ae000d9456dd261c857d284bdc6", "shasum": "" }, "require": { "ext-json": "*", "jetbrains/phpstorm-stubs": "dev-master#0a73df114cdea7f30c8b5f6fbfbf8e6839a89e88", - "nikic/php-parser": "4.10.5", + "nikic/php-parser": "4.11.0", "php": ">=7.1.0" }, "require-dev": { @@ -2138,9 +2138,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/4.3.60" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/4.3.61" }, - "time": "2021-06-17T17:55:18+00:00" + "time": "2021-07-03T14:42:18+00:00" }, { "name": "phpstan/php-8-stubs", From 8c50d887d60cacf688cc9c6a61d490df16eaabb6 Mon Sep 17 00:00:00 2001 From: Jean-Luc Herren Date: Sat, 3 Jul 2021 19:25:18 +0200 Subject: [PATCH 0004/1284] Type cast fixes, add tests for intval(), boolval(), floatval() --- conf/config.neon | 2 +- src/Type/ArrayType.php | 11 +++- src/Type/Constant/ConstantArrayType.php | 10 +++ src/Type/Constant/ConstantStringType.php | 14 +---- ...rvalFamilyFunctionReturnTypeExtension.php} | 28 ++++++++- .../Analyser/LegacyNodeScopeResolverTest.php | 4 +- tests/PHPStan/Analyser/data/strval.php | 63 ++++++++++++++++++- tests/PHPStan/Levels/data/casts-2.json | 12 ---- tests/PHPStan/Levels/data/casts-7.json | 5 ++ .../Rules/Cast/InvalidCastRuleTest.php | 8 --- 10 files changed, 115 insertions(+), 42 deletions(-) rename src/Type/Php/{StrvalFunctionReturnTypeExtension.php => StrvalFamilyFunctionReturnTypeExtension.php} (51%) delete mode 100644 tests/PHPStan/Levels/data/casts-2.json diff --git a/conf/config.neon b/conf/config.neon index 88022bc064..593bc7dd7c 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1403,7 +1403,7 @@ services: - phpstan.broker.dynamicFunctionReturnTypeExtension - - class: PHPStan\Type\Php\StrvalFunctionReturnTypeExtension + class: PHPStan\Type\Php\StrvalFamilyFunctionReturnTypeExtension tags: - phpstan.broker.dynamicFunctionReturnTypeExtension diff --git a/src/Type/ArrayType.php b/src/Type/ArrayType.php index f298d805b5..3ee4c84393 100644 --- a/src/Type/ArrayType.php +++ b/src/Type/ArrayType.php @@ -7,6 +7,7 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\Constant\ConstantArrayType; +use PHPStan\Type\Constant\ConstantFloatType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\Generic\TemplateMixedType; @@ -266,12 +267,18 @@ public function toString(): Type public function toInteger(): Type { - return new ErrorType(); + return TypeCombinator::union( + new ConstantIntegerType(0), + new ConstantIntegerType(1) + ); } public function toFloat(): Type { - return new ErrorType(); + return TypeCombinator::union( + new ConstantFloatType(0.0), + new ConstantFloatType(1.0) + ); } public function toArray(): Type diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 7b0a6eb5ab..19c61f800f 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -608,6 +608,16 @@ public function toBoolean(): BooleanType return $this->count()->toBoolean(); } + public function toInteger(): Type + { + return $this->toBoolean()->toInteger(); + } + + public function toFloat(): Type + { + return $this->toBoolean()->toFloat(); + } + public function generalize(): Type { if (count($this->keyTypes) === 0) { diff --git a/src/Type/Constant/ConstantStringType.php b/src/Type/Constant/ConstantStringType.php index ceec668f56..7dbdde168e 100644 --- a/src/Type/Constant/ConstantStringType.php +++ b/src/Type/Constant/ConstantStringType.php @@ -226,22 +226,12 @@ public function toNumber(): Type public function toInteger(): Type { - $type = $this->toNumber(); - if ($type instanceof ErrorType) { - return $type; - } - - return $type->toInteger(); + return new ConstantIntegerType((int) $this->value); } public function toFloat(): Type { - $type = $this->toNumber(); - if ($type instanceof ErrorType) { - return $type; - } - - return $type->toFloat(); + return new ConstantFloatType((float) $this->value); } public function isNumericString(): TrinaryLogic diff --git a/src/Type/Php/StrvalFunctionReturnTypeExtension.php b/src/Type/Php/StrvalFamilyFunctionReturnTypeExtension.php similarity index 51% rename from src/Type/Php/StrvalFunctionReturnTypeExtension.php rename to src/Type/Php/StrvalFamilyFunctionReturnTypeExtension.php index 391003efcb..ffb7b8f5d5 100644 --- a/src/Type/Php/StrvalFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrvalFamilyFunctionReturnTypeExtension.php @@ -9,12 +9,20 @@ use PHPStan\Type\NullType; use PHPStan\Type\Type; -class StrvalFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension +class StrvalFamilyFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { + private const FUNCTIONS = [ + 'strval', + 'intval', + 'boolval', + 'floatval', + 'doubleval', + ]; + public function isFunctionSupported(FunctionReflection $functionReflection): bool { - return $functionReflection->getName() === 'strval'; + return in_array($functionReflection->getName(), self::FUNCTIONS, true); } public function getTypeFromFunctionCall( @@ -26,8 +34,22 @@ public function getTypeFromFunctionCall( if (count($functionCall->args) === 0) { return new NullType(); } + $argType = $scope->getType($functionCall->args[0]->value); - return $argType->toString(); + + switch ($functionReflection->getName()) { + case 'strval': + return $argType->toString(); + case 'intval': + return $argType->toInteger(); + case 'boolval': + return $argType->toBoolean(); + case 'floatval': + case 'doubleval': + return $argType->toFloat(); + default: + throw new \PHPStan\ShouldNotHappenException(); + } } } diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 9db0b9dc09..b4100a0ddb 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -1550,11 +1550,11 @@ public function dataCasts(): array '(float) "5"', ], [ - '*ERROR*', + '0', '(int) "blabla"', ], [ - '*ERROR*', + '0.0', '(float) "blabla"', ], [ diff --git a/tests/PHPStan/Analyser/data/strval.php b/tests/PHPStan/Analyser/data/strval.php index 04689cbcb8..1d186d55c2 100644 --- a/tests/PHPStan/Analyser/data/strval.php +++ b/tests/PHPStan/Analyser/data/strval.php @@ -1,19 +1,78 @@ $class */ -function test(string $class) +function strvalTest(string $string, string $class): void { + assertType('null', strval()); assertType('\'foo\'', strval('foo')); + assertType('string', strval($string)); assertType('\'\'', strval(null)); + assertType('\'\'', strval(false)); + assertType('\'1\'', strval(true)); assertType('\'\'|\'1\'', strval(rand(0, 1) === 0)); + assertType('\'42\'', strval(42)); assertType('string&numeric', strval(rand())); assertType('string&numeric', strval(rand() * 0.1)); assertType('string&numeric', strval(strval(rand()))); assertType('class-string', strval($class)); + assertType('string', strval(new \Exception())); + assertType('*ERROR*', strval(new \stdClass())); +} + +function intvalTest(string $string): void +{ + assertType('null', intval()); + assertType('42', intval('42')); + assertType('0', intval('foo')); + assertType('int', intval($string)); + assertType('0', intval(null)); + assertType('0', intval(false)); + assertType('1', intval(true)); + assertType('0|1', intval(rand(0, 1) === 0)); + assertType('42', intval(42)); + assertType('int', intval(rand())); + assertType('int', intval(rand() * 0.1)); + assertType('0', intval([])); + assertType('1', intval([null])); +} + +function floatvalTest(string $string): void +{ + assertType('null', floatval()); + assertType('3.14', floatval('3.14')); + assertType('0.0', floatval('foo')); + assertType('float', floatval($string)); + assertType('0.0', floatval(null)); + assertType('0.0', floatval(false)); + assertType('1.0', floatval(true)); + assertType('0.0|1.0', floatval(rand(0, 1) === 0)); + assertType('42.0', floatval(42)); + assertType('float', floatval(rand())); + assertType('float', floatval(rand() * 0.1)); + assertType('0.0', floatval([])); + assertType('1.0', floatval([null])); +} + +function boolvalTest(string $string): void +{ + assertType('null', boolval()); + assertType('false', boolval('')); + assertType('true', boolval('foo')); + assertType('bool', boolval($string)); + assertType('false', boolval(null)); + assertType('false', boolval(false)); + assertType('true', boolval(true)); + assertType('bool', boolval(rand(0, 1) === 0)); + assertType('true', boolval(42)); + assertType('bool', boolval(rand())); + assertType('bool', boolval(rand() * 0.1)); + assertType('false', boolval([])); + assertType('true', boolval([null])); + assertType('true', boolval(new \stdClass())); } diff --git a/tests/PHPStan/Levels/data/casts-2.json b/tests/PHPStan/Levels/data/casts-2.json deleted file mode 100644 index 3fef9a1842..0000000000 --- a/tests/PHPStan/Levels/data/casts-2.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - { - "message": "Cannot cast array to int.", - "line": 19, - "ignorable": true - }, - { - "message": "Cannot cast array|(callable(): mixed) to int.", - "line": 20, - "ignorable": true - } -] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/casts-7.json b/tests/PHPStan/Levels/data/casts-7.json index 40dd8a8de5..d9b7a85b78 100644 --- a/tests/PHPStan/Levels/data/casts-7.json +++ b/tests/PHPStan/Levels/data/casts-7.json @@ -1,4 +1,9 @@ [ + { + "message": "Cannot cast array|(callable(): mixed) to int.", + "line": 20, + "ignorable": true + }, { "message": "Cannot cast array|float|int to string.", "line": 21, diff --git a/tests/PHPStan/Rules/Cast/InvalidCastRuleTest.php b/tests/PHPStan/Rules/Cast/InvalidCastRuleTest.php index e0a0fd1e57..6c5327be68 100644 --- a/tests/PHPStan/Rules/Cast/InvalidCastRuleTest.php +++ b/tests/PHPStan/Rules/Cast/InvalidCastRuleTest.php @@ -23,14 +23,6 @@ public function testRule(): void 'Cannot cast stdClass to string.', 7, ], - [ - 'Cannot cast array() to int.', - 16, - ], - [ - 'Cannot cast \'blabla\' to int.', - 21, - ], [ 'Cannot cast stdClass to int.', 23, From 4a3ffab7ec33edf10f16e904ca8eb0d4e710b915 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 4 Jul 2021 11:41:46 +0200 Subject: [PATCH 0005/1284] Fix for non-empty-array --- src/Type/Accessory/NonEmptyArrayType.php | 6 ++++-- tests/PHPStan/Analyser/data/strval.php | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/Type/Accessory/NonEmptyArrayType.php b/src/Type/Accessory/NonEmptyArrayType.php index f2edcb180c..81932ef2f9 100644 --- a/src/Type/Accessory/NonEmptyArrayType.php +++ b/src/Type/Accessory/NonEmptyArrayType.php @@ -5,6 +5,8 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\CompoundType; use PHPStan\Type\CompoundTypeHelper; +use PHPStan\Type\Constant\ConstantFloatType; +use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\ErrorType; use PHPStan\Type\IntersectionType; use PHPStan\Type\MixedType; @@ -142,12 +144,12 @@ public function toNumber(): Type public function toInteger(): Type { - return new ErrorType(); + return new ConstantIntegerType(1); } public function toFloat(): Type { - return new ErrorType(); + return new ConstantFloatType(1.0); } public function toString(): Type diff --git a/tests/PHPStan/Analyser/data/strval.php b/tests/PHPStan/Analyser/data/strval.php index 1d186d55c2..593bcf5065 100644 --- a/tests/PHPStan/Analyser/data/strval.php +++ b/tests/PHPStan/Analyser/data/strval.php @@ -76,3 +76,18 @@ function boolvalTest(string $string): void assertType('true', boolval([null])); assertType('true', boolval(new \stdClass())); } + +function arrayTest(array $a): void +{ + assertType('0|1', intval($a)); + assertType('0.0|1.0', floatval($a)); + assertType('bool', boolval($a)); +} + +/** @param non-empty-array $a */ +function nonEmptyArrayTest(array $a): void +{ + assertType('1', intval($a)); + assertType('1.0', floatval($a)); + assertType('true', boolval($a)); +} From 3c3ea2f21898e8e411b0a7263910722738839288 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 4 Jul 2021 17:07:02 +0200 Subject: [PATCH 0006/1284] Regression tests Closes https://github.com/phpstan/phpstan/issues/4829 Closes https://github.com/phpstan/phpstan/issues/4844 Closes https://github.com/phpstan/phpstan/issues/3784 Closes https://github.com/phpstan/phpstan/issues/3700 Closes https://github.com/phpstan/phpstan/issues/4848 Closes https://github.com/phpstan/phpstan/issues/5162 --- ...nexistentOffsetInArrayDimFetchRuleTest.php | 15 +++++ tests/PHPStan/Rules/Arrays/data/bug-3700.php | 13 +++++ tests/PHPStan/Rules/Arrays/data/bug-3784.php | 23 ++++++++ tests/PHPStan/Rules/Arrays/data/bug-4829.php | 31 ++++++++++ .../Rules/Cast/InvalidCastRuleTest.php | 5 ++ tests/PHPStan/Rules/Cast/data/bug-5162.php | 58 +++++++++++++++++++ ...rictComparisonOfDifferentTypesRuleTest.php | 11 ++++ .../Rules/Comparison/data/bug-4848.php | 12 ++++ .../Rules/Methods/CallMethodsRuleTest.php | 8 +++ tests/PHPStan/Rules/Methods/data/bug-4844.php | 33 +++++++++++ 10 files changed, 209 insertions(+) create mode 100644 tests/PHPStan/Rules/Arrays/data/bug-3700.php create mode 100644 tests/PHPStan/Rules/Arrays/data/bug-3784.php create mode 100644 tests/PHPStan/Rules/Arrays/data/bug-4829.php create mode 100644 tests/PHPStan/Rules/Cast/data/bug-5162.php create mode 100644 tests/PHPStan/Rules/Comparison/data/bug-4848.php create mode 100644 tests/PHPStan/Rules/Methods/data/bug-4844.php diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index 9cc70f326b..a8f34fdc8d 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -283,4 +283,19 @@ public function testBug3297(): void $this->analyse([__DIR__ . '/data/bug-3297.php'], []); } + public function testBug4829(): void + { + $this->analyse([__DIR__ . '/data/bug-4829.php'], []); + } + + public function testBug3784(): void + { + $this->analyse([__DIR__ . '/data/bug-3784.php'], []); + } + + public function testBug3700(): void + { + $this->analyse([__DIR__ . '/data/bug-3700.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Arrays/data/bug-3700.php b/tests/PHPStan/Rules/Arrays/data/bug-3700.php new file mode 100644 index 0000000000..b0c6f6a672 --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/bug-3700.php @@ -0,0 +1,13 @@ + 'blah1', 'cat2' => 'blah2', ?'cat3' => 'blah3'). + public function stan() : void + { + $ret = []; + + // if $result has 1 element, the error does not occur + // if $result is [], the error occurs + $result = ["val1", "val2"]; + + foreach ($result as $val) { + // if I replace $val with a string, the error does not occur + //$val = "test"; + + // if I remove one assignment, the error does not occur + $ret[$val]['cat1'] = "blah1"; + $ret[$val]['cat2'] = "blah2"; + $ret[$val]['cat3'] = 'blah3'; + + $t1 = $ret[$val]['cat1']; + $t2 = $ret[$val]['cat2']; + $t3 = $ret[$val]['cat3']; // error occurs here + } + } +} diff --git a/tests/PHPStan/Rules/Cast/InvalidCastRuleTest.php b/tests/PHPStan/Rules/Cast/InvalidCastRuleTest.php index 6c5327be68..8794b9b6d9 100644 --- a/tests/PHPStan/Rules/Cast/InvalidCastRuleTest.php +++ b/tests/PHPStan/Rules/Cast/InvalidCastRuleTest.php @@ -42,4 +42,9 @@ public function testRule(): void ]); } + public function testBug5162(): void + { + $this->analyse([__DIR__ . '/data/bug-5162.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Cast/data/bug-5162.php b/tests/PHPStan/Rules/Cast/data/bug-5162.php new file mode 100644 index 0000000000..1b38c65ce9 --- /dev/null +++ b/tests/PHPStan/Rules/Cast/data/bug-5162.php @@ -0,0 +1,58 @@ +> + */ + function Get() + { + switch ( rand(1,3) ) + { + case 1: + return ['a' => 'val']; + case 2: + return ['a' => '1']; + case 3: + return ['a' => []]; + } + return []; + } + + public function doFoo(): void + { + // This variant works + $result1 = $this->Get(); + if ( ! array_key_exists('a', $result1)) + { + exit(1); + } + if ( ! is_numeric( $result1['a'] ) ) + { + exit(1); + } + $val = (float) $result1['a']; + } + + public function doBar(): void + { + // This variant doesn't work .. but is logically identical + $result2 = $this->Get(); + if ( array_key_exists('a',$result2) && ! is_numeric( $result2['a'] ) ) + { + exit(1); + } + if ( ! array_key_exists('a', $result2) ) + { + exit(1); + } + $val = (float) $result2['a']; + } + +} diff --git a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php index 2b571f01e8..d7e007e1ad 100644 --- a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php @@ -456,4 +456,15 @@ public function testBug3357(): void $this->analyse([__DIR__ . '/data/bug-3357.php'], []); } + public function testBug4848(): void + { + $this->checkAlwaysTrueStrictComparison = true; + $this->analyse([__DIR__ . '/data/bug-4848.php'], [ + [ + 'Strict comparison using === between \'18446744073709551615\' and \'9223372036854775807\' will always evaluate to false.', + 7, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-4848.php b/tests/PHPStan/Rules/Comparison/data/bug-4848.php new file mode 100644 index 0000000000..b60e9c54d3 --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-4848.php @@ -0,0 +1,12 @@ +analyse([__DIR__ . '/data/bug-5253.php'], []); } + public function testBug4844(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/bug-4844.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-4844.php b/tests/PHPStan/Rules/Methods/data/bug-4844.php new file mode 100644 index 0000000000..34725b4eed --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-4844.php @@ -0,0 +1,33 @@ +update_attributes([ + $name => $value, + ]); + } + + /** + * Updates as an array given attributes and saves the record. + * + * @param non-empty-array $attributes + * + * @return bool + */ + public function update_attributes($attributes) + { + return true; + } +} From c98d0a4946c373543c379427983e02ccef71f86c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 5 Jul 2021 16:05:10 +0200 Subject: [PATCH 0007/1284] Bleeding edge - validate overriding methods in stubs --- conf/bleedingEdge.neon | 1 + conf/config.neon | 6 +++++- src/PhpDoc/StubValidator.php | 23 ++++++++++++++++++++--- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index ea66c26407..3baea6e50c 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -21,5 +21,6 @@ parameters: apiRules: true deepInspectTypes: true neverInGenericReturnType: true + validateOverridingMethodsInStubs: true stubFiles: - ../stubs/arrayFunctions.stub diff --git a/conf/config.neon b/conf/config.neon index 593bc7dd7c..d865f17d3b 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -44,6 +44,7 @@ parameters: apiRules: false deepInspectTypes: false neverInGenericReturnType: false + validateOverridingMethodsInStubs: false fileExtensions: - php checkAlwaysTrueCheckTypeFunctionCall: false @@ -215,7 +216,8 @@ parametersSchema: preciseExceptionTracking: bool(), apiRules: bool(), deepInspectTypes: bool(), - neverInGenericReturnType: bool() + neverInGenericReturnType: bool(), + validateOverridingMethodsInStubs: bool() ]) fileExtensions: listOf(string()) checkAlwaysTrueCheckTypeFunctionCall: bool() @@ -418,6 +420,8 @@ services: - class: PHPStan\PhpDoc\StubValidator + arguments: + validateOverridingMethods: %featureToggles.validateOverridingMethodsInStubs% - class: PHPStan\Analyser\Analyser diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index 7b7e606389..206bc3d908 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -8,6 +8,7 @@ use PHPStan\Broker\Broker; use PHPStan\DependencyInjection\Container; use PHPStan\DependencyInjection\DerivativeContainerFactory; +use PHPStan\Php\PhpVersion; use PHPStan\PhpDocParser\Lexer\Lexer; use PHPStan\PhpDocParser\Parser\PhpDocParser; use PHPStan\Reflection\ReflectionProvider; @@ -33,8 +34,10 @@ use PHPStan\Rules\Generics\TraitTemplateTypeRule; use PHPStan\Rules\Generics\VarianceCheck; use PHPStan\Rules\Methods\ExistingClassesInTypehintsRule; +use PHPStan\Rules\Methods\MethodSignatureRule; use PHPStan\Rules\Methods\MissingMethodParameterTypehintRule; use PHPStan\Rules\Methods\MissingMethodReturnTypehintRule; +use PHPStan\Rules\Methods\OverridingMethodRule; use PHPStan\Rules\MissingTypehintCheck; use PHPStan\Rules\PhpDoc\IncompatiblePhpDocTypeRule; use PHPStan\Rules\PhpDoc\IncompatiblePropertyPhpDocTypeRule; @@ -52,11 +55,15 @@ class StubValidator private \PHPStan\DependencyInjection\DerivativeContainerFactory $derivativeContainerFactory; + private bool $validateOverridingMethods; + public function __construct( - DerivativeContainerFactory $derivativeContainerFactory + DerivativeContainerFactory $derivativeContainerFactory, + bool $validateOverridingMethods ) { $this->derivativeContainerFactory = $derivativeContainerFactory; + $this->validateOverridingMethods = $validateOverridingMethods; } /** @@ -127,7 +134,7 @@ private function getRuleRegistry(Container $container): Registry $missingTypehintCheck = $container->getByType(MissingTypehintCheck::class); $unresolvableTypeHelper = $container->getByType(UnresolvableTypeHelper::class); - return new Registry([ + $rules = [ // level 0 new ExistingClassesInClassImplementsRule($classCaseSensitivityCheck, $reflectionProvider), new ExistingClassesInInterfaceExtendsRule($classCaseSensitivityCheck, $reflectionProvider), @@ -165,7 +172,17 @@ private function getRuleRegistry(Container $container): Registry new MissingMethodParameterTypehintRule($missingTypehintCheck), new MissingMethodReturnTypehintRule($missingTypehintCheck), new MissingPropertyTypehintRule($missingTypehintCheck), - ]); + ]; + + if ($this->validateOverridingMethods) { + $rules[] = new OverridingMethodRule( + $container->getByType(PhpVersion::class), + new MethodSignatureRule(true, true), + true + ); + } + + return new Registry($rules); } } From 2a1597bf8ad3634a36db8d96189b07891cae92a6 Mon Sep 17 00:00:00 2001 From: prinsfrank Date: Tue, 6 Jul 2021 14:36:27 +0200 Subject: [PATCH 0008/1284] Don't try to create a directory that already exists --- src/Command/CommandHelper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php index 5194ab3f3a..1a5caa2dce 100644 --- a/src/Command/CommandHelper.php +++ b/src/Command/CommandHelper.php @@ -238,7 +238,7 @@ public static function begin( ); $createDir = static function (string $path) use ($errorOutput): void { - if (!@mkdir($path, 0777) && !is_dir($path)) { + if (!is_dir($path) && !@mkdir($path, 0777) && !is_dir($path)) { $errorOutput->writeLineFormatted(sprintf('Cannot create a temp directory %s', $path)); throw new \PHPStan\Command\InceptionNotSuccessfulException(); } From 6ef5e91effcbc5c5b3f49cd2cf423d67044b8941 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 5 Jul 2021 16:11:09 +0200 Subject: [PATCH 0009/1284] non-empty-string Closes https://github.com/phpstan/phpstan/issues/3981 Closes https://github.com/phpstan/phpstan/issues/4711 --- conf/config.neon | 5 + resources/functionMap_php80delta.php | 2 +- src/Analyser/TypeSpecifier.php | 45 ++++- src/File/FuzzyRelativePathHelper.php | 2 +- src/PhpDoc/TypeNodeResolver.php | 7 + .../Accessory/AccessoryNonEmptyStringType.php | 172 ++++++++++++++++++ .../Accessory/AccessoryNumericStringType.php | 5 + src/Type/Accessory/HasOffsetType.php | 5 + src/Type/Accessory/NonEmptyArrayType.php | 5 + src/Type/ArrayType.php | 5 + src/Type/CallableType.php | 5 + src/Type/ClassStringType.php | 10 + src/Type/ClosureType.php | 5 + src/Type/Constant/ConstantStringType.php | 5 + src/Type/FloatType.php | 5 + src/Type/IntersectionType.php | 26 ++- src/Type/IterableType.php | 5 + src/Type/JustNullableTypeTrait.php | 5 + src/Type/MixedType.php | 5 + src/Type/NeverType.php | 5 + src/Type/NullType.php | 5 + src/Type/ObjectType.php | 5 + .../Php/StrTokFunctionReturnTypeExtension.php | 42 +++++ src/Type/StaticType.php | 5 + src/Type/StrictMixedType.php | 5 + src/Type/StringType.php | 5 + src/Type/Traits/ObjectTypeTrait.php | 5 + src/Type/Type.php | 2 + src/Type/TypeCombinator.php | 28 +++ src/Type/UnionType.php | 7 + src/Type/VerbosityLevel.php | 3 +- src/Type/VoidType.php | 5 + .../Analyser/LegacyNodeScopeResolverTest.php | 8 +- .../Analyser/NodeScopeResolverTest.php | 3 + tests/PHPStan/Analyser/data/bug-3382.php | 2 +- tests/PHPStan/Analyser/data/bug-3981.php | 25 +++ tests/PHPStan/Analyser/data/bug-4711.php | 19 ++ .../Analyser/data/comparison-operators.php | 2 +- tests/PHPStan/Analyser/data/minmax-arrays.php | 2 +- .../Analyser/data/non-empty-string.php | 116 ++++++++++++ .../data/class-implements-out-of-phpstan.php | 5 + .../CallToFunctionParametersRuleTest.php | 18 ++ .../Rules/Functions/data/explode-80.php | 19 ++ tests/PHPStan/Type/TypeCombinatorTest.php | 12 ++ 44 files changed, 664 insertions(+), 13 deletions(-) create mode 100644 src/Type/Accessory/AccessoryNonEmptyStringType.php create mode 100644 src/Type/Php/StrTokFunctionReturnTypeExtension.php create mode 100644 tests/PHPStan/Analyser/data/bug-3981.php create mode 100644 tests/PHPStan/Analyser/data/bug-4711.php create mode 100644 tests/PHPStan/Analyser/data/non-empty-string.php create mode 100644 tests/PHPStan/Rules/Functions/data/explode-80.php diff --git a/conf/config.neon b/conf/config.neon index d865f17d3b..f6c5f7fc49 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1401,6 +1401,11 @@ services: tags: - phpstan.broker.dynamicFunctionReturnTypeExtension + - + class: PHPStan\Type\Php\StrTokFunctionReturnTypeExtension + tags: + - phpstan.broker.dynamicFunctionReturnTypeExtension + - class: PHPStan\Type\Php\SprintfFunctionDynamicReturnTypeExtension tags: diff --git a/resources/functionMap_php80delta.php b/resources/functionMap_php80delta.php index c42dbbcd3b..4634db9bb5 100644 --- a/resources/functionMap_php80delta.php +++ b/resources/functionMap_php80delta.php @@ -38,7 +38,7 @@ 'date_time_set' => ['DateTime', 'object'=>'DateTime', 'hour'=>'int', 'minute'=>'int', 'second='=>'int', 'microseconds='=>'int'], 'date_timestamp_set' => ['DateTime', 'object'=>'DateTime', 'unixtimestamp'=>'int'], 'date_timezone_set' => ['DateTime', 'object'=>'DateTime', 'timezone'=>'DateTimeZone'], - 'explode' => ['array', 'separator'=>'string', 'str'=>'string', 'limit='=>'int'], + 'explode' => ['array', 'separator'=>'non-empty-string', 'str'=>'string', 'limit='=>'int'], 'fdiv' => ['float', 'dividend'=>'float', 'divisor'=>'float'], 'get_debug_type' => ['string', 'var'=>'mixed'], 'get_resource_id' => ['int', 'res'=>'resource'], diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 41efe48747..34bcab31c5 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -20,6 +20,7 @@ use PhpParser\Node\Name; use PHPStan\Reflection\ReflectionProvider; use PHPStan\TrinaryLogic; +use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\HasOffsetType; use PHPStan\Type\Accessory\HasPropertyType; use PHPStan\Type\Accessory\NonEmptyArrayType; @@ -41,6 +42,7 @@ use PHPStan\Type\ObjectWithoutClassType; use PHPStan\Type\StaticType; use PHPStan\Type\StaticTypeFactory; +use PHPStan\Type\StringType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use PHPStan\Type\TypeTraverser; @@ -219,6 +221,26 @@ public function specifyTypesInCondition( } } } + + if ( + !$context->null() + && $exprNode instanceof FuncCall + && count($exprNode->args) === 1 + && $exprNode->name instanceof Name + && strtolower((string) $exprNode->name) === 'strlen' + && $constantType instanceof ConstantIntegerType + ) { + if ($context->truthy() || $constantType->getValue() === 0) { + $newContext = $context; + if ($constantType->getValue() === 0) { + $newContext = $newContext->negate(); + } + $argType = $scope->getType($exprNode->args[0]->value); + if ($argType instanceof StringType) { + return $this->create($exprNode->args[0]->value, new AccessoryNonEmptyStringType(), $newContext, false, $scope); + } + } + } } if ($context->true()) { @@ -383,11 +405,11 @@ public function specifyTypesInCondition( $expr->left instanceof FuncCall && count($expr->left->args) === 1 && $expr->left->name instanceof Name - && strtolower((string) $expr->left->name) === 'count' + && in_array(strtolower((string) $expr->left->name), ['count', 'strlen'], true) && ( !$expr->right instanceof FuncCall || !$expr->right->name instanceof Name - || strtolower((string) $expr->right->name) !== 'count' + || !in_array(strtolower((string) $expr->right->name), ['count', 'strlen'], true) ) ) { $inverseOperator = $expr instanceof Node\Expr\BinaryOp\Smaller @@ -422,6 +444,25 @@ public function specifyTypesInCondition( } } + if ( + !$context->null() + && $expr->right instanceof FuncCall + && count($expr->right->args) === 1 + && $expr->right->name instanceof Name + && strtolower((string) $expr->right->name) === 'strlen' + && (new IntegerType())->isSuperTypeOf($leftType)->yes() + ) { + if ( + $context->truthy() && (IntegerRangeType::createAllGreaterThanOrEqualTo(1 - $offset)->isSuperTypeOf($leftType)->yes()) + || ($context->falsey() && (new ConstantIntegerType(1 - $offset))->isSuperTypeOf($leftType)->yes()) + ) { + $argType = $scope->getType($expr->right->args[0]->value); + if ($argType instanceof StringType) { + $result = $result->unionWith($this->create($expr->right->args[0]->value, new AccessoryNonEmptyStringType(), $context, false, $scope)); + } + } + } + if ($leftType instanceof ConstantIntegerType) { if ($expr->right instanceof Expr\PostInc) { $result = $result->unionWith($this->createRangeTypes( diff --git a/src/File/FuzzyRelativePathHelper.php b/src/File/FuzzyRelativePathHelper.php index 882cc91c99..89deb459fc 100644 --- a/src/File/FuzzyRelativePathHelper.php +++ b/src/File/FuzzyRelativePathHelper.php @@ -15,7 +15,7 @@ class FuzzyRelativePathHelper implements RelativePathHelper * @param RelativePathHelper $fallbackRelativePathHelper * @param string $currentWorkingDirectory * @param string[] $analysedPaths - * @param string|null $directorySeparator + * @param non-empty-string|null $directorySeparator */ public function __construct( RelativePathHelper $fallbackRelativePathHelper, diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index 918e4a8197..2c9963c000 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -28,6 +28,7 @@ use PHPStan\Reflection\Native\NativeParameterReflection; use PHPStan\Reflection\PassedByReference; use PHPStan\Reflection\ReflectionProvider; +use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\ArrayType; @@ -188,6 +189,12 @@ private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameSco new AccessoryNumericStringType(), ]); + case 'non-empty-string': + return new IntersectionType([ + new StringType(), + new AccessoryNonEmptyStringType(), + ]); + case 'bool': return new BooleanType(); diff --git a/src/Type/Accessory/AccessoryNonEmptyStringType.php b/src/Type/Accessory/AccessoryNonEmptyStringType.php new file mode 100644 index 0000000000..e00d6841f3 --- /dev/null +++ b/src/Type/Accessory/AccessoryNonEmptyStringType.php @@ -0,0 +1,172 @@ +isNonEmptyString(); + } + + public function isSuperTypeOf(Type $type): TrinaryLogic + { + if ($this->equals($type)) { + return TrinaryLogic::createYes(); + } + + return $type->isNonEmptyString(); + } + + public function isSubTypeOf(Type $otherType): TrinaryLogic + { + if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { + return $otherType->isSuperTypeOf($this); + } + + return $otherType->isNonEmptyString() + ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()); + } + + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic + { + return $this->isSubTypeOf($acceptingType); + } + + public function equals(Type $type): bool + { + return $type instanceof self; + } + + public function describe(\PHPStan\Type\VerbosityLevel $level): string + { + return 'non-empty-string'; + } + + public function isOffsetAccessible(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function hasOffsetValueType(Type $offsetType): TrinaryLogic + { + return (new IntegerType())->isSuperTypeOf($offsetType)->and(TrinaryLogic::createMaybe()); + } + + public function getOffsetValueType(Type $offsetType): Type + { + if ($this->hasOffsetValueType($offsetType)->no()) { + return new ErrorType(); + } + + if ((new ConstantIntegerType(0))->isSuperTypeOf($offsetType)->yes()) { + return new IntersectionType([new StringType(), new AccessoryNonEmptyStringType()]); + } + + return new StringType(); + } + + public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type + { + return $this; + } + + public function isArray(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function toNumber(): Type + { + return new UnionType([ + $this->toInteger(), + $this->toFloat(), + ]); + } + + public function toInteger(): Type + { + return new IntegerType(); + } + + public function toFloat(): Type + { + return new FloatType(); + } + + public function toString(): Type + { + return $this; + } + + public function toArray(): Type + { + return new ConstantArrayType( + [new ConstantIntegerType(0)], + [$this], + 1 + ); + } + + public function isNumericString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function isNonEmptyString(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function traverse(callable $cb): Type + { + return $this; + } + + public static function __set_state(array $properties): Type + { + return new self(); + } + +} diff --git a/src/Type/Accessory/AccessoryNumericStringType.php b/src/Type/Accessory/AccessoryNumericStringType.php index 0c9fbf324f..3733c12eff 100644 --- a/src/Type/Accessory/AccessoryNumericStringType.php +++ b/src/Type/Accessory/AccessoryNumericStringType.php @@ -150,6 +150,11 @@ public function isNumericString(): TrinaryLogic return TrinaryLogic::createYes(); } + public function isNonEmptyString(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + public function traverse(callable $cb): Type { return $this; diff --git a/src/Type/Accessory/HasOffsetType.php b/src/Type/Accessory/HasOffsetType.php index 3ceee34cdf..c55f3ebc83 100644 --- a/src/Type/Accessory/HasOffsetType.php +++ b/src/Type/Accessory/HasOffsetType.php @@ -131,6 +131,11 @@ public function isNumericString(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function isNonEmptyString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + public function toNumber(): Type { return new ErrorType(); diff --git a/src/Type/Accessory/NonEmptyArrayType.php b/src/Type/Accessory/NonEmptyArrayType.php index 81932ef2f9..dc1f6cb30d 100644 --- a/src/Type/Accessory/NonEmptyArrayType.php +++ b/src/Type/Accessory/NonEmptyArrayType.php @@ -137,6 +137,11 @@ public function isNumericString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isNonEmptyString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function toNumber(): Type { return new ErrorType(); diff --git a/src/Type/ArrayType.php b/src/Type/ArrayType.php index 3ee4c84393..b9023e2503 100644 --- a/src/Type/ArrayType.php +++ b/src/Type/ArrayType.php @@ -195,6 +195,11 @@ public function isNumericString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isNonEmptyString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isOffsetAccessible(): TrinaryLogic { return TrinaryLogic::createYes(); diff --git a/src/Type/CallableType.php b/src/Type/CallableType.php index ddeeb6530d..55c0262ebd 100644 --- a/src/Type/CallableType.php +++ b/src/Type/CallableType.php @@ -314,6 +314,11 @@ public function isNumericString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isNonEmptyString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isCommonCallable(): bool { return $this->isCommonCallable; diff --git a/src/Type/ClassStringType.php b/src/Type/ClassStringType.php index 4259378630..99bfbb1e27 100644 --- a/src/Type/ClassStringType.php +++ b/src/Type/ClassStringType.php @@ -65,6 +65,16 @@ public function isSuperTypeOf(Type $type): TrinaryLogic return TrinaryLogic::createNo(); } + public function isNumericString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function isNonEmptyString(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + /** * @param mixed[] $properties * @return Type diff --git a/src/Type/ClosureType.php b/src/Type/ClosureType.php index 26b2971b61..31af1f1cc3 100644 --- a/src/Type/ClosureType.php +++ b/src/Type/ClosureType.php @@ -406,6 +406,11 @@ public function isNumericString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isNonEmptyString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + /** * @param mixed[] $properties * @return Type diff --git a/src/Type/Constant/ConstantStringType.php b/src/Type/Constant/ConstantStringType.php index 7dbdde168e..68788bf1cc 100644 --- a/src/Type/Constant/ConstantStringType.php +++ b/src/Type/Constant/ConstantStringType.php @@ -239,6 +239,11 @@ public function isNumericString(): TrinaryLogic return TrinaryLogic::createFromBoolean(is_numeric($this->getValue())); } + public function isNonEmptyString(): TrinaryLogic + { + return TrinaryLogic::createFromBoolean($this->getValue() !== ''); + } + public function hasOffsetValueType(Type $offsetType): TrinaryLogic { if ($offsetType instanceof ConstantIntegerType) { diff --git a/src/Type/FloatType.php b/src/Type/FloatType.php index c6ef8824d5..cd270a3a1e 100644 --- a/src/Type/FloatType.php +++ b/src/Type/FloatType.php @@ -138,6 +138,11 @@ public function isNumericString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isNonEmptyString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function traverse(callable $cb): Type { return $this; diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index e1e2d3ff4d..6355a69a4b 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -12,6 +12,7 @@ use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection; use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection; use PHPStan\TrinaryLogic; +use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; use PHPStan\Type\Accessory\AccessoryType; use PHPStan\Type\Accessory\NonEmptyArrayType; @@ -138,21 +139,37 @@ function () use ($level): string { }, function () use ($level): string { $typeNames = []; + $accessoryTypes = []; foreach ($this->types as $type) { - if ($type instanceof AccessoryType && !$type instanceof AccessoryNumericStringType && !$type instanceof NonEmptyArrayType) { + if ($type instanceof AccessoryNonEmptyStringType) { + $accessoryTypes[] = $type; + } + if ($type instanceof AccessoryType && !$type instanceof AccessoryNumericStringType && !$type instanceof NonEmptyArrayType && !$type instanceof AccessoryNonEmptyStringType) { continue; } $typeNames[] = $type->describe($level); } + if (count($accessoryTypes) === 1) { + return $accessoryTypes[0]->describe($level); + } + return implode('&', $typeNames); }, function () use ($level): string { $typeNames = []; + $accessoryTypes = []; foreach ($this->types as $type) { + if ($type instanceof AccessoryNonEmptyStringType) { + $accessoryTypes[] = $type; + } $typeNames[] = $type->describe($level); } + if (count($accessoryTypes) === 1) { + return $accessoryTypes[0]->describe($level); + } + return implode('&', $typeNames); } ); @@ -309,6 +326,13 @@ public function isNumericString(): TrinaryLogic }); } + public function isNonEmptyString(): TrinaryLogic + { + return $this->intersectResults(static function (Type $type): TrinaryLogic { + return $type->isNonEmptyString(); + }); + } + public function isOffsetAccessible(): TrinaryLogic { return $this->intersectResults(static function (Type $type): TrinaryLogic { diff --git a/src/Type/IterableType.php b/src/Type/IterableType.php index 77a89de4a3..445212b9bf 100644 --- a/src/Type/IterableType.php +++ b/src/Type/IterableType.php @@ -235,6 +235,11 @@ public function isNumericString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isNonEmptyString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function inferTemplateTypes(Type $receivedType): TemplateTypeMap { if ($receivedType instanceof UnionType || $receivedType instanceof IntersectionType) { diff --git a/src/Type/JustNullableTypeTrait.php b/src/Type/JustNullableTypeTrait.php index d56223fa0f..34975d9ca3 100644 --- a/src/Type/JustNullableTypeTrait.php +++ b/src/Type/JustNullableTypeTrait.php @@ -62,4 +62,9 @@ public function isNumericString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isNonEmptyString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + } diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index 6a8fb51971..f8d58304d6 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -382,6 +382,11 @@ public function isNumericString(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function isNonEmptyString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + /** * @param mixed[] $properties * @return Type diff --git a/src/Type/NeverType.php b/src/Type/NeverType.php index 663a37940b..8cda153229 100644 --- a/src/Type/NeverType.php +++ b/src/Type/NeverType.php @@ -231,6 +231,11 @@ public function isNumericString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isNonEmptyString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + /** * @param mixed[] $properties * @return Type diff --git a/src/Type/NullType.php b/src/Type/NullType.php index 39b372f031..cdbce4ebba 100644 --- a/src/Type/NullType.php +++ b/src/Type/NullType.php @@ -173,6 +173,11 @@ public function isNumericString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isNonEmptyString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function getSmallerType(): Type { return new NeverType(); diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index bb5a6d75fd..93606edcad 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -723,6 +723,11 @@ public function isNumericString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isNonEmptyString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + private function isExtraOffsetAccessibleClass(): TrinaryLogic { $classReflection = $this->getClassReflection(); diff --git a/src/Type/Php/StrTokFunctionReturnTypeExtension.php b/src/Type/Php/StrTokFunctionReturnTypeExtension.php new file mode 100644 index 0000000000..5f7f343528 --- /dev/null +++ b/src/Type/Php/StrTokFunctionReturnTypeExtension.php @@ -0,0 +1,42 @@ +getName() === 'strtok'; + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): \PHPStan\Type\Type + { + $args = $functionCall->args; + if (count($args) !== 2) { + return ParametersAcceptorSelector::selectFromArgs($scope, $args, $functionReflection->getVariants())->getReturnType(); + } + + $delimiterType = $scope->getType($functionCall->args[0]->value); + $isEmptyString = (new ConstantStringType(''))->isSuperTypeOf($delimiterType); + if ($isEmptyString->yes()) { + return new ConstantBooleanType(false); + } + + if ($isEmptyString->no()) { + return new StringType(); + } + + return ParametersAcceptorSelector::selectFromArgs($scope, $args, $functionReflection->getVariants())->getReturnType(); + } + +} diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php index 4fda7a3c7b..d40296efbd 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -344,6 +344,11 @@ public function isNumericString(): TrinaryLogic return $this->getStaticObjectType()->isNumericString(); } + public function isNonEmptyString(): TrinaryLogic + { + return $this->getStaticObjectType()->isNonEmptyString(); + } + /** * @param \PHPStan\Reflection\ClassMemberAccessAnswerer $scope * @return \PHPStan\Reflection\ParametersAcceptor[] diff --git a/src/Type/StrictMixedType.php b/src/Type/StrictMixedType.php index 21f3d01cc6..d8e90b3464 100644 --- a/src/Type/StrictMixedType.php +++ b/src/Type/StrictMixedType.php @@ -141,6 +141,11 @@ public function isNumericString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isNonEmptyString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isOffsetAccessible(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/StringType.php b/src/Type/StringType.php index 213a6e0aec..14d48f2f63 100644 --- a/src/Type/StringType.php +++ b/src/Type/StringType.php @@ -131,6 +131,11 @@ public function isNumericString(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function isNonEmptyString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + /** * @param mixed[] $properties * @return Type diff --git a/src/Type/Traits/ObjectTypeTrait.php b/src/Type/Traits/ObjectTypeTrait.php index a0977f9da4..f6e79935d9 100644 --- a/src/Type/Traits/ObjectTypeTrait.php +++ b/src/Type/Traits/ObjectTypeTrait.php @@ -114,6 +114,11 @@ public function isNumericString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isNonEmptyString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function toNumber(): Type { return new ErrorType(); diff --git a/src/Type/Type.php b/src/Type/Type.php index acb40b2093..201963b4f3 100644 --- a/src/Type/Type.php +++ b/src/Type/Type.php @@ -98,6 +98,8 @@ public function isSmallerThanOrEqual(Type $otherType): TrinaryLogic; public function isNumericString(): TrinaryLogic; + public function isNonEmptyString(): TrinaryLogic; + public function getSmallerType(): Type; public function getSmallerOrEqualType(): Type; diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index c43a2eed95..0bfa304487 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -2,6 +2,7 @@ namespace PHPStan\Type; +use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryType; use PHPStan\Type\Accessory\HasOffsetType; use PHPStan\Type\Accessory\NonEmptyArrayType; @@ -112,6 +113,13 @@ public static function remove(Type $fromType, Type $typeToRemove): Type if ($fromType instanceof ConstantArrayType && $typeToRemove instanceof HasOffsetType) { return $fromType->unsetOffset($typeToRemove->getOffsetType()); } + } elseif ($fromType instanceof StringType) { + if ($typeToRemove instanceof ConstantStringType && $typeToRemove->getValue() === '') { + return self::intersect($fromType, new AccessoryNonEmptyStringType()); + } + if ($typeToRemove instanceof AccessoryNonEmptyStringType) { + return new ConstantStringType(''); + } } elseif ($fromType instanceof SubtractableType) { $typeToSubtractFrom = $fromType; if ($fromType instanceof TemplateType) { @@ -349,6 +357,26 @@ public static function union(Type ...$types): Type array_splice($types, $j--, 1); continue 1; } + + if ( + $types[$i] instanceof ConstantStringType + && $types[$i]->getValue() === '' + && $types[$j]->describe(VerbosityLevel::value()) === 'non-empty-string' + ) { + $types[$i] = new StringType(); + array_splice($types, $j--, 1); + continue 1; + } + + if ( + $types[$j] instanceof ConstantStringType + && $types[$j]->getValue() === '' + && $types[$i]->describe(VerbosityLevel::value()) === 'non-empty-string' + ) { + $types[$j] = new StringType(); + array_splice($types, $i--, 1); + continue 2; + } } } diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index a4bdc89bb7..cb52b89760 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -397,6 +397,13 @@ public function isNumericString(): TrinaryLogic }); } + public function isNonEmptyString(): TrinaryLogic + { + return $this->unionResults(static function (Type $type): TrinaryLogic { + return $type->isNonEmptyString(); + }); + } + public function isOffsetAccessible(): TrinaryLogic { return $this->unionResults(static function (Type $type): TrinaryLogic { diff --git a/src/Type/VerbosityLevel.php b/src/Type/VerbosityLevel.php index 87b323e1d1..742b6fe7a1 100644 --- a/src/Type/VerbosityLevel.php +++ b/src/Type/VerbosityLevel.php @@ -2,6 +2,7 @@ namespace PHPStan\Type; +use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\Generic\GenericObjectType; @@ -67,7 +68,7 @@ public static function getRecommendedLevelByType(Type $acceptingType, ?Type $acc $moreVerbose = true; return $type; } - if ($type instanceof AccessoryNumericStringType) { + if ($type instanceof AccessoryNumericStringType || $type instanceof AccessoryNonEmptyStringType) { $moreVerbose = true; return $type; } diff --git a/src/Type/VoidType.php b/src/Type/VoidType.php index 61706e5e08..b14bdbfd65 100644 --- a/src/Type/VoidType.php +++ b/src/Type/VoidType.php @@ -103,6 +103,11 @@ public function isNumericString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isNonEmptyString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function traverse(callable $cb): Type { return $this; diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index b4100a0ddb..53e9f03f14 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -2316,15 +2316,15 @@ public function dataBinaryOperations(): array 'false ? 1 : 2', ], [ - '12|string', + '12|non-empty-string', '$string ?: 12', ], [ - '12|string', + '12|non-empty-string', '$stringOrNull ?: 12', ], [ - '12|string', + '12|non-empty-string', '@$stringOrNull ?: 12', ], [ @@ -3204,7 +3204,7 @@ public function dataBinaryOperations(): array '$simpleXMLReturningXML', ], [ - 'string', + 'non-empty-string', '$xmlString', ], [ diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index f993bdc58d..3862e935bd 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -432,6 +432,9 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5219.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/strval.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/array-next.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/non-empty-string.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3981.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4711.php'); } /** diff --git a/tests/PHPStan/Analyser/data/bug-3382.php b/tests/PHPStan/Analyser/data/bug-3382.php index 3623d2a9cf..973b489f4c 100644 --- a/tests/PHPStan/Analyser/data/bug-3382.php +++ b/tests/PHPStan/Analyser/data/bug-3382.php @@ -5,5 +5,5 @@ use function PHPStan\Testing\assertType; if (ini_get('auto_prepend_file')) { - assertType('string', ini_get('auto_prepend_file')); + assertType('non-empty-string', ini_get('auto_prepend_file')); } diff --git a/tests/PHPStan/Analyser/data/bug-3981.php b/tests/PHPStan/Analyser/data/bug-3981.php new file mode 100644 index 0000000000..2a1929cf1a --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-3981.php @@ -0,0 +1,25 @@ +', explode($string, '')); + assertType('array', explode($string[0], '')); + } + +} diff --git a/tests/PHPStan/Analyser/data/comparison-operators.php b/tests/PHPStan/Analyser/data/comparison-operators.php index 6f5761498c..14488df983 100644 --- a/tests/PHPStan/Analyser/data/comparison-operators.php +++ b/tests/PHPStan/Analyser/data/comparison-operators.php @@ -126,7 +126,7 @@ public function null(?int $i, ?float $f, ?string $s, ?bool $b): void } if ($s > null) { - assertType('string', $s); + assertType('non-empty-string', $s); } if ($s >= null) { assertType('string|null', $s); diff --git a/tests/PHPStan/Analyser/data/minmax-arrays.php b/tests/PHPStan/Analyser/data/minmax-arrays.php index 762c70be68..37c65e0c5a 100644 --- a/tests/PHPStan/Analyser/data/minmax-arrays.php +++ b/tests/PHPStan/Analyser/data/minmax-arrays.php @@ -116,5 +116,5 @@ function dummy5(int $i, int $j): void } function dummy6(string $s, string $t): void { - assertType('array(?0 => string, ?1 => string)', array_filter([$s, $t])); + assertType('array(?0 => non-empty-string, ?1 => non-empty-string)', array_filter([$s, $t])); } diff --git a/tests/PHPStan/Analyser/data/non-empty-string.php b/tests/PHPStan/Analyser/data/non-empty-string.php new file mode 100644 index 0000000000..e0915797d3 --- /dev/null +++ b/tests/PHPStan/Analyser/data/non-empty-string.php @@ -0,0 +1,116 @@ + 0) { + assertType('non-empty-string', $s); + return; + } + + assertType('\'\'', $s); + } + + public function doBar3(string $s): void + { + if (strlen($s) >= 1) { + assertType('non-empty-string', $s); + return; + } + + assertType('\'\'', $s); + } + + public function doFoo5(string $s): void + { + if (0 === strlen($s)) { + return; + } + + assertType('non-empty-string', $s); + } + + public function doBar4(string $s): void + { + if (0 < strlen($s)) { + assertType('non-empty-string', $s); + return; + } + + assertType('\'\'', $s); + } + + public function doBar5(string $s): void + { + if (1 <= strlen($s)) { + assertType('non-empty-string', $s); + return; + } + + assertType('\'\'', $s); + } + + public function doFoo3(string $s): void + { + if ($s) { + assertType('non-empty-string', $s); + } else { + assertType('\'\'|\'0\'', $s); + } + } + + /** + * @param non-empty-string $s + */ + public function doFoo4(string $s): void + { + assertType('array&nonEmpty', explode($s, 'foo')); + } + + /** + * @param non-empty-string $s + */ + public function doWithNumeric(string $s): void + { + if (!is_numeric($s)) { + return; + } + + assertType('string&numeric', $s); + } + +} diff --git a/tests/PHPStan/Rules/Api/data/class-implements-out-of-phpstan.php b/tests/PHPStan/Rules/Api/data/class-implements-out-of-phpstan.php index ec185e0383..300bedb9ff 100644 --- a/tests/PHPStan/Rules/Api/data/class-implements-out-of-phpstan.php +++ b/tests/PHPStan/Rules/Api/data/class-implements-out-of-phpstan.php @@ -234,6 +234,11 @@ public function isNumericString(): \PHPStan\TrinaryLogic // TODO: Implement isNumericString() method. } + public function isNonEmptyString(): \PHPStan\TrinaryLogic + { + // TODO: Implement isNumericString() method. + } + public function getSmallerType(): \PHPStan\Type\Type { // TODO: Implement getSmallerType() method. diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 5fa75fb444..628e685a7d 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -775,4 +775,22 @@ public function testBug3660(): void ]); } + public function testExplode(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->analyse([__DIR__ . '/data/explode-80.php'], [ + [ + 'Parameter #1 $separator of function explode expects non-empty-string, string given.', + 14, + ], + [ + 'Parameter #1 $separator of function explode expects non-empty-string, \'\' given.', + 16, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/explode-80.php b/tests/PHPStan/Rules/Functions/data/explode-80.php new file mode 100644 index 0000000000..76c0a3a5e0 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/explode-80.php @@ -0,0 +1,19 @@ + Date: Tue, 6 Jul 2021 13:30:13 +0200 Subject: [PATCH 0010/1284] explode() always returns non-empty-array --- resources/functionMap.php | 2 +- resources/functionMap_php80delta.php | 2 +- tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php | 6 +++--- tests/PHPStan/Analyser/data/bug-3961-php8.php | 4 ++-- tests/PHPStan/Analyser/data/bug-4711.php | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 0472a4206c..7edc906748 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -2639,7 +2639,7 @@ 'exp' => ['float', 'number'=>'float'], 'expect_expectl' => ['int', 'expect'=>'resource', 'cases'=>'array', 'match='=>'array'], 'expect_popen' => ['resource|false', 'command'=>'string'], -'explode' => ['array|false', 'separator'=>'string', 'str'=>'string', 'limit='=>'int'], +'explode' => ['non-empty-array|false', 'separator'=>'string', 'str'=>'string', 'limit='=>'int'], 'expm1' => ['float', 'number'=>'float'], 'extension_loaded' => ['bool', 'extension_name'=>'string'], 'extract' => ['int', '&rw_var_array'=>'array', 'extract_type='=>'int', 'prefix='=>'string|null'], diff --git a/resources/functionMap_php80delta.php b/resources/functionMap_php80delta.php index 4634db9bb5..132d3ae252 100644 --- a/resources/functionMap_php80delta.php +++ b/resources/functionMap_php80delta.php @@ -38,7 +38,7 @@ 'date_time_set' => ['DateTime', 'object'=>'DateTime', 'hour'=>'int', 'minute'=>'int', 'second='=>'int', 'microseconds='=>'int'], 'date_timestamp_set' => ['DateTime', 'object'=>'DateTime', 'unixtimestamp'=>'int'], 'date_timezone_set' => ['DateTime', 'object'=>'DateTime', 'timezone'=>'DateTimeZone'], - 'explode' => ['array', 'separator'=>'non-empty-string', 'str'=>'string', 'limit='=>'int'], + 'explode' => ['non-empty-array', 'separator'=>'non-empty-string', 'str'=>'string', 'limit='=>'int'], 'fdiv' => ['float', 'dividend'=>'float', 'divisor'=>'float'], 'get_debug_type' => ['string', 'var'=>'mixed'], 'get_resource_id' => ['int', 'res'=>'resource'], diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 53e9f03f14..53ecc338aa 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -8003,15 +8003,15 @@ public function dataExplode(): array '$sureFalse', ], [ - PHP_VERSION_ID < 80000 ? 'array|false' : 'array', + PHP_VERSION_ID < 80000 ? '(array&nonEmpty)|false' : 'array&nonEmpty', '$arrayOrFalse', ], [ - PHP_VERSION_ID < 80000 ? 'array|false' : 'array', + PHP_VERSION_ID < 80000 ? 'array&nonEmpty|false' : 'array&nonEmpty', '$anotherArrayOrFalse', ], [ - PHP_VERSION_ID < 80000 ? '(array|false)' : 'array', + PHP_VERSION_ID < 80000 ? '((array&nonEmpty)|false)' : 'array&nonEmpty', '$benevolentArrayOrFalse', ], ]; diff --git a/tests/PHPStan/Analyser/data/bug-3961-php8.php b/tests/PHPStan/Analyser/data/bug-3961-php8.php index 2fb86e13e4..657fdf22f6 100644 --- a/tests/PHPStan/Analyser/data/bug-3961-php8.php +++ b/tests/PHPStan/Analyser/data/bug-3961-php8.php @@ -14,8 +14,8 @@ public function doFoo(string $v, string $d, $m): void assertType('array', explode('.', $v, -2)); assertType('array&nonEmpty', explode('.', $v, 0)); assertType('array&nonEmpty', explode('.', $v, 1)); - assertType('array', explode($d, $v)); - assertType('array', explode($m, $v)); + assertType('array&nonEmpty', explode($d, $v)); + assertType('array&nonEmpty', explode($m, $v)); } } diff --git a/tests/PHPStan/Analyser/data/bug-4711.php b/tests/PHPStan/Analyser/data/bug-4711.php index d83cf86441..f074b57db1 100644 --- a/tests/PHPStan/Analyser/data/bug-4711.php +++ b/tests/PHPStan/Analyser/data/bug-4711.php @@ -12,8 +12,8 @@ function x(string $string): void { return; } - assertType('array', explode($string, '')); - assertType('array', explode($string[0], '')); + assertType('array&nonEmpty', explode($string, '')); + assertType('array&nonEmpty', explode($string[0], '')); } } From a675aba54a712b7281d1a4c69379e561ee727383 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 7 Jul 2021 11:23:49 +0200 Subject: [PATCH 0011/1284] Fix tests --- tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php | 2 +- tests/PHPStan/Analyser/data/bug-3961.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 53ecc338aa..17f98104ae 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -8007,7 +8007,7 @@ public function dataExplode(): array '$arrayOrFalse', ], [ - PHP_VERSION_ID < 80000 ? 'array&nonEmpty|false' : 'array&nonEmpty', + PHP_VERSION_ID < 80000 ? '(array&nonEmpty)|false' : 'array&nonEmpty', '$anotherArrayOrFalse', ], [ diff --git a/tests/PHPStan/Analyser/data/bug-3961.php b/tests/PHPStan/Analyser/data/bug-3961.php index 5666786e33..e17706794d 100644 --- a/tests/PHPStan/Analyser/data/bug-3961.php +++ b/tests/PHPStan/Analyser/data/bug-3961.php @@ -14,8 +14,8 @@ public function doFoo(string $v, string $d, $m): void assertType('array', explode('.', $v, -2)); assertType('array&nonEmpty', explode('.', $v, 0)); assertType('array&nonEmpty', explode('.', $v, 1)); - assertType('array|false', explode($d, $v)); - assertType('(array|false)', explode($m, $v)); + assertType('(array&nonEmpty)|false', explode($d, $v)); + assertType('((array&nonEmpty)|false)', explode($m, $v)); } } From 3af172843b690a8de363e40765ca862459615568 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 7 Jul 2021 11:48:06 +0200 Subject: [PATCH 0012/1284] Fix accessory types isSuperTypeOf() --- .../Accessory/AccessoryNonEmptyStringType.php | 4 +++ .../Accessory/AccessoryNumericStringType.php | 4 +++ .../Analyser/data/non-empty-string.php | 2 +- .../Comparison/data/strict-comparison.php | 25 +++++++++++++++++++ 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/Type/Accessory/AccessoryNonEmptyStringType.php b/src/Type/Accessory/AccessoryNonEmptyStringType.php index e00d6841f3..479bffe81a 100644 --- a/src/Type/Accessory/AccessoryNonEmptyStringType.php +++ b/src/Type/Accessory/AccessoryNonEmptyStringType.php @@ -52,6 +52,10 @@ public function accepts(Type $type, bool $strictTypes): TrinaryLogic public function isSuperTypeOf(Type $type): TrinaryLogic { + if ($type instanceof CompoundType) { + return $type->isSubTypeOf($this); + } + if ($this->equals($type)) { return TrinaryLogic::createYes(); } diff --git a/src/Type/Accessory/AccessoryNumericStringType.php b/src/Type/Accessory/AccessoryNumericStringType.php index 3733c12eff..9d9cbd5ec0 100644 --- a/src/Type/Accessory/AccessoryNumericStringType.php +++ b/src/Type/Accessory/AccessoryNumericStringType.php @@ -52,6 +52,10 @@ public function accepts(Type $type, bool $strictTypes): TrinaryLogic public function isSuperTypeOf(Type $type): TrinaryLogic { + if ($type instanceof CompoundType) { + return $type->isSubTypeOf($this); + } + if ($this->equals($type)) { return TrinaryLogic::createYes(); } diff --git a/tests/PHPStan/Analyser/data/non-empty-string.php b/tests/PHPStan/Analyser/data/non-empty-string.php index e0915797d3..f3df86e1f9 100644 --- a/tests/PHPStan/Analyser/data/non-empty-string.php +++ b/tests/PHPStan/Analyser/data/non-empty-string.php @@ -110,7 +110,7 @@ public function doWithNumeric(string $s): void return; } - assertType('string&numeric', $s); + assertType('non-empty-string', $s); } } diff --git a/tests/PHPStan/Rules/Comparison/data/strict-comparison.php b/tests/PHPStan/Rules/Comparison/data/strict-comparison.php index 368aab3305..6eba9be0d5 100644 --- a/tests/PHPStan/Rules/Comparison/data/strict-comparison.php +++ b/tests/PHPStan/Rules/Comparison/data/strict-comparison.php @@ -949,3 +949,28 @@ public function test(int $key) } } } + +class ArrayWithNonEmptyStringValue +{ + + /** + * @param array $a + */ + public function doFoo(array $a): void + { + if ($a === []) { + + } + } + + /** + * @param array $a + */ + public function doBar(array $a): void + { + if ($a === []) { + + } + } + +} From 790a83849468a0bc9eac43cb1b9594032653e342 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 7 Jul 2021 13:10:48 +0200 Subject: [PATCH 0013/1284] !empty() leads to non-empty-string --- src/Analyser/TypeSpecifier.php | 52 +++++-------------- tests/PHPStan/Analyser/TypeSpecifierTest.php | 18 +++---- tests/PHPStan/Analyser/data/bug-3991.php | 2 +- .../Analyser/data/non-empty-string.php | 16 ++++++ .../Rules/Methods/CallMethodsRuleTest.php | 8 +++ tests/PHPStan/Rules/Methods/data/bug-5258.php | 23 ++++++++ .../Variables/DefinedVariableRuleTest.php | 6 +-- 7 files changed, 67 insertions(+), 58 deletions(-) create mode 100644 tests/PHPStan/Rules/Methods/data/bug-5258.php diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 34bcab31c5..5cbfd59ea6 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -24,7 +24,6 @@ use PHPStan\Type\Accessory\HasOffsetType; use PHPStan\Type\Accessory\HasPropertyType; use PHPStan\Type\Accessory\NonEmptyArrayType; -use PHPStan\Type\ArrayType; use PHPStan\Type\BooleanType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantIntegerType; @@ -675,20 +674,12 @@ public function specifyTypesInCondition( return $this->specifyTypesInCondition($scope->exitFirstLevelStatements(), $expr->var, $context); } elseif ( - ( - $expr instanceof Expr\Isset_ - && count($expr->vars) > 0 - && $context->true() - ) - || ($expr instanceof Expr\Empty_ && $context->false()) + $expr instanceof Expr\Isset_ + && count($expr->vars) > 0 + && $context->true() ) { $vars = []; - if ($expr instanceof Expr\Isset_) { - $varsToIterate = $expr->vars; - } else { - $varsToIterate = [$expr->expr]; - } - foreach ($varsToIterate as $var) { + foreach ($expr->vars as $var) { $tmpVars = [$var]; while ( @@ -722,21 +713,6 @@ public function specifyTypesInCondition( return new SpecifiedTypes([], []); } } - if ($expr instanceof Expr\Isset_) { - $type = $this->create($var, new NullType(), TypeSpecifierContext::createFalse(), false, $scope); - } else { - $type = $this->create( - $var, - new UnionType([ - new NullType(), - new ConstantBooleanType(false), - ]), - TypeSpecifierContext::createFalse(), - false, - $scope - ); - } - if ( $var instanceof ArrayDimFetch && $var->dim !== null @@ -749,8 +725,10 @@ public function specifyTypesInCondition( false, $scope )->unionWith( - $type + $this->create($var, new NullType(), TypeSpecifierContext::createFalse(), false, $scope) ); + } else { + $type = $this->create($var, new NullType(), TypeSpecifierContext::createFalse(), false, $scope); } if ( @@ -779,14 +757,6 @@ public function specifyTypesInCondition( } } - if ( - $expr instanceof Expr\Empty_ - && (new ArrayType(new MixedType(), new MixedType()))->isSuperTypeOf($scope->getType($expr->expr))->yes()) { - $types = $types->unionWith( - $this->create($expr->expr, new NonEmptyArrayType(), $context->negate(), false, $scope) - ); - } - return $types; } elseif ( $expr instanceof Expr\BinaryOp\Coalesce @@ -801,10 +771,12 @@ public function specifyTypesInCondition( $scope ); } elseif ( - $expr instanceof Expr\Empty_ && $context->truthy() - && (new ArrayType(new MixedType(), new MixedType()))->isSuperTypeOf($scope->getType($expr->expr))->yes() + $expr instanceof Expr\Empty_ ) { - return $this->create($expr->expr, new NonEmptyArrayType(), $context->negate(), false, $scope); + return $this->specifyTypesInCondition($scope, new BooleanOr( + new Expr\BooleanNot(new Expr\Isset_([$expr->expr])), + new Expr\BooleanNot($expr->expr) + ), $context); } elseif ($expr instanceof Expr\ErrorSuppress) { return $this->specifyTypesInCondition($scope, $expr->expr, $context); } elseif ( diff --git a/tests/PHPStan/Analyser/TypeSpecifierTest.php b/tests/PHPStan/Analyser/TypeSpecifierTest.php index b6bdb05a90..f71043a5e8 100644 --- a/tests/PHPStan/Analyser/TypeSpecifierTest.php +++ b/tests/PHPStan/Analyser/TypeSpecifierTest.php @@ -533,11 +533,9 @@ public function dataCondition(): array [ new Expr\BooleanNot(new Expr\Empty_(new Variable('stringOrNull'))), [ - '$stringOrNull' => '~false|null', - ], - [ - 'empty($stringOrNull)' => self::SURE_NOT_FALSEY, + '$stringOrNull' => '~0|0.0|\'\'|\'0\'|array()|false|null', ], + [], ], [ new Expr\BinaryOp\Identical( @@ -552,21 +550,17 @@ public function dataCondition(): array ], [ new Expr\Empty_(new Variable('array')), + [], [ - '$array' => '~nonEmpty', - ], - [ - '$array' => 'nonEmpty & ~false|null', + '$array' => '~0|0.0|\'\'|\'0\'|array()|false|null', ], ], [ new BooleanNot(new Expr\Empty_(new Variable('array'))), [ - '$array' => 'nonEmpty & ~false|null', - ], - [ - '$array' => '~nonEmpty', + '$array' => '~0|0.0|\'\'|\'0\'|array()|false|null', ], + [], ], [ new FuncCall(new Name('count'), [ diff --git a/tests/PHPStan/Analyser/data/bug-3991.php b/tests/PHPStan/Analyser/data/bug-3991.php index 0a801da541..aee3f87ac1 100644 --- a/tests/PHPStan/Analyser/data/bug-3991.php +++ b/tests/PHPStan/Analyser/data/bug-3991.php @@ -22,7 +22,7 @@ public static function email($config = null) assertType('array|stdClass|null', $config); $config = new \stdClass(); } elseif (! (is_array($config) || $config instanceof \stdClass)) { - assertNativeType('mixed~array|stdClass|false|null', $config); + assertNativeType('mixed~0|0.0|\'\'|\'0\'|array()|stdClass|false|null', $config); assertType('*NEVER*', $config); } diff --git a/tests/PHPStan/Analyser/data/non-empty-string.php b/tests/PHPStan/Analyser/data/non-empty-string.php index f3df86e1f9..9d3469ad3d 100644 --- a/tests/PHPStan/Analyser/data/non-empty-string.php +++ b/tests/PHPStan/Analyser/data/non-empty-string.php @@ -113,4 +113,20 @@ public function doWithNumeric(string $s): void assertType('non-empty-string', $s); } + public function doEmpty(string $s): void + { + if (empty($s)) { + return; + } + + assertType('non-empty-string', $s); + } + + public function doEmpty2(string $s): void + { + if (!empty($s)) { + assertType('non-empty-string', $s); + } + } + } diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index d4f2b98472..0c7ead3a27 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -1987,4 +1987,12 @@ public function testBug4844(): void $this->analyse([__DIR__ . '/data/bug-4844.php'], []); } + public function testBug5258(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/bug-5258.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-5258.php b/tests/PHPStan/Rules/Methods/data/bug-5258.php new file mode 100644 index 0000000000..27a751f859 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-5258.php @@ -0,0 +1,23 @@ +method2($params); + + if (!empty($params['other_key'])) $this->method2($params); + } + + /** + * @param array{other_key:string} $params + **/ + public function method2(array$params): void + { + } +} diff --git a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php index c481973ded..9a3c6b606a 100644 --- a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php +++ b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php @@ -107,16 +107,12 @@ public function testDefinedVariables(): void 'Undefined variable: $variableInEmpty', 145, ], - [ - 'Undefined variable: $negatedVariableInEmpty', - 152, - ], [ 'Undefined variable: $variableInEmpty', 155, ], [ - 'Undefined variable: $negatedVariableInEmpty', + 'Variable $negatedVariableInEmpty might not be defined.', 156, ], [ From d50aa7dcac0c76fcaad839821e7537cd55c71e5a Mon Sep 17 00:00:00 2001 From: fluffycondor <62219548+fluffycondor@users.noreply.github.com> Date: Wed, 7 Jul 2021 14:53:23 +0300 Subject: [PATCH 0014/1284] Improve `sscanf()` signature --- resources/functionMap.php | 3 ++- tests/PHPStan/Analyser/NodeScopeResolverTest.php | 1 + tests/PHPStan/Analyser/data/sscanf.php | 6 ++++++ 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Analyser/data/sscanf.php diff --git a/resources/functionMap.php b/resources/functionMap.php index 7edc906748..430818ed37 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -11546,7 +11546,8 @@ 'sqlsrv_server_info' => ['array', 'conn'=>'resource'], 'sqrt' => ['float', 'number'=>'float'], 'srand' => ['void', 'seed='=>'int', 'mode='=>'int'], -'sscanf' => ['mixed', 'str'=>'string', 'format'=>'string', '&...w_vars='=>'string|int|float|null'], +'sscanf' => ['int|null', 'str'=>'string', 'format'=>'string', '&w_war'=>'string|int|float|null', '&...w_vars='=>'string|int|float|null'], +'sscanf\'1' => ['array|null', 'str'=>'string', 'format'=>'string'], 'ssdeep_fuzzy_compare' => ['int', 'signature1'=>'string', 'signature2'=>'string'], 'ssdeep_fuzzy_hash' => ['string', 'to_hash'=>'string'], 'ssdeep_fuzzy_hash_filename' => ['string', 'file_name'=>'string'], diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 3862e935bd..a2629cf109 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -435,6 +435,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/non-empty-string.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3981.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4711.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/sscanf.php'); } /** diff --git a/tests/PHPStan/Analyser/data/sscanf.php b/tests/PHPStan/Analyser/data/sscanf.php new file mode 100644 index 0000000000..2b7105f939 --- /dev/null +++ b/tests/PHPStan/Analyser/data/sscanf.php @@ -0,0 +1,6 @@ + Date: Wed, 7 Jul 2021 18:39:31 +0200 Subject: [PATCH 0015/1284] Test current checkPhpDocMissingReturn behavior --- .../Rules/Missing/MissingReturnRuleTest.php | 97 ++++++++++++++++++- .../data/check-phpdoc-missing-return.php | 90 +++++++++++++++++ 2 files changed, 186 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Rules/Missing/data/check-phpdoc-missing-return.php diff --git a/tests/PHPStan/Rules/Missing/MissingReturnRuleTest.php b/tests/PHPStan/Rules/Missing/MissingReturnRuleTest.php index 5966096e2f..fb23e30270 100644 --- a/tests/PHPStan/Rules/Missing/MissingReturnRuleTest.php +++ b/tests/PHPStan/Rules/Missing/MissingReturnRuleTest.php @@ -14,9 +14,12 @@ class MissingReturnRuleTest extends RuleTestCase /** @var bool */ private $checkExplicitMixedMissingReturn; + /** @var bool */ + private $checkPhpDocMissingReturn = true; + protected function getRule(): Rule { - return new MissingReturnRule($this->checkExplicitMixedMissingReturn, true); + return new MissingReturnRule($this->checkExplicitMixedMissingReturn, $this->checkPhpDocMissingReturn); } public function testRule(): void @@ -143,4 +146,96 @@ public function testBug3669(): void $this->analyse([__DIR__ . '/data/bug-3669.php'], []); } + public function dataCheckPhpDocMissingReturn(): array + { + return [ + [ + true, + [ + [ + 'Method CheckPhpDocMissingReturn\Foo::doFoo() should return int|string but return statement is missing.', + 11, + ], + [ + 'Method CheckPhpDocMissingReturn\Foo::doFoo2() should return string|null but return statement is missing.', + 19, + ], + [ + 'Method CheckPhpDocMissingReturn\Foo::doFoo3() should return int|string but return statement is missing.', + 29, + ], + [ + 'Method CheckPhpDocMissingReturn\Foo::doFoo4() should return string|null but return statement is missing.', + 39, + ], + [ + 'Method CheckPhpDocMissingReturn\Foo::doFoo5() should return mixed but return statement is missing.', + 49, + ], + [ + 'Method CheckPhpDocMissingReturn\Bar::doFoo() should return int|string but return statement is missing.', + 59, + ], + [ + 'Method CheckPhpDocMissingReturn\Bar::doFoo2() should return string|null but return statement is missing.', + 64, + ], + [ + 'Method CheckPhpDocMissingReturn\Bar::doFoo3() should return int|string but return statement is missing.', + 71, + ], + [ + 'Method CheckPhpDocMissingReturn\Bar::doFoo4() should return string|null but return statement is missing.', + 78, + ], + [ + 'Method CheckPhpDocMissingReturn\Bar::doFoo5() should return mixed but return statement is missing.', + 85, + ], + ], + ], + [ + false, + [ + [ + 'Method CheckPhpDocMissingReturn\Bar::doFoo() should return int|string but return statement is missing.', + 59, + ], + [ + 'Method CheckPhpDocMissingReturn\Bar::doFoo2() should return string|null but return statement is missing.', + 64, + ], + [ + 'Method CheckPhpDocMissingReturn\Bar::doFoo3() should return int|string but return statement is missing.', + 71, + ], + [ + 'Method CheckPhpDocMissingReturn\Bar::doFoo4() should return string|null but return statement is missing.', + 78, + ], + [ + 'Method CheckPhpDocMissingReturn\Bar::doFoo5() should return mixed but return statement is missing.', + 85, + ], + ], + ], + ]; + } + + /** + * @dataProvider dataCheckPhpDocMissingReturn + * @param bool $checkPhpDocMissingReturn + * @param mixed[] $errors + */ + public function testCheckPhpDocMissingReturn(bool $checkPhpDocMissingReturn, array $errors): void + { + if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->checkExplicitMixedMissingReturn = true; + $this->checkPhpDocMissingReturn = $checkPhpDocMissingReturn; + $this->analyse([__DIR__ . '/data/check-phpdoc-missing-return.php'], $errors); + } + } diff --git a/tests/PHPStan/Rules/Missing/data/check-phpdoc-missing-return.php b/tests/PHPStan/Rules/Missing/data/check-phpdoc-missing-return.php new file mode 100644 index 0000000000..126b910f05 --- /dev/null +++ b/tests/PHPStan/Rules/Missing/data/check-phpdoc-missing-return.php @@ -0,0 +1,90 @@ += 8.0 + +namespace CheckPhpDocMissingReturn; + +class Foo +{ + + /** + * @return string|int + */ + public function doFoo() + { + + } + + /** + * @return string|null + */ + public function doFoo2() + { + + } + + /** + * @return string|int + */ + public function doFoo3() + { + if (rand()) { + return 'foo'; + } + } + + /** + * @return string|null + */ + public function doFoo4() + { + if (rand()) { + return 'foo'; + } + } + + /** + * @return mixed + */ + public function doFoo5() + { + if (rand()) { + return 'foo'; + } + } + +} + +class Bar +{ + + public function doFoo(): string|int + { + + } + + public function doFoo2(): ?string + { + + } + + public function doFoo3(): string|int + { + if (rand()) { + return 'foo'; + } + } + + public function doFoo4(): ?string + { + if (rand()) { + return 'foo'; + } + } + + public function doFoo5(): mixed + { + if (rand()) { + return 'foo'; + } + } + +} From 26560811765f0a93462180526b552834966c161b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 7 Jul 2021 18:51:22 +0200 Subject: [PATCH 0016/1284] MissingReturnRule - make behavior with checkPhpDocMissingReturn: false more strict --- src/Rules/Missing/MissingReturnRule.php | 7 ++++++- .../PHPStan/Rules/Missing/MissingReturnRuleTest.php | 12 ++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/Rules/Missing/MissingReturnRule.php b/src/Rules/Missing/MissingReturnRule.php index 7074f4fb88..ffa7b2759a 100644 --- a/src/Rules/Missing/MissingReturnRule.php +++ b/src/Rules/Missing/MissingReturnRule.php @@ -13,6 +13,7 @@ use PHPStan\Type\GenericTypeVariableResolver; use PHPStan\Type\MixedType; use PHPStan\Type\NeverType; +use PHPStan\Type\TypeCombinator; use PHPStan\Type\TypeWithClassName; use PHPStan\Type\VerbosityLevel; use PHPStan\Type\VoidType; @@ -96,7 +97,11 @@ public function processNode(Node $node, Scope $scope): array return []; } - if (!$node->hasNativeReturnTypehint() && !$this->checkPhpDocMissingReturn) { + if ( + !$node->hasNativeReturnTypehint() + && !$this->checkPhpDocMissingReturn + && TypeCombinator::containsNull($returnType) + ) { return []; } diff --git a/tests/PHPStan/Rules/Missing/MissingReturnRuleTest.php b/tests/PHPStan/Rules/Missing/MissingReturnRuleTest.php index fb23e30270..aaa8cf8cc6 100644 --- a/tests/PHPStan/Rules/Missing/MissingReturnRuleTest.php +++ b/tests/PHPStan/Rules/Missing/MissingReturnRuleTest.php @@ -197,6 +197,18 @@ public function dataCheckPhpDocMissingReturn(): array [ false, [ + [ + 'Method CheckPhpDocMissingReturn\Foo::doFoo() should return int|string but return statement is missing.', + 11, + ], + [ + 'Method CheckPhpDocMissingReturn\Foo::doFoo3() should return int|string but return statement is missing.', + 29, + ], + [ + 'Method CheckPhpDocMissingReturn\Foo::doFoo5() should return mixed but return statement is missing.', + 49, + ], [ 'Method CheckPhpDocMissingReturn\Bar::doFoo() should return int|string but return statement is missing.', 59, From 858194321b6ddff46adadf2edaa5b0a9e987e248 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 8 Jul 2021 09:29:39 +0200 Subject: [PATCH 0017/1284] Too wide return typehint rules - accommodate missing return with non-native nullable return type --- src/Analyser/NodeScopeResolver.php | 20 ++++++-- src/Node/FunctionReturnStatementsNode.php | 21 ++++++++- src/Node/MethodReturnStatementsNode.php | 21 ++++++++- .../TooWideFunctionReturnTypehintRule.php | 11 +++++ .../TooWideMethodReturnTypehintRule.php | 10 ++++ .../TooWideFunctionReturnTypehintRuleTest.php | 12 +++++ .../TooWideMethodReturnTypehintRuleTest.php | 12 +++++ .../data/tooWideFunctionReturnType.php | 46 +++++++++++++++++++ .../data/tooWideMethodReturnType-private.php | 46 +++++++++++++++++++ 9 files changed, 193 insertions(+), 6 deletions(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index eb1a1b497d..36dfc08262 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -445,7 +445,8 @@ private function processStmtNode( $nodeCallback(new InFunctionNode($stmt), $functionScope); $gatheredReturnStatements = []; - $statementResult = $this->processStmtNodes($stmt, $stmt->stmts, $functionScope, static function (\PhpParser\Node $node, Scope $scope) use ($nodeCallback, $functionScope, &$gatheredReturnStatements): void { + $executionEnds = []; + $statementResult = $this->processStmtNodes($stmt, $stmt->stmts, $functionScope, static function (\PhpParser\Node $node, Scope $scope) use ($nodeCallback, $functionScope, &$gatheredReturnStatements, &$executionEnds): void { $nodeCallback($node, $scope); if ($scope->getFunction() !== $functionScope->getFunction()) { return; @@ -453,6 +454,10 @@ private function processStmtNode( if ($scope->isInAnonymousFunction()) { return; } + if ($node instanceof ExecutionEndNode) { + $executionEnds[] = $node; + return; + } if (!$node instanceof Return_) { return; } @@ -463,7 +468,8 @@ private function processStmtNode( $nodeCallback(new FunctionReturnStatementsNode( $stmt, $gatheredReturnStatements, - $statementResult + $statementResult, + $executionEnds ), $functionScope); } elseif ($stmt instanceof Node\Stmt\ClassMethod) { $hasYield = false; @@ -529,7 +535,8 @@ private function processStmtNode( if ($stmt->stmts !== null) { $gatheredReturnStatements = []; - $statementResult = $this->processStmtNodes($stmt, $stmt->stmts, $methodScope, static function (\PhpParser\Node $node, Scope $scope) use ($nodeCallback, $methodScope, &$gatheredReturnStatements): void { + $executionEnds = []; + $statementResult = $this->processStmtNodes($stmt, $stmt->stmts, $methodScope, static function (\PhpParser\Node $node, Scope $scope) use ($nodeCallback, $methodScope, &$gatheredReturnStatements, &$executionEnds): void { $nodeCallback($node, $scope); if ($scope->getFunction() !== $methodScope->getFunction()) { return; @@ -537,6 +544,10 @@ private function processStmtNode( if ($scope->isInAnonymousFunction()) { return; } + if ($node instanceof ExecutionEndNode) { + $executionEnds[] = $node; + return; + } if (!$node instanceof Return_) { return; } @@ -546,7 +557,8 @@ private function processStmtNode( $nodeCallback(new MethodReturnStatementsNode( $stmt, $gatheredReturnStatements, - $statementResult + $statementResult, + $executionEnds ), $methodScope); } } elseif ($stmt instanceof Echo_) { diff --git a/src/Node/FunctionReturnStatementsNode.php b/src/Node/FunctionReturnStatementsNode.php index 0114cd87f5..e4db4d265d 100644 --- a/src/Node/FunctionReturnStatementsNode.php +++ b/src/Node/FunctionReturnStatementsNode.php @@ -17,21 +17,27 @@ class FunctionReturnStatementsNode extends NodeAbstract implements ReturnStateme private StatementResult $statementResult; + /** @var ExecutionEndNode[] */ + private array $executionEnds; + /** * @param \PhpParser\Node\Stmt\Function_ $function * @param \PHPStan\Node\ReturnStatement[] $returnStatements * @param \PHPStan\Analyser\StatementResult $statementResult + * @param ExecutionEndNode[] $executionEnds */ public function __construct( Function_ $function, array $returnStatements, - StatementResult $statementResult + StatementResult $statementResult, + array $executionEnds ) { parent::__construct($function->getAttributes()); $this->function = $function; $this->returnStatements = $returnStatements; $this->statementResult = $statementResult; + $this->executionEnds = $executionEnds; } /** @@ -47,11 +53,24 @@ public function getStatementResult(): StatementResult return $this->statementResult; } + /** + * @return ExecutionEndNode[] + */ + public function getExecutionEnds(): array + { + return $this->executionEnds; + } + public function returnsByRef(): bool { return $this->function->byRef; } + public function hasNativeReturnTypehint(): bool + { + return $this->function->returnType !== null; + } + public function getType(): string { return 'PHPStan_Node_FunctionReturnStatementsNode'; diff --git a/src/Node/MethodReturnStatementsNode.php b/src/Node/MethodReturnStatementsNode.php index 7367f0fc72..da1cb08c19 100644 --- a/src/Node/MethodReturnStatementsNode.php +++ b/src/Node/MethodReturnStatementsNode.php @@ -17,21 +17,27 @@ class MethodReturnStatementsNode extends NodeAbstract implements ReturnStatement private StatementResult $statementResult; + /** @var ExecutionEndNode[] */ + private array $executionEnds; + /** * @param \PhpParser\Node\Stmt\ClassMethod $method * @param \PHPStan\Node\ReturnStatement[] $returnStatements * @param \PHPStan\Analyser\StatementResult $statementResult + * @param ExecutionEndNode[] $executionEnds */ public function __construct( ClassMethod $method, array $returnStatements, - StatementResult $statementResult + StatementResult $statementResult, + array $executionEnds ) { parent::__construct($method->getAttributes()); $this->classMethod = $method; $this->returnStatements = $returnStatements; $this->statementResult = $statementResult; + $this->executionEnds = $executionEnds; } /** @@ -47,11 +53,24 @@ public function getStatementResult(): StatementResult return $this->statementResult; } + /** + * @return ExecutionEndNode[] + */ + public function getExecutionEnds(): array + { + return $this->executionEnds; + } + public function returnsByRef(): bool { return $this->classMethod->byRef; } + public function hasNativeReturnTypehint(): bool + { + return $this->classMethod->returnType !== null; + } + public function getType(): string { return 'PHPStan_Node_FunctionReturnStatementsNode'; diff --git a/src/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRule.php b/src/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRule.php index 827d6d9490..ea35670a56 100644 --- a/src/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRule.php +++ b/src/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRule.php @@ -9,6 +9,7 @@ use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; +use PHPStan\Type\NullType; use PHPStan\Type\TypeCombinator; use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; @@ -67,6 +68,16 @@ public function processNode(Node $node, Scope $scope): array continue; } + if ($type instanceof NullType && !$node->hasNativeReturnTypehint()) { + foreach ($node->getExecutionEnds() as $executionEnd) { + if ($executionEnd->getStatementResult()->isAlwaysTerminating()) { + continue; + } + + continue 2; + } + } + $messages[] = RuleErrorBuilder::message(sprintf( 'Function %s() never returns %s so it can be removed from the return typehint.', $function->getName(), diff --git a/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php b/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php index 5b1210ff9b..dfe18c0b08 100644 --- a/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php +++ b/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php @@ -92,6 +92,16 @@ public function processNode(Node $node, Scope $scope): array continue; } + if ($type instanceof NullType && !$node->hasNativeReturnTypehint()) { + foreach ($node->getExecutionEnds() as $executionEnd) { + if ($executionEnd->getStatementResult()->isAlwaysTerminating()) { + continue; + } + + continue 2; + } + } + $messages[] = RuleErrorBuilder::message(sprintf( 'Method %s::%s() never returns %s so it can be removed from the return typehint.', $method->getDeclaringClass()->getDisplayName(), diff --git a/tests/PHPStan/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRuleTest.php b/tests/PHPStan/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRuleTest.php index 916cc3cefd..dc6921b40e 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRuleTest.php @@ -32,6 +32,18 @@ public function testRule(): void 'Function TooWideFunctionReturnType\ipsum() never returns null so it can be removed from the return typehint.', 27, ], + [ + 'Function TooWideFunctionReturnType\dolor2() never returns null so it can be removed from the return typehint.', + 41, + ], + [ + 'Function TooWideFunctionReturnType\dolor4() never returns int so it can be removed from the return typehint.', + 59, + ], + [ + 'Function TooWideFunctionReturnType\dolor6() never returns null so it can be removed from the return typehint.', + 79, + ], ]); } diff --git a/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php b/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php index 6ed2a9cf44..bc5e952219 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php @@ -31,6 +31,18 @@ public function testPrivate(): void 'Method TooWideMethodReturnType\Foo::dolor() never returns null so it can be removed from the return typehint.', 34, ], + [ + 'Method TooWideMethodReturnType\Foo::dolor2() never returns null so it can be removed from the return typehint.', + 48, + ], + [ + 'Method TooWideMethodReturnType\Foo::dolor4() never returns int so it can be removed from the return typehint.', + 66, + ], + [ + 'Method TooWideMethodReturnType\Foo::dolor6() never returns null so it can be removed from the return typehint.', + 86, + ], ]); } diff --git a/tests/PHPStan/Rules/TooWideTypehints/data/tooWideFunctionReturnType.php b/tests/PHPStan/Rules/TooWideTypehints/data/tooWideFunctionReturnType.php index 513870cff7..8c55f35252 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/data/tooWideFunctionReturnType.php +++ b/tests/PHPStan/Rules/TooWideTypehints/data/tooWideFunctionReturnType.php @@ -37,3 +37,49 @@ public function doFoo() { return 'str'; } + +function dolor2(): ?string { + if (rand()) { + return 'foo'; + } +} + +/** + * @return string|null + */ +function dolor3() { + if (rand()) { + return 'foo'; + } +} + +/** + * @return string|int + */ +function dolor4() { + if (rand()) { + return 'foo'; + } +} + +/** + * @return string|null + */ +function dolor5() { + if (rand()) { + return 'foo'; + } + + return null; +} + +/** + * @return string|null + */ +function dolor6() { + if (rand()) { + return 'foo'; + } + + return 'bar'; +} diff --git a/tests/PHPStan/Rules/TooWideTypehints/data/tooWideMethodReturnType-private.php b/tests/PHPStan/Rules/TooWideTypehints/data/tooWideMethodReturnType-private.php index 475b2319a5..aa3f6259a6 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/data/tooWideMethodReturnType-private.php +++ b/tests/PHPStan/Rules/TooWideTypehints/data/tooWideMethodReturnType-private.php @@ -45,4 +45,50 @@ public function doFoo() { return 'str'; } + private function dolor2(): ?string { + if (rand()) { + return 'foo'; + } + } + + /** + * @return string|null + */ + private function dolor3() { + if (rand()) { + return 'foo'; + } + } + + /** + * @return string|int + */ + private function dolor4() { + if (rand()) { + return 'foo'; + } + } + + /** + * @return string|null + */ + private function dolor5() { + if (rand()) { + return 'foo'; + } + + return null; + } + + /** + * @return string|null + */ + private function dolor6() { + if (rand()) { + return 'foo'; + } + + return 'bar'; + } + } From f27f5800783748943bf39b2b8b59157f98d6a574 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 8 Jul 2021 10:01:46 +0200 Subject: [PATCH 0018/1284] Fix tests --- tests/PHPStan/Levels/data/missingReturn-0.json | 5 +++++ tests/PHPStan/Levels/data/missingReturn-2.json | 7 ------- .../Levels/data/{returnTypes-2.json => returnTypes-0.json} | 0 3 files changed, 5 insertions(+), 7 deletions(-) delete mode 100644 tests/PHPStan/Levels/data/missingReturn-2.json rename tests/PHPStan/Levels/data/{returnTypes-2.json => returnTypes-0.json} (100%) diff --git a/tests/PHPStan/Levels/data/missingReturn-0.json b/tests/PHPStan/Levels/data/missingReturn-0.json index d11943ca76..222473943d 100644 --- a/tests/PHPStan/Levels/data/missingReturn-0.json +++ b/tests/PHPStan/Levels/data/missingReturn-0.json @@ -3,5 +3,10 @@ "message": "Method Levels\\MissingReturn\\Foo::doFoo() should return int but return statement is missing.", "line": 8, "ignorable": true + }, + { + "message": "Method Levels\\MissingReturn\\Foo::doBar() should return int but return statement is missing.", + "line": 16, + "ignorable": true } ] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/missingReturn-2.json b/tests/PHPStan/Levels/data/missingReturn-2.json deleted file mode 100644 index 3a4788f45d..0000000000 --- a/tests/PHPStan/Levels/data/missingReturn-2.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - { - "message": "Method Levels\\MissingReturn\\Foo::doBar() should return int but return statement is missing.", - "line": 16, - "ignorable": true - } -] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/returnTypes-2.json b/tests/PHPStan/Levels/data/returnTypes-0.json similarity index 100% rename from tests/PHPStan/Levels/data/returnTypes-2.json rename to tests/PHPStan/Levels/data/returnTypes-0.json From 2af6aef968263faf5598b53c5eb08d78ab938e80 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 8 Jul 2021 15:00:43 +0200 Subject: [PATCH 0019/1284] Test generic offsetGet() --- .../Analyser/NodeScopeResolverTest.php | 1 + .../Analyser/data/generic-offset-get.php | 42 +++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 tests/PHPStan/Analyser/data/generic-offset-get.php diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index a2629cf109..5e5217414a 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -436,6 +436,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3981.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4711.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/sscanf.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/generic-offset-get.php'); } /** diff --git a/tests/PHPStan/Analyser/data/generic-offset-get.php b/tests/PHPStan/Analyser/data/generic-offset-get.php new file mode 100644 index 0000000000..7243b96576 --- /dev/null +++ b/tests/PHPStan/Analyser/data/generic-offset-get.php @@ -0,0 +1,42 @@ + $offset + * @return T + */ + public function offsetGet($offset) + { + + } + + public function offsetSet($offset, $value) + { + + } + + public function offsetUnset($offset) + { + + } + +} + +function (Foo $foo): void { + assertType(stdClass::class, $foo->offsetGet(stdClass::class)); + assertType(stdClass::class, $foo[stdClass::class]); +}; From fb23cb8a87c6345de035eb83f197c0258b5fd2c4 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 8 Jul 2021 16:59:47 +0200 Subject: [PATCH 0020/1284] ClassAncestorsRule - refactoring to use virtual InClassNode --- src/Rules/Generics/ClassAncestorsRule.php | 25 +++++++++++++++-------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/Rules/Generics/ClassAncestorsRule.php b/src/Rules/Generics/ClassAncestorsRule.php index d6087dbfe7..7ae6d37b74 100644 --- a/src/Rules/Generics/ClassAncestorsRule.php +++ b/src/Rules/Generics/ClassAncestorsRule.php @@ -4,6 +4,7 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\Node\InClassNode; use PHPStan\PhpDoc\Tag\ExtendsTag; use PHPStan\PhpDoc\Tag\ImplementsTag; use PHPStan\Rules\Rule; @@ -11,7 +12,7 @@ use PHPStan\Type\Type; /** - * @implements \PHPStan\Rules\Rule<\PhpParser\Node\Stmt\Class_> + * @implements \PHPStan\Rules\Rule */ class ClassAncestorsRule implements Rule { @@ -31,21 +32,27 @@ public function __construct( public function getNodeType(): string { - return Node\Stmt\Class_::class; + return InClassNode::class; } public function processNode(Node $node, Scope $scope): array { - if (!isset($node->namespacedName)) { - // anonymous class + $originalNode = $node->getOriginalNode(); + if (!$originalNode instanceof Node\Stmt\Class_) { return []; } - - $className = (string) $node->namespacedName; + if (!$scope->isInClass()) { + return []; + } + $classReflection = $scope->getClassReflection(); + if ($classReflection->isAnonymous()) { + return []; + } + $className = $classReflection->getName(); $extendsTags = []; $implementsTags = []; - $docComment = $node->getDocComment(); + $docComment = $originalNode->getDocComment(); if ($docComment !== null) { $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( $scope->getFile(), @@ -59,7 +66,7 @@ public function processNode(Node $node, Scope $scope): array } $extendsErrors = $this->genericAncestorsCheck->check( - $node->extends !== null ? [$node->extends] : [], + $originalNode->extends !== null ? [$originalNode->extends] : [], array_map(static function (ExtendsTag $tag): Type { return $tag->getType(); }, $extendsTags), @@ -76,7 +83,7 @@ public function processNode(Node $node, Scope $scope): array ); $implementsErrors = $this->genericAncestorsCheck->check( - $node->implements, + $originalNode->implements, array_map(static function (ImplementsTag $tag): Type { return $tag->getType(); }, $implementsTags), From 1443c5efa1de40af6bbfebb1958bc9462a81e2ed Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 8 Jul 2021 17:02:02 +0200 Subject: [PATCH 0021/1284] InterfaceAncestorsRule - refactoring to use virtual InClassNode --- src/Rules/Generics/InterfaceAncestorsRule.php | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/Rules/Generics/InterfaceAncestorsRule.php b/src/Rules/Generics/InterfaceAncestorsRule.php index 5a7f294113..7aca41e13b 100644 --- a/src/Rules/Generics/InterfaceAncestorsRule.php +++ b/src/Rules/Generics/InterfaceAncestorsRule.php @@ -4,6 +4,7 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\Node\InClassNode; use PHPStan\PhpDoc\Tag\ExtendsTag; use PHPStan\PhpDoc\Tag\ImplementsTag; use PHPStan\Rules\Rule; @@ -11,7 +12,7 @@ use PHPStan\Type\Type; /** - * @implements \PHPStan\Rules\Rule<\PhpParser\Node\Stmt\Interface_> + * @implements \PHPStan\Rules\Rule */ class InterfaceAncestorsRule implements Rule { @@ -31,19 +32,24 @@ public function __construct( public function getNodeType(): string { - return Node\Stmt\Interface_::class; + return InClassNode::class; } public function processNode(Node $node, Scope $scope): array { - if (!isset($node->namespacedName)) { - throw new \PHPStan\ShouldNotHappenException(); + $originalNode = $node->getOriginalNode(); + if (!$originalNode instanceof Node\Stmt\Interface_) { + return []; } + if (!$scope->isInClass()) { + return []; + } + $classReflection = $scope->getClassReflection(); - $interfaceName = (string) $node->namespacedName; + $interfaceName = $classReflection->getName(); $extendsTags = []; $implementsTags = []; - $docComment = $node->getDocComment(); + $docComment = $originalNode->getDocComment(); if ($docComment !== null) { $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( $scope->getFile(), @@ -57,7 +63,7 @@ public function processNode(Node $node, Scope $scope): array } $extendsErrors = $this->genericAncestorsCheck->check( - $node->extends, + $originalNode->extends, array_map(static function (ExtendsTag $tag): Type { return $tag->getType(); }, $extendsTags), From 284af50d76210a449f210699732171ecacf3307f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 8 Jul 2021 15:43:42 +0200 Subject: [PATCH 0022/1284] Bleeding edge - cross-check generic interface implementations --- conf/bleedingEdge.neon | 1 + conf/config.level2.neon | 14 +++- conf/config.neon | 8 ++- src/PhpDoc/StubValidator.php | 12 +++- src/Reflection/ClassReflection.php | 8 +-- src/Rules/Generics/ClassAncestorsRule.php | 16 ++++- .../Generics/CrossCheckInterfacesHelper.php | 69 +++++++++++++++++++ src/Rules/Generics/InterfaceAncestorsRule.php | 16 ++++- .../Rules/Generics/ClassAncestorsRuleTest.php | 14 +++- .../Generics/InterfaceAncestorsRuleTest.php | 14 +++- .../cross-check-interfaces-interfaces.php | 30 ++++++++ .../Generics/data/cross-check-interfaces.php | 36 ++++++++++ 12 files changed, 224 insertions(+), 14 deletions(-) create mode 100644 src/Rules/Generics/CrossCheckInterfacesHelper.php create mode 100644 tests/PHPStan/Rules/Generics/data/cross-check-interfaces-interfaces.php create mode 100644 tests/PHPStan/Rules/Generics/data/cross-check-interfaces.php diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 3baea6e50c..1742270d01 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -22,5 +22,6 @@ parameters: deepInspectTypes: true neverInGenericReturnType: true validateOverridingMethodsInStubs: true + crossCheckInterfaces: true stubFiles: - ../stubs/arrayFunctions.stub diff --git a/conf/config.level2.neon b/conf/config.level2.neon index c284232e28..857d972c53 100644 --- a/conf/config.level2.neon +++ b/conf/config.level2.neon @@ -13,11 +13,9 @@ rules: - PHPStan\Rules\Cast\PrintRule - PHPStan\Rules\Comparison\UsageOfVoidMatchExpressionRule - PHPStan\Rules\Functions\IncompatibleDefaultParameterTypeRule - - PHPStan\Rules\Generics\ClassAncestorsRule - PHPStan\Rules\Generics\ClassTemplateTypeRule - PHPStan\Rules\Generics\FunctionTemplateTypeRule - PHPStan\Rules\Generics\FunctionSignatureVarianceRule - - PHPStan\Rules\Generics\InterfaceAncestorsRule - PHPStan\Rules\Generics\InterfaceTemplateTypeRule - PHPStan\Rules\Generics\MethodTemplateTypeRule - PHPStan\Rules\Generics\MethodSignatureVarianceRule @@ -46,6 +44,18 @@ services: reportMaybes: %reportMaybes% tags: - phpstan.rules.rule + - + class: PHPStan\Rules\Generics\ClassAncestorsRule + arguments: + crossCheckInterfaces: %featureToggles.crossCheckInterfaces% + tags: + - phpstan.rules.rule + - + class: PHPStan\Rules\Generics\InterfaceAncestorsRule + arguments: + crossCheckInterfaces: %featureToggles.crossCheckInterfaces% + tags: + - phpstan.rules.rule - class: PHPStan\Rules\PhpDoc\InvalidPhpDocVarTagTypeRule arguments: diff --git a/conf/config.neon b/conf/config.neon index f6c5f7fc49..3ca2493c7b 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -45,6 +45,7 @@ parameters: deepInspectTypes: false neverInGenericReturnType: false validateOverridingMethodsInStubs: false + crossCheckInterfaces: false fileExtensions: - php checkAlwaysTrueCheckTypeFunctionCall: false @@ -217,7 +218,8 @@ parametersSchema: apiRules: bool(), deepInspectTypes: bool(), neverInGenericReturnType: bool(), - validateOverridingMethodsInStubs: bool() + validateOverridingMethodsInStubs: bool(), + crossCheckInterfaces: bool() ]) fileExtensions: listOf(string()) checkAlwaysTrueCheckTypeFunctionCall: bool() @@ -422,6 +424,7 @@ services: class: PHPStan\PhpDoc\StubValidator arguments: validateOverridingMethods: %featureToggles.validateOverridingMethodsInStubs% + crossCheckInterfaces: %featureToggles.crossCheckInterfaces% - class: PHPStan\Analyser\Analyser @@ -814,6 +817,9 @@ services: - class: PHPStan\Rules\FunctionReturnTypeCheck + - + class: PHPStan\Rules\Generics\CrossCheckInterfacesHelper + - class: PHPStan\Rules\Generics\GenericAncestorsCheck arguments: diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index 206bc3d908..6e619f5b8a 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -22,6 +22,7 @@ use PHPStan\Rules\Functions\MissingFunctionReturnTypehintRule; use PHPStan\Rules\Generics\ClassAncestorsRule; use PHPStan\Rules\Generics\ClassTemplateTypeRule; +use PHPStan\Rules\Generics\CrossCheckInterfacesHelper; use PHPStan\Rules\Generics\FunctionSignatureVarianceRule; use PHPStan\Rules\Generics\FunctionTemplateTypeRule; use PHPStan\Rules\Generics\GenericAncestorsCheck; @@ -57,13 +58,17 @@ class StubValidator private bool $validateOverridingMethods; + private bool $crossCheckInterfaces; + public function __construct( DerivativeContainerFactory $derivativeContainerFactory, - bool $validateOverridingMethods + bool $validateOverridingMethods, + bool $crossCheckInterfaces ) { $this->derivativeContainerFactory = $derivativeContainerFactory; $this->validateOverridingMethods = $validateOverridingMethods; + $this->crossCheckInterfaces = $crossCheckInterfaces; } /** @@ -133,6 +138,7 @@ private function getRuleRegistry(Container $container): Registry $functionDefinitionCheck = $container->getByType(FunctionDefinitionCheck::class); $missingTypehintCheck = $container->getByType(MissingTypehintCheck::class); $unresolvableTypeHelper = $container->getByType(UnresolvableTypeHelper::class); + $crossCheckInterfacesHelper = $container->getByType(CrossCheckInterfacesHelper::class); $rules = [ // level 0 @@ -145,11 +151,11 @@ private function getRuleRegistry(Container $container): Registry new ExistingClassesInPropertiesRule($reflectionProvider, $classCaseSensitivityCheck, true, false), // level 2 - new ClassAncestorsRule($fileTypeMapper, $genericAncestorsCheck), + new ClassAncestorsRule($fileTypeMapper, $genericAncestorsCheck, $crossCheckInterfacesHelper, $this->crossCheckInterfaces), new ClassTemplateTypeRule($templateTypeCheck), new FunctionTemplateTypeRule($fileTypeMapper, $templateTypeCheck), new FunctionSignatureVarianceRule($varianceCheck), - new InterfaceAncestorsRule($fileTypeMapper, $genericAncestorsCheck), + new InterfaceAncestorsRule($fileTypeMapper, $genericAncestorsCheck, $crossCheckInterfacesHelper, $this->crossCheckInterfaces), new InterfaceTemplateTypeRule($fileTypeMapper, $templateTypeCheck), new MethodTemplateTypeRule($fileTypeMapper, $templateTypeCheck), new MethodSignatureVarianceRule($varianceCheck), diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index 2415737b5e..ff5c260171 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -1068,8 +1068,8 @@ private function getFirstExtendsTag(): ?ExtendsTag return null; } - /** @return ExtendsTag[] */ - private function getExtendsTags(): array + /** @return array */ + public function getExtendsTags(): array { $resolvedPhpDoc = $this->getResolvedPhpDoc(); if ($resolvedPhpDoc === null) { @@ -1079,8 +1079,8 @@ private function getExtendsTags(): array return $resolvedPhpDoc->getExtendsTags(); } - /** @return ImplementsTag[] */ - private function getImplementsTags(): array + /** @return array */ + public function getImplementsTags(): array { $resolvedPhpDoc = $this->getResolvedPhpDoc(); if ($resolvedPhpDoc === null) { diff --git a/src/Rules/Generics/ClassAncestorsRule.php b/src/Rules/Generics/ClassAncestorsRule.php index 7ae6d37b74..b07c3453d2 100644 --- a/src/Rules/Generics/ClassAncestorsRule.php +++ b/src/Rules/Generics/ClassAncestorsRule.php @@ -21,13 +21,21 @@ class ClassAncestorsRule implements Rule private \PHPStan\Rules\Generics\GenericAncestorsCheck $genericAncestorsCheck; + private CrossCheckInterfacesHelper $crossCheckInterfacesHelper; + + private bool $crossCheckInterfaces; + public function __construct( FileTypeMapper $fileTypeMapper, - GenericAncestorsCheck $genericAncestorsCheck + GenericAncestorsCheck $genericAncestorsCheck, + CrossCheckInterfacesHelper $crossCheckInterfacesHelper, + bool $crossCheckInterfaces = false ) { $this->fileTypeMapper = $fileTypeMapper; $this->genericAncestorsCheck = $genericAncestorsCheck; + $this->crossCheckInterfacesHelper = $crossCheckInterfacesHelper; + $this->crossCheckInterfaces = $crossCheckInterfaces; } public function getNodeType(): string @@ -99,6 +107,12 @@ public function processNode(Node $node, Scope $scope): array sprintf('in implemented type %%s of class %s', $className) ); + if ($this->crossCheckInterfaces) { + foreach ($this->crossCheckInterfacesHelper->check($classReflection) as $error) { + $implementsErrors[] = $error; + } + } + return array_merge($extendsErrors, $implementsErrors); } diff --git a/src/Rules/Generics/CrossCheckInterfacesHelper.php b/src/Rules/Generics/CrossCheckInterfacesHelper.php new file mode 100644 index 0000000000..03c8ea3934 --- /dev/null +++ b/src/Rules/Generics/CrossCheckInterfacesHelper.php @@ -0,0 +1,69 @@ +getInterfaces() as $interface) { + if (!$interface->isGeneric()) { + continue; + } + + if (array_key_exists($interface->getName(), $interfaceTemplateTypeMaps)) { + $otherMap = $interfaceTemplateTypeMaps[$interface->getName()]; + foreach ($interface->getActiveTemplateTypeMap()->getTypes() as $name => $type) { + $otherType = $otherMap->getType($name); + if ($otherType === null) { + continue; + } + + if ($type->equals($otherType)) { + continue; + } + + $errors[] = RuleErrorBuilder::message(sprintf( + '%s specifies template type %s of interface %s as %s but it\'s already specified as %s.', + $classReflection->isInterface() ? sprintf('Interface %s', $classReflection->getName()) : sprintf('Class %s', $classReflection->getName()), + $name, + $interface->getName(), + $type->describe(VerbosityLevel::value()), + $otherType->describe(VerbosityLevel::value()) + ))->build(); + } + continue; + } + + $interfaceTemplateTypeMaps[$interface->getName()] = $interface->getActiveTemplateTypeMap(); + } + + $parent = $classReflection->getParentClass(); + while ($parent !== false) { + $check($parent); + $parent = $parent->getParentClass(); + } + + foreach ($classReflection->getInterfaces() as $interface) { + $check($interface); + } + }; + + $check($classReflection); + + return $errors; + } + +} diff --git a/src/Rules/Generics/InterfaceAncestorsRule.php b/src/Rules/Generics/InterfaceAncestorsRule.php index 7aca41e13b..52fe2d03f6 100644 --- a/src/Rules/Generics/InterfaceAncestorsRule.php +++ b/src/Rules/Generics/InterfaceAncestorsRule.php @@ -21,13 +21,21 @@ class InterfaceAncestorsRule implements Rule private \PHPStan\Rules\Generics\GenericAncestorsCheck $genericAncestorsCheck; + private CrossCheckInterfacesHelper $crossCheckInterfacesHelper; + + private bool $crossCheckInterfaces; + public function __construct( FileTypeMapper $fileTypeMapper, - GenericAncestorsCheck $genericAncestorsCheck + GenericAncestorsCheck $genericAncestorsCheck, + CrossCheckInterfacesHelper $crossCheckInterfacesHelper, + bool $crossCheckInterfaces = false ) { $this->fileTypeMapper = $fileTypeMapper; $this->genericAncestorsCheck = $genericAncestorsCheck; + $this->crossCheckInterfacesHelper = $crossCheckInterfacesHelper; + $this->crossCheckInterfaces = $crossCheckInterfaces; } public function getNodeType(): string @@ -96,6 +104,12 @@ public function processNode(Node $node, Scope $scope): array '' ); + if ($this->crossCheckInterfaces) { + foreach ($this->crossCheckInterfacesHelper->check($classReflection) as $error) { + $implementsErrors[] = $error; + } + } + return array_merge($extendsErrors, $implementsErrors); } diff --git a/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php b/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php index 042f1ebb93..525c9fd381 100644 --- a/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php +++ b/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php @@ -21,7 +21,9 @@ protected function getRule(): Rule new GenericObjectTypeCheck(), new VarianceCheck(), true - ) + ), + new CrossCheckInterfacesHelper(), + true ); } @@ -204,4 +206,14 @@ public function testBug3922Reversed(): void ]); } + public function testCrossCheckInterfaces(): void + { + $this->analyse([__DIR__ . '/data/cross-check-interfaces.php'], [ + [ + 'Interface CrossCheckInterfaces\ItemListInterface specifies template type TValue of interface Traversable as CrossCheckInterfaces\Item but it\'s already specified as string.', + 19, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Generics/InterfaceAncestorsRuleTest.php b/tests/PHPStan/Rules/Generics/InterfaceAncestorsRuleTest.php index 6ffca19004..1eefba1215 100644 --- a/tests/PHPStan/Rules/Generics/InterfaceAncestorsRuleTest.php +++ b/tests/PHPStan/Rules/Generics/InterfaceAncestorsRuleTest.php @@ -21,7 +21,9 @@ protected function getRule(): Rule new GenericObjectTypeCheck(), new VarianceCheck(), true - ) + ), + new CrossCheckInterfacesHelper(), + true ); } @@ -197,4 +199,14 @@ public function testRuleExtends(): void ]); } + public function testCrossCheckInterfaces(): void + { + $this->analyse([__DIR__ . '/data/cross-check-interfaces-interfaces.php'], [ + [ + 'Interface CrossCheckInterfacesInInterfaces\ItemListInterface specifies template type TValue of interface Traversable as CrossCheckInterfacesInInterfaces\Item but it\'s already specified as string.', + 19, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Generics/data/cross-check-interfaces-interfaces.php b/tests/PHPStan/Rules/Generics/data/cross-check-interfaces-interfaces.php new file mode 100644 index 0000000000..d7054e6fab --- /dev/null +++ b/tests/PHPStan/Rules/Generics/data/cross-check-interfaces-interfaces.php @@ -0,0 +1,30 @@ + + */ +interface ItemListInterface extends \Traversable +{ +} + +/** + * @extends \IteratorAggregate + */ +interface ItemList extends \IteratorAggregate, ItemListInterface +{ + +} + +/** + * @extends \IteratorAggregate + */ +interface ItemList2 extends \IteratorAggregate, ItemListInterface +{ + +} diff --git a/tests/PHPStan/Rules/Generics/data/cross-check-interfaces.php b/tests/PHPStan/Rules/Generics/data/cross-check-interfaces.php new file mode 100644 index 0000000000..cb0977a10c --- /dev/null +++ b/tests/PHPStan/Rules/Generics/data/cross-check-interfaces.php @@ -0,0 +1,36 @@ + + */ +interface ItemListInterface extends \Traversable +{ +} + +/** + * @implements \IteratorAggregate + */ +final class ItemList implements \IteratorAggregate, ItemListInterface +{ + public function getIterator() + { + return new \ArrayIterator([]); + } +} + +/** + * @implements \IteratorAggregate + */ +final class ItemList2 implements \IteratorAggregate, ItemListInterface +{ + public function getIterator() + { + return new \ArrayIterator([]); + } +} From bc665d814b99289da8327851dc5352c2c931b70a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 8 Jul 2021 18:20:48 +0200 Subject: [PATCH 0023/1284] Revert visibility --- src/Reflection/ClassReflection.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index ff5c260171..bb2db45afc 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -1069,7 +1069,7 @@ private function getFirstExtendsTag(): ?ExtendsTag } /** @return array */ - public function getExtendsTags(): array + private function getExtendsTags(): array { $resolvedPhpDoc = $this->getResolvedPhpDoc(); if ($resolvedPhpDoc === null) { @@ -1080,7 +1080,7 @@ public function getExtendsTags(): array } /** @return array */ - public function getImplementsTags(): array + private function getImplementsTags(): array { $resolvedPhpDoc = $this->getResolvedPhpDoc(); if ($resolvedPhpDoc === null) { From 01bb082ad3807bd741920536ce11289373ddda81 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 9 Jul 2021 14:18:06 +0200 Subject: [PATCH 0024/1284] Test generics --- .../Analyser/NodeScopeResolverTest.php | 1 + .../data/generic-object-lower-bound.php | 75 +++++++++++++++++++ .../Rules/Methods/CallMethodsRuleTest.php | 13 ++++ 3 files changed, 89 insertions(+) create mode 100644 tests/PHPStan/Analyser/data/generic-object-lower-bound.php diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 5e5217414a..82ee0c4161 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -437,6 +437,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4711.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/sscanf.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/generic-offset-get.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/generic-object-lower-bound.php'); } /** diff --git a/tests/PHPStan/Analyser/data/generic-object-lower-bound.php b/tests/PHPStan/Analyser/data/generic-object-lower-bound.php new file mode 100644 index 0000000000..fe71aab3d6 --- /dev/null +++ b/tests/PHPStan/Analyser/data/generic-object-lower-bound.php @@ -0,0 +1,75 @@ + $c + * @param T $d + * @return T + */ + function doFoo(Collection $c, object $d) + { + $c->add($d); + } + + /** + * @param Collection $c + */ + function doBar(Collection $c): void + { + assertType(Cat::class . '|' . Dog::class, $this->doFoo($c, new Cat())); + } + +} + +class Bar +{ + + /** + * @template T of object + * @param Collection2 $c + * @param T $d + * @return T + */ + function doFoo(Collection2 $c, object $d) + { + $c->add($d); + } + + /** + * @param Collection2 $c + */ + function doBar(Collection2 $c): void + { + assertType(Cat::class . '|' . Dog::class, $this->doFoo($c, new Cat())); + } + +} diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 0c7ead3a27..88c202eb02 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -1995,4 +1995,17 @@ public function testBug5258(): void $this->analyse([__DIR__ . '/data/bug-5258.php'], []); } + public function testGenericObjectLowerBound(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/../../Analyser/data/generic-object-lower-bound.php'], [ + [ + 'Parameter #1 $c of method GenericObjectLowerBound\Foo::doFoo() expects GenericObjectLowerBound\Collection, GenericObjectLowerBound\Collection given.', + 48, + ], + ]); + } + } From 0f8ead706ba4b514e4a511c743645c065f52f195 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 9 Jul 2021 17:50:02 +0200 Subject: [PATCH 0025/1284] Update nikic/php-parser to work on PHP 8.1 again --- composer.json | 2 +- composer.lock | 27 ++++++++++++++++++--------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/composer.json b/composer.json index ed82fc90d8..7515daab9a 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ "nette/neon": "^3.0", "nette/schema": "^1.0", "nette/utils": "^3.1.3", - "nikic/php-parser": "4.11.0", + "nikic/php-parser": "dev-master#c758510a37218d631fd10f67bca5bccbfef864fa as 4.11.0", "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "4.3.61", "phpstan/php-8-stubs": "^0.1.21", diff --git a/composer.lock b/composer.lock index 843fa4c4df..362e4c1859 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "8935821e0ae449b7a35345d12e270aa5", + "content-hash": "1444c37e9e0463764d3e43fec7b20f44", "packages": [ { "name": "clue/block-react", @@ -1946,16 +1946,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.11.0", + "version": "dev-master", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "fe14cf3672a149364fb66dfe11bf6549af899f94" + "reference": "c758510a37218d631fd10f67bca5bccbfef864fa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/fe14cf3672a149364fb66dfe11bf6549af899f94", - "reference": "fe14cf3672a149364fb66dfe11bf6549af899f94", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/c758510a37218d631fd10f67bca5bccbfef864fa", + "reference": "c758510a37218d631fd10f67bca5bccbfef864fa", "shasum": "" }, "require": { @@ -1966,6 +1966,7 @@ "ircmaxell/php-yacc": "^0.0.7", "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" }, + "default-branch": true, "bin": [ "bin/php-parse" ], @@ -1996,9 +1997,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.11.0" + "source": "https://github.com/nikic/PHP-Parser/tree/master" }, - "time": "2021-07-03T13:36:55+00:00" + "time": "2021-07-09T14:52:58+00:00" }, { "name": "ondram/ci-detector", @@ -6385,10 +6386,18 @@ "time": "2021-03-09T10:59:23+00:00" } ], - "aliases": [], + "aliases": [ + { + "package": "nikic/php-parser", + "version": "9999999-dev", + "alias": "4.11.0", + "alias_normalized": "4.11.0.0" + } + ], "minimum-stability": "dev", "stability-flags": { - "jetbrains/phpstorm-stubs": 20 + "jetbrains/phpstorm-stubs": 20, + "nikic/php-parser": 20 }, "prefer-stable": true, "prefer-lowest": false, From cd02bf86a65cfe87be22ad59c35bde25b82ab700 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 9 Jul 2021 14:47:33 +0200 Subject: [PATCH 0026/1284] Fix mess in ClassReflection::getInterfaces() --- src/PhpDoc/PhpDocNodeResolver.php | 6 +- src/Reflection/ClassReflection.php | 127 +++++++++++------- .../Generics/CrossCheckInterfacesHelper.php | 35 ++++- .../Analyser/NodeScopeResolverTest.php | 2 + .../data/class-reflection-interfaces.php | 26 ++++ .../Reflection/ClassReflectionTest.php | 17 +-- .../Reflection/data/GenericInheritance.php | 7 - .../Rules/Generics/ClassAncestorsRuleTest.php | 2 +- .../Generics/InterfaceAncestorsRuleTest.php | 2 +- .../cross-check-interfaces-interfaces.php | 18 +++ .../MissingMethodReturnTypehintRuleTest.php | 8 +- tests/PHPStan/Rules/Methods/data/bug-4415.php | 9 ++ 12 files changed, 172 insertions(+), 87 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/class-reflection-interfaces.php diff --git a/src/PhpDoc/PhpDocNodeResolver.php b/src/PhpDoc/PhpDocNodeResolver.php index 82cb4df018..024f2489bc 100644 --- a/src/PhpDoc/PhpDocNodeResolver.php +++ b/src/PhpDoc/PhpDocNodeResolver.php @@ -191,7 +191,7 @@ public function resolveExtendsTags(PhpDocNode $phpDocNode, NameScope $nameScope) foreach (['@extends', '@template-extends', '@phpstan-extends'] as $tagName) { foreach ($phpDocNode->getExtendsTagValues($tagName) as $tagValue) { - $resolved[$tagValue->type->type->name] = new ExtendsTag( + $resolved[$nameScope->resolveStringName($tagValue->type->type->name)] = new ExtendsTag( $this->typeNodeResolver->resolve($tagValue->type, $nameScope) ); } @@ -209,7 +209,7 @@ public function resolveImplementsTags(PhpDocNode $phpDocNode, NameScope $nameSco foreach (['@implements', '@template-implements', '@phpstan-implements'] as $tagName) { foreach ($phpDocNode->getImplementsTagValues($tagName) as $tagValue) { - $resolved[$tagValue->type->type->name] = new ImplementsTag( + $resolved[$nameScope->resolveStringName($tagValue->type->type->name)] = new ImplementsTag( $this->typeNodeResolver->resolve($tagValue->type, $nameScope) ); } @@ -227,7 +227,7 @@ public function resolveUsesTags(PhpDocNode $phpDocNode, NameScope $nameScope): a foreach (['@use', '@template-use', '@phpstan-use'] as $tagName) { foreach ($phpDocNode->getUsesTagValues($tagName) as $tagValue) { - $resolved[$tagValue->type->type->name] = new UsesTag( + $resolved[$nameScope->resolveStringName($tagValue->type->type->name)] = new UsesTag( $this->typeNodeResolver->resolve($tagValue->type, $nameScope) ); } diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index bb2db45afc..34534395c2 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -596,77 +596,112 @@ public function getInterfaces(): array return $this->cachedInterfaces; } - $interfaces = []; - + $interfaces = $this->getImmediateInterfaces(); + $immediateInterfaces = $interfaces; $parent = $this->getParentClass(); - if ($parent !== false) { - foreach ($parent->getInterfaces() as $interface) { - $interfaces[$interface->getName()] = $interface; + while ($parent !== false) { + foreach ($parent->getImmediateInterfaces() as $parentInterface) { + $interfaces[$parentInterface->getName()] = $parentInterface; + foreach ($this->collectInterfaces($parentInterface) as $parentInterfaceInterface) { + $interfaces[$parentInterfaceInterface->getName()] = $parentInterfaceInterface; + } } - } - if ($this->reflection->isInterface()) { - $implementsTags = $this->getExtendsTags(); - } else { - $implementsTags = $this->getImplementsTags(); + $parent = $parent->getParentClass(); } - $interfaceNames = $this->reflection->getInterfaceNames(); - $genericInterfaces = []; + foreach ($immediateInterfaces as $immediateInterface) { + foreach ($this->collectInterfaces($immediateInterface) as $interfaceInterface) { + $interfaces[$interfaceInterface->getName()] = $interfaceInterface; + } + } - foreach ($implementsTags as $implementsTag) { - $implementedType = $implementsTag->getType(); + $this->cachedInterfaces = $interfaces; - if (!$this->isValidAncestorType($implementedType, $interfaceNames)) { - continue; - } + return $interfaces; + } - if ($this->isGeneric()) { - $implementedType = TemplateTypeHelper::resolveTemplateTypes( - $implementedType, - $this->getActiveTemplateTypeMap() - ); + /** + * @return \PHPStan\Reflection\ClassReflection[] + */ + private function collectInterfaces(ClassReflection $interface): array + { + $interfaces = []; + foreach ($interface->getImmediateInterfaces() as $immediateInterface) { + $interfaces[$immediateInterface->getName()] = $immediateInterface; + foreach ($this->collectInterfaces($immediateInterface) as $immediateInterfaceInterface) { + $interfaces[$immediateInterfaceInterface->getName()] = $immediateInterfaceInterface; } + } - if (!$implementedType instanceof GenericObjectType) { - continue; - } + return $interfaces; + } - $reflectionIface = $implementedType->getClassReflection(); - if ($reflectionIface === null) { - continue; + /** + * @return \PHPStan\Reflection\ClassReflection[] + */ + private function getImmediateInterfaces(): array + { + $indirectInterfaceNames = []; + $parent = $this->getParentClass(); + while ($parent !== false) { + foreach ($parent->getNativeReflection()->getInterfaceNames() as $parentInterfaceName) { + $indirectInterfaceNames[] = $parentInterfaceName; } - $genericInterfaces[] = $reflectionIface; + $parent = $parent->getParentClass(); } - foreach ($genericInterfaces as $genericInterface) { - $interfaces = array_merge($interfaces, $genericInterface->getInterfaces()); + foreach ($this->getNativeReflection()->getInterfaces() as $interfaceInterface) { + foreach ($interfaceInterface->getInterfaceNames() as $interfaceInterfaceName) { + $indirectInterfaceNames[] = $interfaceInterfaceName; + } } - foreach ($genericInterfaces as $genericInterface) { - $interfaces[$genericInterface->getName()] = $genericInterface; + if ($this->reflection->isInterface()) { + $implementsTags = $this->getExtendsTags(); + } else { + $implementsTags = $this->getImplementsTags(); } - foreach ($interfaceNames as $interfaceName) { - if (isset($interfaces[$interfaceName])) { + $immediateInterfaceNames = array_diff($this->getNativeReflection()->getInterfaceNames(), $indirectInterfaceNames); + $immediateInterfaces = []; + foreach ($immediateInterfaceNames as $immediateInterfaceName) { + if (!$this->reflectionProvider->hasClass($immediateInterfaceName)) { continue; } - $interfaceReflection = $this->reflectionProvider->getClass($interfaceName); - if (!$interfaceReflection->isGeneric()) { - $interfaces[$interfaceName] = $interfaceReflection; + $immediateInterface = $this->reflectionProvider->getClass($immediateInterfaceName); + if (array_key_exists($immediateInterface->getName(), $implementsTags)) { + $implementsTag = $implementsTags[$immediateInterface->getName()]; + $implementedType = $implementsTag->getType(); + if ($this->isGeneric()) { + $implementedType = TemplateTypeHelper::resolveTemplateTypes( + $implementedType, + $this->getActiveTemplateTypeMap() + ); + } + + if ( + $implementedType instanceof GenericObjectType + && $implementedType->getClassReflection() !== null + ) { + $immediateInterfaces[$immediateInterface->getName()] = $implementedType->getClassReflection(); + continue; + } + } + + if ($immediateInterface->isGeneric()) { + $immediateInterfaces[$immediateInterface->getName()] = $immediateInterface->withTypes( + array_values($immediateInterface->getTemplateTypeMap()->resolveToBounds()->getTypes()) + ); continue; } - $interfaces[$interfaceName] = $interfaceReflection->withTypes( - array_values($interfaceReflection->getTemplateTypeMap()->resolveToBounds()->getTypes()) - ); + $immediateInterfaces[$immediateInterface->getName()] = $immediateInterface; } - $this->cachedInterfaces = $interfaces; - - return $interfaces; + return $immediateInterfaces; } /** @@ -1069,7 +1104,7 @@ private function getFirstExtendsTag(): ?ExtendsTag } /** @return array */ - private function getExtendsTags(): array + public function getExtendsTags(): array { $resolvedPhpDoc = $this->getResolvedPhpDoc(); if ($resolvedPhpDoc === null) { @@ -1080,7 +1115,7 @@ private function getExtendsTags(): array } /** @return array */ - private function getImplementsTags(): array + public function getImplementsTags(): array { $resolvedPhpDoc = $this->getResolvedPhpDoc(); if ($resolvedPhpDoc === null) { diff --git a/src/Rules/Generics/CrossCheckInterfacesHelper.php b/src/Rules/Generics/CrossCheckInterfacesHelper.php index 03c8ea3934..4bc11186c4 100644 --- a/src/Rules/Generics/CrossCheckInterfacesHelper.php +++ b/src/Rules/Generics/CrossCheckInterfacesHelper.php @@ -17,7 +17,7 @@ public function check(ClassReflection $classReflection): array { $interfaceTemplateTypeMaps = []; $errors = []; - $check = static function (ClassReflection $classReflection) use (&$interfaceTemplateTypeMaps, &$check, &$errors): void { + $check = static function (ClassReflection $classReflection, bool $first) use (&$interfaceTemplateTypeMaps, &$check, &$errors): void { foreach ($classReflection->getInterfaces() as $interface) { if (!$interface->isGeneric()) { continue; @@ -51,17 +51,40 @@ public function check(ClassReflection $classReflection): array } $parent = $classReflection->getParentClass(); - while ($parent !== false) { - $check($parent); - $parent = $parent->getParentClass(); + $checkParents = true; + if ($first && $parent !== false) { + $extendsTags = $classReflection->getExtendsTags(); + if (!array_key_exists($parent->getName(), $extendsTags)) { + $checkParents = false; + } + } + + if ($checkParents) { + while ($parent !== false) { + $check($parent, false); + $parent = $parent->getParentClass(); + } } + $interfaceTags = []; + if ($first) { + if ($classReflection->isInterface()) { + $interfaceTags = $classReflection->getExtendsTags(); + } else { + $interfaceTags = $classReflection->getImplementsTags(); + } + } foreach ($classReflection->getInterfaces() as $interface) { - $check($interface); + if ($first) { + if (!array_key_exists($interface->getName(), $interfaceTags)) { + continue; + } + } + $check($interface, false); } }; - $check($classReflection); + $check($classReflection, true); return $errors; } diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 82ee0c4161..e38bdbb669 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -438,6 +438,8 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/sscanf.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/generic-offset-get.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/generic-object-lower-bound.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/class-reflection-interfaces.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/bug-4415.php'); } /** diff --git a/tests/PHPStan/Analyser/data/class-reflection-interfaces.php b/tests/PHPStan/Analyser/data/class-reflection-interfaces.php new file mode 100644 index 0000000000..eac3ea9b0a --- /dev/null +++ b/tests/PHPStan/Analyser/data/class-reflection-interfaces.php @@ -0,0 +1,26 @@ + + */ +interface ResultStatement extends \Traversable +{ + +} + +interface Statement extends ResultStatement +{ + +} + +function (Statement $s): void +{ + foreach ($s as $k => $v) { + assertType('int', $k); + assertType('mixed', $v); + } +}; diff --git a/tests/PHPStan/Reflection/ClassReflectionTest.php b/tests/PHPStan/Reflection/ClassReflectionTest.php index 86a016db66..2a74d56c8d 100644 --- a/tests/PHPStan/Reflection/ClassReflectionTest.php +++ b/tests/PHPStan/Reflection/ClassReflectionTest.php @@ -138,24 +138,9 @@ public function testGenericInheritance(): void $this->assertSame('GenericInheritance\\C0', $parent->getDisplayName()); $this->assertSame([ - 'GenericInheritance\\I0', - 'GenericInheritance\\I1', 'GenericInheritance\\I', - ], array_map(static function (ClassReflection $r): string { - return $r->getDisplayName(); - }, array_values($reflection->getInterfaces()))); - } - - public function testGenericInheritanceOverride(): void - { - /** @var Broker $broker */ - $broker = self::getContainer()->getService('broker'); - $reflection = $broker->getClass(\GenericInheritance\Override::class); - - $this->assertSame([ - 'GenericInheritance\\I0', + 'GenericInheritance\\I0', 'GenericInheritance\\I1', - 'GenericInheritance\\I', ], array_map(static function (ClassReflection $r): string { return $r->getDisplayName(); }, array_values($reflection->getInterfaces()))); diff --git a/tests/PHPStan/Reflection/data/GenericInheritance.php b/tests/PHPStan/Reflection/data/GenericInheritance.php index 9de88b7f3a..e378da01c6 100644 --- a/tests/PHPStan/Reflection/data/GenericInheritance.php +++ b/tests/PHPStan/Reflection/data/GenericInheritance.php @@ -46,10 +46,3 @@ class C0 implements I { */ class C extends C0 { } - - -/** - * @implements I<\DateTimeInterface> - */ -class Override extends C { -} diff --git a/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php b/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php index 525c9fd381..ee5db5befc 100644 --- a/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php +++ b/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php @@ -210,7 +210,7 @@ public function testCrossCheckInterfaces(): void { $this->analyse([__DIR__ . '/data/cross-check-interfaces.php'], [ [ - 'Interface CrossCheckInterfaces\ItemListInterface specifies template type TValue of interface Traversable as CrossCheckInterfaces\Item but it\'s already specified as string.', + 'Interface IteratorAggregate specifies template type TValue of interface Traversable as string but it\'s already specified as CrossCheckInterfaces\Item.', 19, ], ]); diff --git a/tests/PHPStan/Rules/Generics/InterfaceAncestorsRuleTest.php b/tests/PHPStan/Rules/Generics/InterfaceAncestorsRuleTest.php index 1eefba1215..7aeb93d07b 100644 --- a/tests/PHPStan/Rules/Generics/InterfaceAncestorsRuleTest.php +++ b/tests/PHPStan/Rules/Generics/InterfaceAncestorsRuleTest.php @@ -203,7 +203,7 @@ public function testCrossCheckInterfaces(): void { $this->analyse([__DIR__ . '/data/cross-check-interfaces-interfaces.php'], [ [ - 'Interface CrossCheckInterfacesInInterfaces\ItemListInterface specifies template type TValue of interface Traversable as CrossCheckInterfacesInInterfaces\Item but it\'s already specified as string.', + 'Interface IteratorAggregate specifies template type TValue of interface Traversable as string but it\'s already specified as CrossCheckInterfacesInInterfaces\Item.', 19, ], ]); diff --git a/tests/PHPStan/Rules/Generics/data/cross-check-interfaces-interfaces.php b/tests/PHPStan/Rules/Generics/data/cross-check-interfaces-interfaces.php index d7054e6fab..8900cefd5c 100644 --- a/tests/PHPStan/Rules/Generics/data/cross-check-interfaces-interfaces.php +++ b/tests/PHPStan/Rules/Generics/data/cross-check-interfaces-interfaces.php @@ -28,3 +28,21 @@ interface ItemList2 extends \IteratorAggregate, ItemListInterface { } + +interface ItemList3 extends ItemList // do not report +{ + +} + +/** + * @extends \Traversable + */ +interface ResultStatement extends \Traversable +{ + +} + +interface Statement extends ResultStatement +{ + +} diff --git a/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php b/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php index 0509242cd2..5ca9f7ae6b 100644 --- a/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php @@ -69,13 +69,7 @@ public function testArrayTypehintWithoutNullInPhpDoc(): void public function testBug4415(): void { - $this->analyse([__DIR__ . '/data/bug-4415.php'], [ - [ - 'Method Bug4415Rule\CategoryCollection::getIterator() return type has no value type specified in iterable type Iterator.', - 76, - MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP, - ], - ]); + $this->analyse([__DIR__ . '/data/bug-4415.php'], []); } public function testBug5089(): void diff --git a/tests/PHPStan/Rules/Methods/data/bug-4415.php b/tests/PHPStan/Rules/Methods/data/bug-4415.php index e440b56e79..247de77c3e 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-4415.php +++ b/tests/PHPStan/Rules/Methods/data/bug-4415.php @@ -2,6 +2,8 @@ namespace Bug4415Rule; +use function PHPStan\Testing\assertType; + /** * @template T * @extends \IteratorAggregate @@ -85,3 +87,10 @@ public function getName(): string return ''; } } + +function (CategoryCollection $c): void { + foreach ($c as $k => $v) { + assertType('mixed', $k); + assertType(Category::class, $v); + } +}; From 5eb5eaa8f292da748b344483a5f2e80ec96f8d98 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 10 Jul 2021 15:41:54 +0200 Subject: [PATCH 0027/1284] Regression test Closes https://github.com/phpstan/phpstan/issues/4854 --- .../Rules/Methods/MethodSignatureRuleTest.php | 7 ++ tests/PHPStan/Rules/Methods/data/bug-4854.php | 66 +++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 tests/PHPStan/Rules/Methods/data/bug-4854.php diff --git a/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php b/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php index f42226d2d5..00581095b8 100644 --- a/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php @@ -340,4 +340,11 @@ public function testBug4729(): void $this->analyse([__DIR__ . '/data/bug-4729.php'], []); } + public function testBug4854(): void + { + $this->reportMaybes = true; + $this->reportStatic = true; + $this->analyse([__DIR__ . '/data/bug-4854.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-4854.php b/tests/PHPStan/Rules/Methods/data/bug-4854.php new file mode 100644 index 0000000000..82ff7c0eb2 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-4854.php @@ -0,0 +1,66 @@ + + * @extends \ArrayAccess + */ +interface DomainsAvailabilityInterface extends \IteratorAggregate, \ArrayAccess +{ + public const AVAILABLE = 1; + public const UNAVAILABLE = 2; + public const UNKNOWN = 3; +} + +abstract class AbstractDomainsAvailability implements DomainsAvailabilityInterface +{ + /** + * @var int[] + */ + protected array $domains; + + /** + * {@inheritdoc} + */ + public function getIterator() + { + return new \ArrayIterator($this->domains); + } + + /** + * {@inheritdoc} + */ + public function offsetSet($offset, $value): void + { + if ($offset === null) { + $this->domains[] = $value; + } else { + $this->domains[$offset] = $value; + } + } + + /** + * {@inheritdoc} + */ + public function offsetExists($offset) + { + return isset($this->domains[$offset]); + } + + /** + * {@inheritdoc} + */ + public function offsetUnset($offset): void + { + unset($this->domains[$offset]); + } + + /** + * {@inheritdoc} + */ + public function offsetGet($offset): int + { + return $this->domains[$offset]; + } +} From 865ae5d414bd8b359a3411844664c64489c16538 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 10 Jul 2021 15:48:20 +0200 Subject: [PATCH 0028/1284] Fix test --- tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php b/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php index 00581095b8..080a6e4a3e 100644 --- a/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php @@ -342,6 +342,10 @@ public function testBug4729(): void public function testBug4854(): void { + if (PHP_VERSION_ID < 70400 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 7.4.'); + } + $this->reportMaybes = true; $this->reportStatic = true; $this->analyse([__DIR__ . '/data/bug-4854.php'], []); From 902899da70ef8a26afd80394def64eacb171b2f6 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 12 Jul 2021 09:44:19 +0200 Subject: [PATCH 0029/1284] array_walk also understands object --- resources/functionMap.php | 4 ++-- .../SignatureMap/Php8SignatureMapProvider.php | 7 +------ .../SignatureMap/Php8SignatureMapProviderTest.php | 4 ++-- .../Functions/CallToFunctionParametersRuleTest.php | 4 ++-- tests/PHPStan/Rules/Functions/data/array_walk.php | 14 ++++++++++++++ 5 files changed, 21 insertions(+), 12 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 430818ed37..1886789d41 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -320,8 +320,8 @@ 'array_unique' => ['array', 'array'=>'array', 'flags='=>'int'], 'array_unshift' => ['int', '&rw_stack'=>'array', 'var'=>'mixed', '...vars='=>'mixed'], 'array_values' => ['array', 'input'=>'array'], -'array_walk' => ['bool', '&rw_input'=>'array', 'callback'=>'callable', 'userdata='=>'mixed'], -'array_walk_recursive' => ['bool', '&rw_input'=>'array', 'callback'=>'callable', 'userdata='=>'mixed'], +'array_walk' => ['bool', '&rw_input'=>'array|object', 'callback'=>'callable', 'userdata='=>'mixed'], +'array_walk_recursive' => ['bool', '&rw_input'=>'array|object', 'callback'=>'callable', 'userdata='=>'mixed'], 'ArrayAccess::offsetExists' => ['bool', 'offset'=>'mixed'], 'ArrayAccess::offsetGet' => ['mixed', 'offset'=>'mixed'], 'ArrayAccess::offsetSet' => ['void', 'offset'=>'mixed', 'value'=>'mixed'], diff --git a/src/Reflection/SignatureMap/Php8SignatureMapProvider.php b/src/Reflection/SignatureMap/Php8SignatureMapProvider.php index 86be50426a..1c136b8185 100644 --- a/src/Reflection/SignatureMap/Php8SignatureMapProvider.php +++ b/src/Reflection/SignatureMap/Php8SignatureMapProvider.php @@ -10,7 +10,6 @@ use PHPStan\PhpDoc\Tag\ParamTag; use PHPStan\Reflection\BetterReflection\SourceLocator\FileNodesFetcher; use PHPStan\Reflection\PassedByReference; -use PHPStan\Type\ArrayType; use PHPStan\Type\FileTypeMapper; use PHPStan\Type\MixedType; use PHPStan\Type\ParserNodeTypeToPHPStanType; @@ -291,11 +290,7 @@ private function getSignature( if (!$name instanceof Variable || !is_string($name->name)) { throw new \PHPStan\ShouldNotHappenException(); } - if ($name->name === 'array') { - $parameterType = new ArrayType(new MixedType(), new MixedType()); - } else { - $parameterType = ParserNodeTypeToPHPStanType::resolve($param->type, null); - } + $parameterType = ParserNodeTypeToPHPStanType::resolve($param->type, null); $parameters[] = new ParameterSignature( $name->name, $param->default !== null || $param->variadic, diff --git a/tests/PHPStan/Reflection/SignatureMap/Php8SignatureMapProviderTest.php b/tests/PHPStan/Reflection/SignatureMap/Php8SignatureMapProviderTest.php index db00ba439a..8684859503 100644 --- a/tests/PHPStan/Reflection/SignatureMap/Php8SignatureMapProviderTest.php +++ b/tests/PHPStan/Reflection/SignatureMap/Php8SignatureMapProviderTest.php @@ -105,8 +105,8 @@ public function dataFunctions(): array [ 'name' => 'array', 'optional' => false, - 'type' => new ArrayType(new MixedType(), new MixedType()), - 'nativeType' => new ArrayType(new MixedType(), new MixedType()), + 'type' => new UnionType([new ArrayType(new MixedType(), new MixedType()), new ObjectWithoutClassType()]), + 'nativeType' => new UnionType([new ArrayType(new MixedType(), new MixedType()), new ObjectWithoutClassType()]), 'passedByReference' => PassedByReference::createReadsArgument(), 'variadic' => false, ], diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 628e685a7d..c1202605db 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -290,7 +290,7 @@ public function testPassingNonVariableToParameterPassedByReference(): void 33, ], [ - 'Parameter #1 $array of function reset expects array, null given.', + 'Parameter #1 $array of function reset expects array|object, null given.', 39, ], ]); @@ -318,7 +318,7 @@ public function testImplodeOnPhp74(): void if (PHP_VERSION_ID >= 80000) { $errors = [ [ - 'Parameter #2 $array of function implode expects array, string given.', + 'Parameter #2 $array of function implode expects array|null, string given.', 8, ], ]; diff --git a/tests/PHPStan/Rules/Functions/data/array_walk.php b/tests/PHPStan/Rules/Functions/data/array_walk.php index 2d513b78ba..eeebab63f0 100644 --- a/tests/PHPStan/Rules/Functions/data/array_walk.php +++ b/tests/PHPStan/Rules/Functions/data/array_walk.php @@ -24,3 +24,17 @@ function(int $value, string $key, int $extra): string { return ''; } ); + +function (): void { + $object = (object)['foo' => 'bar']; + array_walk($object, function ($v) { + return '_' . $v; + }); +}; + +function (): void { + $object = (object)['foo' => 'bar']; + array_walk_recursive($object, function ($v) { + return '_' . $v; + }); +}; From 8fd9667c73710d586e90b5f5296575a3f6c9623a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 12 Jul 2021 10:09:27 +0200 Subject: [PATCH 0030/1284] Fix infinite recursion --- src/Type/ObjectType.php | 14 +++- .../Analyser/AnalyserIntegrationTest.php | 12 +++ tests/PHPStan/Analyser/data/bug-5231.php | 78 +++++++++++++++++++ tests/PHPStan/Analyser/data/bug-5231_2.php | 78 +++++++++++++++++++ 4 files changed, 178 insertions(+), 4 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/bug-5231.php create mode 100644 tests/PHPStan/Analyser/data/bug-5231_2.php diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 93606edcad..3d23923f28 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -656,7 +656,11 @@ public function getIterableKeyType(): Type } if ($this->isInstanceOf(\Iterator::class)->yes()) { - return ParametersAcceptorSelector::selectSingle($this->getMethod('key', new OutOfClassScope())->getVariants())->getReturnType(); + return RecursionGuard::run($this, function (): Type { + return ParametersAcceptorSelector::selectSingle( + $this->getMethod('key', new OutOfClassScope())->getVariants() + )->getReturnType(); + }); } if ($this->isInstanceOf(\IteratorAggregate::class)->yes()) { @@ -685,9 +689,11 @@ public function getIterableKeyType(): Type public function getIterableValueType(): Type { if ($this->isInstanceOf(\Iterator::class)->yes()) { - return ParametersAcceptorSelector::selectSingle( - $this->getMethod('current', new OutOfClassScope())->getVariants() - )->getReturnType(); + return RecursionGuard::run($this, function (): Type { + return ParametersAcceptorSelector::selectSingle( + $this->getMethod('current', new OutOfClassScope())->getVariants() + )->getReturnType(); + }); } if ($this->isInstanceOf(\IteratorAggregate::class)->yes()) { diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 3a7cff0388..22c8057e11 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -410,6 +410,18 @@ public function testBug4734(): void $this->assertSame('Access to an undefined property Bug4734\Foo::$httpMethodParameterOverride4.', $errors[1]->getMessage()); } + public function testBug5231(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-5231.php'); + $this->assertCount(5, $errors); + } + + public function testBug5231Two(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-5231_2.php'); + $this->assertCount(1, $errors); + } + /** * @param string $file * @return \PHPStan\Analyser\Error[] diff --git a/tests/PHPStan/Analyser/data/bug-5231.php b/tests/PHPStan/Analyser/data/bug-5231.php new file mode 100644 index 0000000000..f42aebe125 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-5231.php @@ -0,0 +1,78 @@ +collection as $item) { + if ((string)$item === $name) { + return $item; + } + } + + return null; + } + + // must be exists! + public function existsByKey(string $name): bool + { + return $this->findByName($name) !== null; + } + + public function getSorted(callable $comparator): self + { + $sortedCollection = $this->collection; + usort($sortedCollection, $comparator); + + $filtered = array_values($sortedCollection); + + return new static(...$filtered); + } + + public function rewind(): void + { + reset($this->collection); + } + + public function current() + { + return current($this->collection); + } + + /** + * @return bool|float|int|string|null + */ + public function key() + { + return key($this->collection); + } + + /** + * @return mixed|void + */ + public function next() + { + return next($this->collection); + } + + public function valid(): bool + { + return isset($this->collection[key($this->collection)]); + } +} diff --git a/tests/PHPStan/Analyser/data/bug-5231_2.php b/tests/PHPStan/Analyser/data/bug-5231_2.php new file mode 100644 index 0000000000..07e65e5b26 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-5231_2.php @@ -0,0 +1,78 @@ +collection as $item) { + if ((string)$item === $name) { + return $item; + } + } + + return null; + } + + // must be exists! + public function existsByKey(string $name): bool + { + return $this->findByName($name) !== null; + } + + public function getSorted(callable $comparator): self + { + $sortedCollection = $this->collection; + usort($sortedCollection, $comparator); + + $filtered = array_values($sortedCollection); + + return new static(...$filtered); + } + + public function rewind(): void + { + reset($this->collection); + } + + public function current() + { + return current($this->collection); + } + + /** + * @return bool|float|int|string|null + */ + public function key() + { + return key($this->collection); + } + + /** + * @return mixed|void + */ + public function next() + { + return next($this->collection); + } + + public function valid(): bool + { + return isset($this->collection[key($this->collection)]); + } +} From a056d3491645a0f7198b0396e9553cc0b9564972 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 12 Jul 2021 10:28:00 +0200 Subject: [PATCH 0031/1284] More array functions also accept object --- resources/functionMap.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 1886789d41..ee3a252edd 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -1525,7 +1525,7 @@ 'CURLFile::getPostFilename' => ['string'], 'CURLFile::setMimeType' => ['void', 'mime'=>'string'], 'CURLFile::setPostFilename' => ['void', 'name'=>'string'], -'current' => ['mixed', 'array_arg'=>'array'], +'current' => ['mixed', 'array_arg'=>'array|object'], 'cyrus_authenticate' => ['void', 'connection'=>'resource', 'mechlist='=>'string', 'service='=>'string', 'user='=>'string', 'minssf='=>'int', 'maxssf='=>'int', 'authname='=>'string', 'password='=>'string'], 'cyrus_bind' => ['bool', 'connection'=>'resource', 'callbacks'=>'array'], 'cyrus_close' => ['bool', 'connection'=>'resource'], @@ -5739,7 +5739,7 @@ 'kadm5_get_principals' => ['array', 'handle'=>'resource'], 'kadm5_init_with_password' => ['resource', 'admin_server'=>'string', 'realm'=>'string', 'principal'=>'string', 'password'=>'string'], 'kadm5_modify_principal' => ['bool', 'handle'=>'resource', 'principal'=>'string', 'options'=>'array'], -'key' => ['int|string|null', 'array_arg'=>'array'], +'key' => ['int|string|null', 'array_arg'=>'array|object'], 'key_exists' => ['bool', 'key'=>'string|int', 'search'=>'array'], 'krsort' => ['bool', '&rw_array_arg'=>'array', 'sort_flags='=>'int'], 'ksort' => ['bool', '&rw_array_arg'=>'array', 'sort_flags='=>'int'], @@ -7751,7 +7751,7 @@ 'newt_win_message' => ['void', 'title'=>'string', 'button_text'=>'string', 'format'=>'string', 'args='=>'mixed', '...args='=>'mixed'], 'newt_win_messagev' => ['void', 'title'=>'string', 'button_text'=>'string', 'format'=>'string', 'args'=>'array'], 'newt_win_ternary' => ['int', 'title'=>'string', 'button1_text'=>'string', 'button2_text'=>'string', 'button3_text'=>'string', 'format'=>'string', 'args='=>'mixed', '...args='=>'mixed'], -'next' => ['mixed', '&rw_array_arg'=>'array'], +'next' => ['mixed', '&rw_array_arg'=>'array|object'], 'ngettext' => ['string', 'msgid1'=>'string', 'msgid2'=>'string', 'n'=>'int'], 'nl2br' => ['string', 'str'=>'string', 'is_xhtml='=>'bool'], 'nl_langinfo' => ['string|false', 'item'=>'int'], @@ -8841,7 +8841,7 @@ 'preg_replace_callback' => ['string|array|null', 'regex'=>'string|array', 'callback'=>'callable', 'subject'=>'string|array', 'limit='=>'int', '&w_count='=>'int'], 'preg_replace_callback_array' => ['string|array|null', 'pattern'=>'array', 'subject'=>'string|array', 'limit='=>'int', '&w_count='=>'int'], 'preg_split' => ['array|false', 'pattern'=>'string', 'subject'=>'string', 'limit='=>'?int', 'flags='=>'int'], -'prev' => ['mixed', '&rw_array_arg'=>'array'], +'prev' => ['mixed', '&rw_array_arg'=>'array|object'], 'print' => ['int', 'arg'=>'string'], 'print_r' => ['string|true', 'var'=>'mixed', 'return='=>'bool'], 'printf' => ['int', 'format'=>'string', '...values='=>'string|int|float'], @@ -9875,7 +9875,7 @@ 'register_tick_function' => ['bool', 'function'=>'callable(): void', '...args='=>'mixed'], 'rename' => ['bool', 'old_name'=>'string', 'new_name'=>'string', 'context='=>'resource'], 'rename_function' => ['bool', 'original_name'=>'string', 'new_name'=>'string'], -'reset' => ['mixed', '&rw_array'=>'array'], +'reset' => ['mixed', '&rw_array'=>'array|object'], 'ResourceBundle::__construct' => ['void', 'locale'=>'string', 'bundlename'=>'string', 'fallback='=>'bool'], 'ResourceBundle::count' => ['0|positive-int'], 'ResourceBundle::create' => ['?ResourceBundle', 'locale'=>'string', 'bundlename'=>'string', 'fallback='=>'bool'], From 2fd7bfec455c20295ad1a3eee78fc399bb9fcfec Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 12 Jul 2021 10:28:09 +0200 Subject: [PATCH 0032/1284] next() dynamic return type extension also valid for prev() --- .../ArrayNextDynamicReturnTypeExtension.php | 2 +- tests/PHPStan/Analyser/data/array-next.php | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/Type/Php/ArrayNextDynamicReturnTypeExtension.php b/src/Type/Php/ArrayNextDynamicReturnTypeExtension.php index 48645d9cde..5a1a19a0d6 100644 --- a/src/Type/Php/ArrayNextDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayNextDynamicReturnTypeExtension.php @@ -15,7 +15,7 @@ class ArrayNextDynamicReturnTypeExtension implements \PHPStan\Type\DynamicFuncti public function isFunctionSupported(FunctionReflection $functionReflection): bool { - return $functionReflection->getName() === 'next'; + return in_array($functionReflection->getName(), ['next', 'prev'], true); } public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type diff --git a/tests/PHPStan/Analyser/data/array-next.php b/tests/PHPStan/Analyser/data/array-next.php index c1b0a1ecf9..97dc582e49 100644 --- a/tests/PHPStan/Analyser/data/array-next.php +++ b/tests/PHPStan/Analyser/data/array-next.php @@ -31,6 +31,33 @@ public function doBaz(array $a) } +class Foo2 +{ + + public function doFoo() + { + $array = []; + assertType('false', prev($array)); + } + + /** + * @param int[] $a + */ + public function doBar(array $a) + { + assertType('int|false', prev($a)); + } + + /** + * @param non-empty-array $a + */ + public function doBaz(array $a) + { + assertType('string|false', prev($a)); + } + +} + interface HttpClientPoolItem { public function isDisabled(): bool; From 5d59b4b228376e237975a36f342473ac37b4fcf8 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 12 Jul 2021 13:37:24 +0200 Subject: [PATCH 0033/1284] Fix --- tests/PHPStan/Analyser/AnalyserIntegrationTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 22c8057e11..900a615005 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -413,13 +413,13 @@ public function testBug4734(): void public function testBug5231(): void { $errors = $this->runAnalyse(__DIR__ . '/data/bug-5231.php'); - $this->assertCount(5, $errors); + $this->assertNotEmpty($errors); } public function testBug5231Two(): void { $errors = $this->runAnalyse(__DIR__ . '/data/bug-5231_2.php'); - $this->assertCount(1, $errors); + $this->assertNotEmpty($errors); } /** From 1d4835eaec494ada4867d0d0a138ff0a6d98b4ec Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 12 Jul 2021 14:07:05 +0200 Subject: [PATCH 0034/1284] Fix --- .../Rules/Functions/CallToFunctionParametersRuleTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index c1202605db..c09852b378 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -337,7 +337,7 @@ public function testImplodeOnLessThanPhp74(): void if (PHP_VERSION_ID >= 80000) { $errors = [ [ - 'Parameter #2 $array of function implode expects array, string given.', + 'Parameter #2 $array of function implode expects array|null, string given.', 8, ], ]; From f5e88ae06ed144eacd3c042d77b0d4654e6bd84c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 14 Jul 2021 09:27:14 +0200 Subject: [PATCH 0035/1284] Final Exception methods can never throw an exception --- conf/config.stubFiles.neon | 1 + stubs/Exception.stub | 146 ++++++++++++++++++ .../CatchWithUnthrownExceptionRuleTest.php | 4 + .../Exceptions/data/unthrown-exception.php | 14 ++ 4 files changed, 165 insertions(+) create mode 100644 stubs/Exception.stub diff --git a/conf/config.stubFiles.neon b/conf/config.stubFiles.neon index 158fdc5a6f..ecdd61bf77 100644 --- a/conf/config.stubFiles.neon +++ b/conf/config.stubFiles.neon @@ -12,3 +12,4 @@ parameters: - ../stubs/dom.stub - ../stubs/spl.stub - ../stubs/SplObjectStorage.stub + - ../stubs/Exception.stub diff --git a/stubs/Exception.stub b/stubs/Exception.stub new file mode 100644 index 0000000000..27270cb9b7 --- /dev/null +++ b/stubs/Exception.stub @@ -0,0 +1,146 @@ +getMessage(); + } catch (\Exception $t) { + + } + } + +} From 5eb96f55b75ae71acaa721ab831ac4a8c9647769 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 14 Jul 2021 09:47:11 +0200 Subject: [PATCH 0036/1284] DateTimeImmutable removed from DateTimeInterface results in DateTime --- src/Type/TypeCombinator.php | 12 +++++++- .../Analyser/NodeScopeResolverTest.php | 1 + tests/PHPStan/Analyser/data/bug-5259.php | 30 +++++++++++++++++++ tests/PHPStan/Analyser/data/native-types.php | 2 +- .../Methods/CallStaticMethodsRuleTest.php | 6 ++++ tests/PHPStan/Rules/Methods/data/bug-5259.php | 15 ++++++++++ 6 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/bug-5259.php create mode 100644 tests/PHPStan/Rules/Methods/data/bug-5259.php diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index 0bfa304487..8ea1154757 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -120,7 +120,17 @@ public static function remove(Type $fromType, Type $typeToRemove): Type if ($typeToRemove instanceof AccessoryNonEmptyStringType) { return new ConstantStringType(''); } - } elseif ($fromType instanceof SubtractableType) { + } elseif ($fromType instanceof ObjectType && $fromType->getClassName() === \DateTimeInterface::class) { + if ($typeToRemove instanceof ObjectType && $typeToRemove->getClassName() === \DateTimeImmutable::class) { + return new ObjectType(\DateTime::class); + } + + if ($typeToRemove instanceof ObjectType && $typeToRemove->getClassName() === \DateTime::class) { + return new ObjectType(\DateTimeImmutable::class); + } + } + + if ($fromType instanceof SubtractableType) { $typeToSubtractFrom = $fromType; if ($fromType instanceof TemplateType) { $typeToSubtractFrom = $fromType->getBound(); diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index e38bdbb669..df53443533 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -440,6 +440,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/generic-object-lower-bound.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/class-reflection-interfaces.php'); yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/bug-4415.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5259.php'); } /** diff --git a/tests/PHPStan/Analyser/data/bug-5259.php b/tests/PHPStan/Analyser/data/bug-5259.php new file mode 100644 index 0000000000..35e9bca126 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-5259.php @@ -0,0 +1,30 @@ +checkThisOnly = false; + $this->analyse([__DIR__ . '/data/bug-5259.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-5259.php b/tests/PHPStan/Rules/Methods/data/bug-5259.php new file mode 100644 index 0000000000..de650ccc0b --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-5259.php @@ -0,0 +1,15 @@ +date = $date instanceof \DateTimeImmutable ? $date : \DateTimeImmutable::createFromMutable($date); + } +} From 520ae229d592cd425e2ef1ea5eaa2816fe08f89c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 14 Jul 2021 10:13:53 +0200 Subject: [PATCH 0037/1284] Fix implode() for non-empty-string return type --- conf/config.neon | 5 ++ .../ImplodeFunctionReturnTypeExtension.php | 69 ++++++++++++++ .../Analyser/data/non-empty-string.php | 89 +++++++++++++++++++ 3 files changed, 163 insertions(+) create mode 100644 src/Type/Php/ImplodeFunctionReturnTypeExtension.php diff --git a/conf/config.neon b/conf/config.neon index 3ca2493c7b..7c15aabf56 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1250,6 +1250,11 @@ services: tags: - phpstan.broker.dynamicFunctionReturnTypeExtension + - + class: PHPStan\Type\Php\ImplodeFunctionReturnTypeExtension + tags: + - phpstan.broker.dynamicFunctionReturnTypeExtension + - class: PHPStan\Type\Php\ParseUrlFunctionDynamicReturnTypeExtension tags: diff --git a/src/Type/Php/ImplodeFunctionReturnTypeExtension.php b/src/Type/Php/ImplodeFunctionReturnTypeExtension.php new file mode 100644 index 0000000000..7423e287a2 --- /dev/null +++ b/src/Type/Php/ImplodeFunctionReturnTypeExtension.php @@ -0,0 +1,69 @@ +getName(), [ + 'implode', + 'join', + ], true); + } + + public function getTypeFromFunctionCall( + FunctionReflection $functionReflection, + FuncCall $functionCall, + Scope $scope + ): \PHPStan\Type\Type + { + $args = $functionCall->args; + if (count($args) === 1) { + $argType = $scope->getType($args[0]->value); + if ($argType->isArray()->yes()) { + if ($argType->isIterableAtLeastOnce()->yes() && $argType->getIterableValueType()->isNonEmptyString()->yes()) { + return new IntersectionType([ + new StringType(), + new AccessoryNonEmptyStringType(), + ]); + } + + return new StringType(); + } + } + + if (count($args) !== 2) { + return new StringType(); + } + + $separatorType = $scope->getType($args[0]->value); + $arrayType = $scope->getType($args[1]->value); + if ($arrayType->isIterableAtLeastOnce()->yes()) { + if ($arrayType->getIterableValueType()->isNonEmptyString()->yes()) { + return new IntersectionType([ + new StringType(), + new AccessoryNonEmptyStringType(), + ]); + } + if ($separatorType->isNonEmptyString()->yes()) { + return new IntersectionType([ + new StringType(), + new AccessoryNonEmptyStringType(), + ]); + } + } + + return new StringType(); + } + +} diff --git a/tests/PHPStan/Analyser/data/non-empty-string.php b/tests/PHPStan/Analyser/data/non-empty-string.php index 9d3469ad3d..2191ce4149 100644 --- a/tests/PHPStan/Analyser/data/non-empty-string.php +++ b/tests/PHPStan/Analyser/data/non-empty-string.php @@ -130,3 +130,92 @@ public function doEmpty2(string $s): void } } + +class ImplodingStrings +{ + + /** + * @param array $commonStrings + */ + public function doFoo(string $s, array $commonStrings): void + { + assertType('string', implode($s, $commonStrings)); + assertType('string', implode(' ', $commonStrings)); + assertType('string', implode('', $commonStrings)); + assertType('string', implode($commonStrings)); + } + + /** + * @param non-empty-array $nonEmptyArrayWithStrings + */ + public function doFoo2(string $s, array $nonEmptyArrayWithStrings): void + { + assertType('string', implode($s, $nonEmptyArrayWithStrings)); + assertType('string', implode('', $nonEmptyArrayWithStrings)); + assertType('non-empty-string', implode(' ', $nonEmptyArrayWithStrings)); + assertType('string', implode($nonEmptyArrayWithStrings)); + } + + /** + * @param array $arrayWithNonEmptyStrings + */ + public function doFoo3(string $s, array $arrayWithNonEmptyStrings): void + { + assertType('string', implode($s, $arrayWithNonEmptyStrings)); + assertType('string', implode('', $arrayWithNonEmptyStrings)); + assertType('string', implode(' ', $arrayWithNonEmptyStrings)); + assertType('string', implode($arrayWithNonEmptyStrings)); + } + + /** + * @param non-empty-array $nonEmptyArrayWithNonEmptyStrings + */ + public function doFoo4(string $s, array $nonEmptyArrayWithNonEmptyStrings): void + { + assertType('non-empty-string', implode($s, $nonEmptyArrayWithNonEmptyStrings)); + assertType('non-empty-string', implode('', $nonEmptyArrayWithNonEmptyStrings)); + assertType('non-empty-string', implode(' ', $nonEmptyArrayWithNonEmptyStrings)); + assertType('non-empty-string', implode($nonEmptyArrayWithNonEmptyStrings)); + } + + public function sayHello(): void + { + // coming from issue #5291 + $s = array(1,2); + + assertType('non-empty-string', implode("a", $s)); + } + + /** + * @param non-empty-string $glue + */ + public function nonE($glue, array $a) { + // coming from issue #5291 + if (empty($a)) { + return "xyz"; + } + + assertType('non-empty-string', implode($glue, $a)); + } + + public function sayHello2(): void + { + // coming from issue #5291 + $s = array(1,2); + + assertType('non-empty-string', join("a", $s)); + } + + /** + * @param non-empty-string $glue + */ + public function nonE2($glue, array $a) { + // coming from issue #5291 + if (empty($a)) { + return "xyz"; + } + + assertType('non-empty-string', join($glue, $a)); + } + +} From 1283db7ae426142af99b41a480c964fb8a2c8c83 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 14 Jul 2021 10:35:56 +0200 Subject: [PATCH 0038/1284] Fixed encapsed string and concat in regard to non-empty-string --- src/Analyser/MutatingScope.php | 46 +++++++++++++++---- .../Analyser/LegacyNodeScopeResolverTest.php | 10 ++-- .../Analyser/data/non-empty-string.php | 19 ++++++++ .../CallToFunctionParametersRuleTest.php | 8 ++-- 4 files changed, 64 insertions(+), 19 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index c0c03073e0..d4333827d5 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -45,6 +45,7 @@ use PHPStan\Reflection\TrivialParametersAcceptor; use PHPStan\Rules\Properties\PropertyReflectionFinder; use PHPStan\TrinaryLogic; +use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\ArrayType; use PHPStan\Type\BenevolentUnionType; use PHPStan\Type\BooleanType; @@ -1045,6 +1046,13 @@ private function resolveType(Expr $node): Type return $leftStringType->append($rightStringType); } + if ($leftStringType->isNonEmptyString()->or($rightStringType->isNonEmptyString())->yes()) { + return new IntersectionType([ + new StringType(), + new AccessoryNonEmptyStringType(), + ]); + } + return new StringType(); } @@ -1316,22 +1324,40 @@ private function resolveType(Expr $node): Type } elseif ($node instanceof String_) { return new ConstantStringType($node->value); } elseif ($node instanceof Node\Scalar\Encapsed) { - $constantString = new ConstantStringType(''); + $parts = []; foreach ($node->parts as $part) { if ($part instanceof EncapsedStringPart) { - $partStringType = new ConstantStringType($part->value); - } else { - $partStringType = $this->getType($part)->toString(); - if ($partStringType instanceof ErrorType) { - return new ErrorType(); - } - if (!$partStringType instanceof ConstantStringType) { - return new StringType(); + $parts[] = new ConstantStringType($part->value); + continue; + } + + $partStringType = $this->getType($part)->toString(); + if ($partStringType instanceof ErrorType) { + return new ErrorType(); + } + + $parts[] = $partStringType; + } + + $constantString = new ConstantStringType(''); + foreach ($parts as $part) { + if ($part instanceof ConstantStringType) { + $constantString = $constantString->append($part); + continue; + } + + foreach ($parts as $partType) { + if ($partType->isNonEmptyString()->yes()) { + return new IntersectionType([ + new StringType(), + new AccessoryNonEmptyStringType(), + ]); } } - $constantString = $constantString->append($partStringType); + return new StringType(); } + return $constantString; } elseif ($node instanceof DNumber) { return new ConstantFloatType($node->value); diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 17f98104ae..3744a661ac 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -2496,11 +2496,11 @@ public function dataBinaryOperations(): array 'min(1, 2.2, 3.3)', ], [ - 'string', + 'non-empty-string', '"Hello $world"', ], [ - 'string', + 'non-empty-string', '$string .= "str"', ], [ @@ -3072,15 +3072,15 @@ public function dataBinaryOperations(): array '$decrementedFooString', ], [ - 'string', + 'non-empty-string', '$conditionalString . $conditionalString', ], [ - 'string', + 'non-empty-string', '$conditionalString . $anotherConditionalString', ], [ - 'string', + 'non-empty-string', '$anotherConditionalString . $conditionalString', ], [ diff --git a/tests/PHPStan/Analyser/data/non-empty-string.php b/tests/PHPStan/Analyser/data/non-empty-string.php index 2191ce4149..2a441112e3 100644 --- a/tests/PHPStan/Analyser/data/non-empty-string.php +++ b/tests/PHPStan/Analyser/data/non-empty-string.php @@ -219,3 +219,22 @@ public function nonE2($glue, array $a) { } } + +class LiteralString +{ + + function x(string $tableName, string $original): void { + assertType('non-empty-string', "from `$tableName`"); + } + + /** + * @param non-empty-string $nonEmpty + */ + function concat(string $s, string $nonEmpty): void + { + assertType('string', $s . ''); + assertType('non-empty-string', $nonEmpty . ''); + assertType('non-empty-string', $nonEmpty . $s); + } + +} diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index c09852b378..ae41bc2f3a 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -563,11 +563,11 @@ public function testArrayReduceCallback(): void 5, ], [ - 'Parameter #2 $callback of function array_reduce expects callable(string|null, int): string|null, Closure(string, int): string given.', + 'Parameter #2 $callback of function array_reduce expects callable(non-empty-string|null, int): non-empty-string|null, Closure(string, int): non-empty-string given.', 13, ], [ - 'Parameter #2 $callback of function array_reduce expects callable(string|null, int): string|null, Closure(string, int): string given.', + 'Parameter #2 $callback of function array_reduce expects callable(non-empty-string|null, int): non-empty-string|null, Closure(string, int): non-empty-string given.', 22, ], ]); @@ -584,11 +584,11 @@ public function testArrayReduceArrowFunctionCallback(): void 5, ], [ - 'Parameter #2 $callback of function array_reduce expects callable(string|null, int): string|null, Closure(string, int): string given.', + 'Parameter #2 $callback of function array_reduce expects callable(non-empty-string|null, int): non-empty-string|null, Closure(string, int): non-empty-string given.', 11, ], [ - 'Parameter #2 $callback of function array_reduce expects callable(string|null, int): string|null, Closure(string, int): string given.', + 'Parameter #2 $callback of function array_reduce expects callable(non-empty-string|null, int): non-empty-string|null, Closure(string, int): non-empty-string given.', 18, ], ]); From 5fbfc14fb9636da9b4d4506f91186efec49023fc Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 14 Jul 2021 11:24:19 +0200 Subject: [PATCH 0039/1284] Test IntersectionType - describe itself as string with typeOnly verbosity level --- .../CallToFunctionParametersRuleTest.php | 4 ++++ .../Rules/Functions/data/explode-80.php | 1 + .../Rules/Methods/CallMethodsRuleTest.php | 13 ++++++++++++ .../data/non-empty-string-verbosity.php | 21 +++++++++++++++++++ 4 files changed, 39 insertions(+) create mode 100644 tests/PHPStan/Rules/Methods/data/non-empty-string-verbosity.php diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index ae41bc2f3a..5557501c16 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -790,6 +790,10 @@ public function testExplode(): void 'Parameter #1 $separator of function explode expects non-empty-string, \'\' given.', 16, ], + [ + 'Parameter #1 $separator of function explode expects non-empty-string, 1 given.', + 17, + ], ]); } diff --git a/tests/PHPStan/Rules/Functions/data/explode-80.php b/tests/PHPStan/Rules/Functions/data/explode-80.php index 76c0a3a5e0..9e207bfd8d 100644 --- a/tests/PHPStan/Rules/Functions/data/explode-80.php +++ b/tests/PHPStan/Rules/Functions/data/explode-80.php @@ -14,6 +14,7 @@ public function doFoo( explode($s, 'foo'); explode($nonEmptyString, 'foo'); explode('', 'foo'); + explode(1, 'foo'); } } diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 88c202eb02..ca301bc9e8 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -2008,4 +2008,17 @@ public function testGenericObjectLowerBound(): void ]); } + public function testNonEmptyStringVerbosity(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/non-empty-string-verbosity.php'], [ + [ + 'Parameter #1 $i of method NonEmptyStringVerbosity\Foo::doBar() expects int, string given.', + 13, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/non-empty-string-verbosity.php b/tests/PHPStan/Rules/Methods/data/non-empty-string-verbosity.php new file mode 100644 index 0000000000..3437ea767e --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/non-empty-string-verbosity.php @@ -0,0 +1,21 @@ +doBar($s); + } + + public function doBar(int $i): void + { + + } + +} From b864a95b4616dd62a2df48908a103179946a4589 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 14 Jul 2021 11:22:14 +0200 Subject: [PATCH 0040/1284] Fix inferring non-empty-string in array_map closure --- src/Analyser/MutatingScope.php | 7 ++- .../Php/PhpClassReflectionExtension.php | 3 +- ...MissingClosureNativeReturnTypehintRule.php | 3 +- src/Type/ArrayType.php | 2 +- src/Type/Constant/ConstantArrayType.php | 11 ++-- .../Constant/ConstantArrayTypeBuilder.php | 3 +- src/Type/Constant/ConstantStringType.php | 13 ++++- src/Type/ConstantType.php | 2 +- src/Type/GeneralizePrecision.php | 44 ++++++++++++++ src/Type/Generic/TemplateTypeHelper.php | 3 +- src/Type/IntegerRangeType.php | 2 +- src/Type/IntersectionType.php | 2 +- src/Type/NullType.php | 2 +- ...gumentBasedFunctionReturnTypeExtension.php | 5 +- ...ergeFunctionDynamicReturnTypeExtension.php | 3 +- .../Php/RangeFunctionReturnTypeExtension.php | 5 +- src/Type/Traits/ConstantScalarTypeTrait.php | 3 +- src/Type/TypeCombinator.php | 2 +- src/Type/TypeUtils.php | 6 +- src/Type/UnionType.php | 2 +- .../Analyser/LegacyNodeScopeResolverTest.php | 10 ++-- .../Analyser/NodeScopeResolverTest.php | 1 + tests/PHPStan/Analyser/ScopeTest.php | 6 +- tests/PHPStan/Analyser/data/bug-5293.php | 58 +++++++++++++++++++ .../Analyser/data/non-empty-string.php | 27 +++++++++ .../Type/Constant/ConstantStringTypeTest.php | 13 +++-- tests/PHPStan/Type/TypeCombinatorTest.php | 4 +- 27 files changed, 199 insertions(+), 43 deletions(-) create mode 100644 src/Type/GeneralizePrecision.php create mode 100644 tests/PHPStan/Analyser/data/bug-5293.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index d4333827d5..5708e5cb9d 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -63,6 +63,7 @@ use PHPStan\Type\DynamicReturnTypeExtensionRegistry; use PHPStan\Type\ErrorType; use PHPStan\Type\FloatType; +use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\Generic\GenericClassStringType; use PHPStan\Type\Generic\GenericObjectType; use PHPStan\Type\Generic\TemplateType; @@ -1934,7 +1935,7 @@ private function resolveType(Expr $node): Type $constantType instanceof ConstantType && in_array(sprintf('%s::%s', $propertyClassReflection->getName(), $constantName), $this->dynamicConstantNames, true) ) { - $constantType = $constantType->generalize(); + $constantType = $constantType->generalize(GeneralizePrecision::lessSpecific()); } $types[] = $constantType; } @@ -2196,7 +2197,7 @@ private function getNullsafeShortCircuitingType(Expr $expr, Type $type): Type private function resolveConstantType(string $constantName, Type $constantType): Type { if ($constantType instanceof ConstantType && in_array($constantName, $this->dynamicConstantNames, true)) { - return $constantType->generalize(); + return $constantType->generalize(GeneralizePrecision::lessSpecific()); } return $constantType; @@ -4497,7 +4498,7 @@ private static function generalizeType(Type $a, Type $b): Type continue; } - $resultTypes[] = TypeUtils::generalizeType($constantTypes['a'][0]); + $resultTypes[] = TypeUtils::generalizeType($constantTypes['a'][0], GeneralizePrecision::moreSpecific()); } if (count($constantArrays['a']) > 0) { diff --git a/src/Reflection/Php/PhpClassReflectionExtension.php b/src/Reflection/Php/PhpClassReflectionExtension.php index 0bd6ffd796..b88e6dace3 100644 --- a/src/Reflection/Php/PhpClassReflectionExtension.php +++ b/src/Reflection/Php/PhpClassReflectionExtension.php @@ -35,6 +35,7 @@ use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\ErrorType; use PHPStan\Type\FileTypeMapper; +use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\Generic\TemplateTypeHelper; use PHPStan\Type\Generic\TemplateTypeMap; use PHPStan\Type\MixedType; @@ -994,7 +995,7 @@ private function inferAndCachePropertyTypes( continue; } - $propertyType = TypeUtils::generalizeType($propertyType); + $propertyType = TypeUtils::generalizeType($propertyType, GeneralizePrecision::lessSpecific()); if ($propertyType instanceof ConstantArrayType) { $propertyType = new ArrayType(new MixedType(true), new MixedType(true)); } diff --git a/src/Rules/Missing/MissingClosureNativeReturnTypehintRule.php b/src/Rules/Missing/MissingClosureNativeReturnTypehintRule.php index b9154d40c2..c51602e500 100644 --- a/src/Rules/Missing/MissingClosureNativeReturnTypehintRule.php +++ b/src/Rules/Missing/MissingClosureNativeReturnTypehintRule.php @@ -7,6 +7,7 @@ use PHPStan\Node\ClosureReturnStatementsNode; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; +use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\IntersectionType; use PHPStan\Type\MixedType; use PHPStan\Type\NeverType; @@ -112,7 +113,7 @@ public function processNode(Node $node, Scope $scope): array return $messages; } - $returnType = TypeUtils::generalizeType($returnType); + $returnType = TypeUtils::generalizeType($returnType, GeneralizePrecision::lessSpecific()); $description = $returnType->describe(VerbosityLevel::typeOnly()); if ($returnType->isArray()->yes()) { $description = 'array'; diff --git a/src/Type/ArrayType.php b/src/Type/ArrayType.php index b9023e2503..1696294001 100644 --- a/src/Type/ArrayType.php +++ b/src/Type/ArrayType.php @@ -147,7 +147,7 @@ function () use ($level, $isMixedKeyType, $isMixedItemType): string { public function generalizeValues(): self { - return new self($this->keyType, TypeUtils::generalizeType($this->itemType)); + return new self($this->keyType, TypeUtils::generalizeType($this->itemType, GeneralizePrecision::lessSpecific())); } public function getKeysArray(): self diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 19c61f800f..306510e5a5 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -13,6 +13,7 @@ use PHPStan\Type\CompoundType; use PHPStan\Type\ConstantType; use PHPStan\Type\ErrorType; +use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\Generic\GenericClassStringType; use PHPStan\Type\Generic\TemplateMixedType; use PHPStan\Type\Generic\TemplateTypeMap; @@ -498,7 +499,7 @@ public function unsetOffset(Type $offsetType): Type $arrays[] = new self($tmp->keyTypes, $tmp->valueTypes, $tmp->nextAutoIndex, array_keys($tmp->keyTypes)); } - return TypeUtils::generalizeType(TypeCombinator::union(...$arrays)); + return TypeUtils::generalizeType(TypeCombinator::union(...$arrays), GeneralizePrecision::moreSpecific()); } public function isIterableAtLeastOnce(): TrinaryLogic @@ -618,15 +619,15 @@ public function toFloat(): Type return $this->toBoolean()->toFloat(); } - public function generalize(): Type + public function generalize(?GeneralizePrecision $precision = null): Type { if (count($this->keyTypes) === 0) { return $this; } $arrayType = new ArrayType( - TypeUtils::generalizeType($this->getKeyType()), - TypeUtils::generalizeType($this->getItemType()) + TypeUtils::generalizeType($this->getKeyType(), $precision), + TypeUtils::generalizeType($this->getItemType(), $precision) ); if (count($this->keyTypes) > count($this->optionalKeys)) { @@ -643,7 +644,7 @@ public function generalizeValues(): ArrayType { $valueTypes = []; foreach ($this->valueTypes as $valueType) { - $valueTypes[] = TypeUtils::generalizeType($valueType); + $valueTypes[] = TypeUtils::generalizeType($valueType, GeneralizePrecision::lessSpecific()); } return new self($this->keyTypes, $valueTypes, $this->nextAutoIndex, $this->optionalKeys); diff --git a/src/Type/Constant/ConstantArrayTypeBuilder.php b/src/Type/Constant/ConstantArrayTypeBuilder.php index 0cbc15dae7..6fd74c32e1 100644 --- a/src/Type/Constant/ConstantArrayTypeBuilder.php +++ b/src/Type/Constant/ConstantArrayTypeBuilder.php @@ -4,6 +4,7 @@ use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\ArrayType; +use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use PHPStan\Type\TypeUtils; @@ -100,7 +101,7 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $opt return; } - $this->keyTypes[] = TypeUtils::generalizeType($offsetType); + $this->keyTypes[] = TypeUtils::generalizeType($offsetType, GeneralizePrecision::moreSpecific()); $this->valueTypes[] = $valueType; $this->degradeToGeneralArray = true; } diff --git a/src/Type/Constant/ConstantStringType.php b/src/Type/Constant/ConstantStringType.php index 68788bf1cc..d2e45eff54 100644 --- a/src/Type/Constant/ConstantStringType.php +++ b/src/Type/Constant/ConstantStringType.php @@ -8,13 +8,16 @@ use PHPStan\Reflection\InaccessibleMethod; use PHPStan\Reflection\TrivialParametersAcceptor; use PHPStan\TrinaryLogic; +use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\ClassStringType; use PHPStan\Type\CompoundType; use PHPStan\Type\ConstantScalarType; use PHPStan\Type\ErrorType; +use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\Generic\GenericClassStringType; use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\IntegerRangeType; +use PHPStan\Type\IntersectionType; use PHPStan\Type\MixedType; use PHPStan\Type\NullType; use PHPStan\Type\ObjectType; @@ -292,11 +295,19 @@ public function append(self $otherString): self return new self($this->getValue() . $otherString->getValue()); } - public function generalize(): Type + public function generalize(?GeneralizePrecision $precision = null): Type { if ($this->isClassString) { return new ClassStringType(); } + + if ($this->getValue() !== '' && $precision !== null && $precision->isMoreSpecific()) { + return new IntersectionType([ + new StringType(), + new AccessoryNonEmptyStringType(), + ]); + } + return new StringType(); } diff --git a/src/Type/ConstantType.php b/src/Type/ConstantType.php index b4ebf5ca84..cb0d7fe026 100644 --- a/src/Type/ConstantType.php +++ b/src/Type/ConstantType.php @@ -6,6 +6,6 @@ interface ConstantType extends Type { - public function generalize(): Type; + public function generalize(?GeneralizePrecision $precision = null): Type; } diff --git a/src/Type/GeneralizePrecision.php b/src/Type/GeneralizePrecision.php new file mode 100644 index 0000000000..d0f9a218f6 --- /dev/null +++ b/src/Type/GeneralizePrecision.php @@ -0,0 +1,44 @@ +value = $value; + } + + private static function create(int $value): self + { + self::$registry[$value] = self::$registry[$value] ?? new self($value); + return self::$registry[$value]; + } + + /** @api */ + public static function lessSpecific(): self + { + return self::create(self::LESS_SPECIFIC); + } + + /** @api */ + public static function moreSpecific(): self + { + return self::create(self::MORE_SPECIFIC); + } + + public function isMoreSpecific(): bool + { + return $this->value === self::MORE_SPECIFIC; + } + +} diff --git a/src/Type/Generic/TemplateTypeHelper.php b/src/Type/Generic/TemplateTypeHelper.php index 9c8165bcdc..a466b8f9ed 100644 --- a/src/Type/Generic/TemplateTypeHelper.php +++ b/src/Type/Generic/TemplateTypeHelper.php @@ -5,6 +5,7 @@ use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\ConstantType; use PHPStan\Type\ErrorType; +use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\Type; use PHPStan\Type\TypeTraverser; @@ -66,7 +67,7 @@ public static function generalizeType(Type $type): Type { return TypeTraverser::map($type, static function (Type $type, callable $traverse): Type { if ($type instanceof ConstantType && !$type instanceof ConstantArrayType) { - return $type->generalize(); + return $type->generalize(GeneralizePrecision::lessSpecific()); } return $traverse($type); diff --git a/src/Type/IntegerRangeType.php b/src/Type/IntegerRangeType.php index 1703fd5a25..7d2982c8d7 100644 --- a/src/Type/IntegerRangeType.php +++ b/src/Type/IntegerRangeType.php @@ -269,7 +269,7 @@ public function equals(Type $type): bool } - public function generalize(): Type + public function generalize(?GeneralizePrecision $precision = null): Type { return new parent(); } diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index 6355a69a4b..378fa51da5 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -132,7 +132,7 @@ function () use ($level): string { if ($type instanceof AccessoryType) { continue; } - $typeNames[] = TypeUtils::generalizeType($type)->describe($level); + $typeNames[] = TypeUtils::generalizeType($type, GeneralizePrecision::lessSpecific())->describe($level); } return implode('&', $typeNames); diff --git a/src/Type/NullType.php b/src/Type/NullType.php index cdbce4ebba..f535ea4db6 100644 --- a/src/Type/NullType.php +++ b/src/Type/NullType.php @@ -45,7 +45,7 @@ public function getValue() return null; } - public function generalize(): Type + public function generalize(?GeneralizePrecision $precision = null): Type { return $this; } diff --git a/src/Type/Php/ArgumentBasedFunctionReturnTypeExtension.php b/src/Type/Php/ArgumentBasedFunctionReturnTypeExtension.php index 29ede2987c..8525cca74f 100644 --- a/src/Type/Php/ArgumentBasedFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArgumentBasedFunctionReturnTypeExtension.php @@ -7,6 +7,7 @@ use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Type\ArrayType; +use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\Type; use PHPStan\Type\TypeUtils; @@ -54,8 +55,8 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $argumentKeyType = $argumentType->getIterableKeyType(); $argumentValueType = $argumentType->getIterableValueType(); if ($argument->unpack) { - $argumentKeyType = TypeUtils::generalizeType($argumentKeyType); - $argumentValueType = TypeUtils::generalizeType($argumentValueType->getIterableValueType()); + $argumentKeyType = TypeUtils::generalizeType($argumentKeyType, GeneralizePrecision::moreSpecific()); + $argumentValueType = TypeUtils::generalizeType($argumentValueType->getIterableValueType(), GeneralizePrecision::moreSpecific()); } return new ArrayType( diff --git a/src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php index 38bb6b3141..fa7703cd9c 100644 --- a/src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php @@ -7,6 +7,7 @@ use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Type\ArrayType; +use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use PHPStan\Type\TypeUtils; @@ -39,7 +40,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, } } - $keyTypes[] = TypeUtils::generalizeType($argType->getIterableKeyType()); + $keyTypes[] = TypeUtils::generalizeType($argType->getIterableKeyType(), GeneralizePrecision::moreSpecific()); $valueTypes[] = $argType->getIterableValueType(); } diff --git a/src/Type/Php/RangeFunctionReturnTypeExtension.php b/src/Type/Php/RangeFunctionReturnTypeExtension.php index 1fadd6b2df..101cb8d09f 100644 --- a/src/Type/Php/RangeFunctionReturnTypeExtension.php +++ b/src/Type/Php/RangeFunctionReturnTypeExtension.php @@ -14,6 +14,7 @@ use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\FloatType; +use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\IntegerType; use PHPStan\Type\IntersectionType; use PHPStan\Type\StringType; @@ -68,8 +69,8 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, new ArrayType( new IntegerType(), TypeCombinator::union( - $startConstant->generalize(), - $endConstant->generalize() + $startConstant->generalize(GeneralizePrecision::moreSpecific()), + $endConstant->generalize(GeneralizePrecision::moreSpecific()) ) ), new NonEmptyArrayType(), diff --git a/src/Type/Traits/ConstantScalarTypeTrait.php b/src/Type/Traits/ConstantScalarTypeTrait.php index fc24cc9abc..0ac64800cd 100644 --- a/src/Type/Traits/ConstantScalarTypeTrait.php +++ b/src/Type/Traits/ConstantScalarTypeTrait.php @@ -6,6 +6,7 @@ use PHPStan\Type\CompoundType; use PHPStan\Type\CompoundTypeHelper; use PHPStan\Type\ConstantScalarType; +use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\Type; trait ConstantScalarTypeTrait @@ -72,7 +73,7 @@ public function isSmallerThanOrEqual(Type $otherType): TrinaryLogic return TrinaryLogic::createMaybe(); } - public function generalize(): Type + public function generalize(?GeneralizePrecision $precision = null): Type { return new parent(); } diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index 8ea1154757..1958c21029 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -290,7 +290,7 @@ public static function union(Type ...$types): Type } foreach ($scalarTypeItems as $type) { if (count($scalarTypeItems) > self::CONSTANT_SCALAR_UNION_THRESHOLD) { - $types[] = $type->generalize(); + $types[] = $type->generalize(GeneralizePrecision::moreSpecific()); if ($type instanceof ConstantStringType) { continue; diff --git a/src/Type/TypeUtils.php b/src/Type/TypeUtils.php index 70fcb4cc86..6437a16585 100644 --- a/src/Type/TypeUtils.php +++ b/src/Type/TypeUtils.php @@ -119,11 +119,11 @@ public static function getAnyArrays(Type $type): array return self::map(ArrayType::class, $type, true, false); } - public static function generalizeType(Type $type): Type + public static function generalizeType(Type $type, ?GeneralizePrecision $precision = null): Type { - return TypeTraverser::map($type, static function (Type $type, callable $traverse): Type { + return TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($precision): Type { if ($type instanceof ConstantType) { - return $type->generalize(); + return $type->generalize($precision); } return $traverse($type); diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index cb52b89760..d951eb6d27 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -164,7 +164,7 @@ function () use ($joinTypes): string { $type instanceof ConstantType && !$type instanceof ConstantBooleanType ) { - return $type->generalize(); + return $type->generalize(GeneralizePrecision::moreSpecific()); } return $type; diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 3744a661ac..d35ded7879 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -5129,7 +5129,7 @@ public function dataArrayFunctions(): array 'array_values($generalStringKeys)', ], [ - 'array', + 'array', 'array_merge($stringOrIntegerKeys)', ], [ @@ -5145,15 +5145,15 @@ public function dataArrayFunctions(): array 'array_merge($stringOrIntegerKeys, $generalStringKeys)', ], [ - 'array', + 'array', 'array_merge($stringKeys, $stringOrIntegerKeys)', ], [ - 'array', + 'array', 'array_merge($stringOrIntegerKeys, $stringKeys)', ], [ - 'array', + 'array', 'array_merge(array("color" => "red", 2, 4), array("a", "b", "color" => "green", "shape" => "trapezoid", 4))', ], [ @@ -9233,7 +9233,7 @@ public function dataIsset(): array '$anotherArrayCopy', ], [ - 'array', + 'array', '$yetAnotherArrayCopy', ], [ diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index df53443533..a6afe8cc63 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -441,6 +441,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/class-reflection-interfaces.php'); yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/bug-4415.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5259.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5293.php'); } /** diff --git a/tests/PHPStan/Analyser/ScopeTest.php b/tests/PHPStan/Analyser/ScopeTest.php index 4c1f9c663c..472563fd54 100644 --- a/tests/PHPStan/Analyser/ScopeTest.php +++ b/tests/PHPStan/Analyser/ScopeTest.php @@ -28,7 +28,7 @@ public function dataGeneralize(): array [ new ConstantStringType('a'), new ConstantStringType('b'), - 'string', + 'non-empty-string', ], [ new ConstantIntegerType(0), @@ -138,7 +138,7 @@ public function dataGeneralize(): array new ConstantIntegerType(1), new ConstantIntegerType(1), ]), - 'array', + 'array', ], [ new ConstantArrayType([ @@ -153,7 +153,7 @@ public function dataGeneralize(): array new ConstantIntegerType(1), new ConstantIntegerType(2), ]), - 'array', + 'array', ], ]; } diff --git a/tests/PHPStan/Analyser/data/bug-5293.php b/tests/PHPStan/Analyser/data/bug-5293.php new file mode 100644 index 0000000000..63f30fe000 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-5293.php @@ -0,0 +1,58 @@ + $a + * @param non-empty-string $s + */ + public function doFoo(array $a, string $s): void + { + $a[$s] = 2; + + // there might be non-empty-string that becomes a number instead + assertType('array&nonEmpty', $a); + } + + /** + * @param array $a + * @param non-empty-string $s + */ + public function doFoo2(array $a, string $s): void + { + $a[''] = 2; + assertType('array&nonEmpty', $a); + } + +} diff --git a/tests/PHPStan/Type/Constant/ConstantStringTypeTest.php b/tests/PHPStan/Type/Constant/ConstantStringTypeTest.php index 5e28ae9a68..d7839946af 100644 --- a/tests/PHPStan/Type/Constant/ConstantStringTypeTest.php +++ b/tests/PHPStan/Type/Constant/ConstantStringTypeTest.php @@ -4,6 +4,7 @@ use PHPStan\Testing\TestCase; use PHPStan\TrinaryLogic; +use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\Generic\GenericClassStringType; use PHPStan\Type\Generic\TemplateTypeFactory; use PHPStan\Type\Generic\TemplateTypeScope; @@ -142,10 +143,14 @@ public function testIsSuperTypeOf(ConstantStringType $type, Type $otherType, Tri public function testGeneralize(): void { - $this->assertSame('string', (new ConstantStringType('NonexistentClass'))->generalize()->describe(VerbosityLevel::precise())); - $this->assertSame('string', (new ConstantStringType(\stdClass::class))->generalize()->describe(VerbosityLevel::precise())); - $this->assertSame('class-string', (new ConstantStringType(\stdClass::class, true))->generalize()->describe(VerbosityLevel::precise())); - $this->assertSame('class-string', (new ConstantStringType('NonexistentClass', true))->generalize()->describe(VerbosityLevel::precise())); + $this->assertSame('non-empty-string', (new ConstantStringType('NonexistentClass'))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); + $this->assertSame('string', (new ConstantStringType(''))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); + $this->assertSame('non-empty-string', (new ConstantStringType('a'))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); + $this->assertSame('string', (new ConstantStringType(''))->generalize(GeneralizePrecision::lessSpecific())->describe(VerbosityLevel::precise())); + $this->assertSame('string', (new ConstantStringType('a'))->generalize(GeneralizePrecision::lessSpecific())->describe(VerbosityLevel::precise())); + $this->assertSame('non-empty-string', (new ConstantStringType(\stdClass::class))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); + $this->assertSame('class-string', (new ConstantStringType(\stdClass::class, true))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); + $this->assertSame('class-string', (new ConstantStringType('NonexistentClass', true))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); } public function testTextInvalidEncoding(): void diff --git a/tests/PHPStan/Type/TypeCombinatorTest.php b/tests/PHPStan/Type/TypeCombinatorTest.php index aa509b74fe..aa3c4ac0cb 100644 --- a/tests/PHPStan/Type/TypeCombinatorTest.php +++ b/tests/PHPStan/Type/TypeCombinatorTest.php @@ -841,8 +841,8 @@ public function dataUnion(): array new ConstantStringType('loremm'), new ConstantStringType('loremmm'), ], - StringType::class, - 'string', + IntersectionType::class, + 'non-empty-string', ], [ [ From 4a9e0695fcc57610804685722f136b5830b96c2d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 14 Jul 2021 16:00:14 +0200 Subject: [PATCH 0041/1284] Dynamic return type extension for functions that return non-empty-string when given one --- conf/config.neon | 10 ++++ ...mptyStringFunctionsReturnTypeExtension.php | 54 +++++++++++++++++++ ...intfFunctionDynamicReturnTypeExtension.php | 26 ++++++--- .../Php/StrlenFunctionReturnTypeExtension.php | 45 ++++++++++++++++ tests/PHPStan/Analyser/data/bug-5219.php | 2 +- .../Analyser/data/non-empty-string.php | 42 +++++++++++++++ 6 files changed, 171 insertions(+), 8 deletions(-) create mode 100644 src/Type/Php/NonEmptyStringFunctionsReturnTypeExtension.php create mode 100644 src/Type/Php/StrlenFunctionReturnTypeExtension.php diff --git a/conf/config.neon b/conf/config.neon index 7c15aabf56..8814df2cd1 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1255,6 +1255,16 @@ services: tags: - phpstan.broker.dynamicFunctionReturnTypeExtension + - + class: PHPStan\Type\Php\NonEmptyStringFunctionsReturnTypeExtension + tags: + - phpstan.broker.dynamicFunctionReturnTypeExtension + + - + class: PHPStan\Type\Php\StrlenFunctionReturnTypeExtension + tags: + - phpstan.broker.dynamicFunctionReturnTypeExtension + - class: PHPStan\Type\Php\ParseUrlFunctionDynamicReturnTypeExtension tags: diff --git a/src/Type/Php/NonEmptyStringFunctionsReturnTypeExtension.php b/src/Type/Php/NonEmptyStringFunctionsReturnTypeExtension.php new file mode 100644 index 0000000000..5a09c19d88 --- /dev/null +++ b/src/Type/Php/NonEmptyStringFunctionsReturnTypeExtension.php @@ -0,0 +1,54 @@ +getName(), [ + 'strtoupper', + 'strtolower', + 'mb_strtoupper', + 'mb_strtolower', + 'lcfirst', + 'ucfirst', + 'ucwords', + 'htmlspecialchars', + 'vsprintf', + ], true); + } + + public function getTypeFromFunctionCall( + FunctionReflection $functionReflection, + FuncCall $functionCall, + Scope $scope + ): \PHPStan\Type\Type + { + $args = $functionCall->args; + if (count($args) === 0) { + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } + + $argType = $scope->getType($args[0]->value); + if ($argType->isNonEmptyString()->yes()) { + return new IntersectionType([ + new StringType(), + new AccessoryNonEmptyStringType(), + ]); + } + + return new StringType(); + } + +} diff --git a/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php b/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php index 62016da18f..f885585d2c 100644 --- a/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php @@ -6,8 +6,10 @@ use PHPStan\Analyser\Scope; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\ParametersAcceptorSelector; +use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\ConstantScalarType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; +use PHPStan\Type\IntersectionType; use PHPStan\Type\StringType; use PHPStan\Type\Type; @@ -25,9 +27,23 @@ public function getTypeFromFunctionCall( Scope $scope ): Type { + $args = $functionCall->args; + if (count($args) === 0) { + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } + + $formatType = $scope->getType($args[0]->value); + if ($formatType->isNonEmptyString()->yes()) { + $returnType = new IntersectionType([ + new StringType(), + new AccessoryNonEmptyStringType(), + ]); + } else { + $returnType = new StringType(); + } + $values = []; - $returnType = new StringType(); - foreach ($functionCall->args as $arg) { + foreach ($args as $arg) { $argType = $scope->getType($arg->value); if (!$argType instanceof ConstantScalarType) { return $returnType; @@ -36,13 +52,9 @@ public function getTypeFromFunctionCall( $values[] = $argType->getValue(); } - if (count($values) === 0) { - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - } - $format = array_shift($values); if (!is_string($format)) { - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + return $returnType; } try { diff --git a/src/Type/Php/StrlenFunctionReturnTypeExtension.php b/src/Type/Php/StrlenFunctionReturnTypeExtension.php new file mode 100644 index 0000000000..da048a9556 --- /dev/null +++ b/src/Type/Php/StrlenFunctionReturnTypeExtension.php @@ -0,0 +1,45 @@ +getName() === 'strlen'; + } + + public function getTypeFromFunctionCall( + FunctionReflection $functionReflection, + FuncCall $functionCall, + Scope $scope + ): \PHPStan\Type\Type + { + $args = $functionCall->args; + if (count($args) === 0) { + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } + + $argType = $scope->getType($args[0]->value); + $isNonEmpty = $argType->isNonEmptyString(); + if ($isNonEmpty->yes()) { + return IntegerRangeType::fromInterval(1, null); + } + + if ($isNonEmpty->no()) { + return new ConstantIntegerType(0); + } + + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } + +} diff --git a/tests/PHPStan/Analyser/data/bug-5219.php b/tests/PHPStan/Analyser/data/bug-5219.php index 2ff5b1c47e..c79e9f59a2 100644 --- a/tests/PHPStan/Analyser/data/bug-5219.php +++ b/tests/PHPStan/Analyser/data/bug-5219.php @@ -11,7 +11,7 @@ protected function foo(string $message): void { $header = sprintf('%s-%s', '', implode('-', ['x'])); - assertType('string', $header); + assertType('non-empty-string', $header); assertType('array&nonEmpty', [$header => $message]); } diff --git a/tests/PHPStan/Analyser/data/non-empty-string.php b/tests/PHPStan/Analyser/data/non-empty-string.php index 34d9e889c4..a1e13ec551 100644 --- a/tests/PHPStan/Analyser/data/non-empty-string.php +++ b/tests/PHPStan/Analyser/data/non-empty-string.php @@ -2,7 +2,12 @@ namespace NonEmptyString; +use function htmlspecialchars; +use function lcfirst; use function PHPStan\Testing\assertType; +use function strtolower; +use function strtoupper; +use function ucfirst; class Foo { @@ -265,3 +270,40 @@ public function doFoo2(array $a, string $s): void } } + +class MoreNonEmptyStringFunctions +{ + + /** + * @param non-empty-string $nonEmpty + */ + public function doFoo(string $s, string $nonEmpty) + { + assertType('string', strtoupper($s)); + assertType('non-empty-string', strtoupper($nonEmpty)); + assertType('string', strtolower($s)); + assertType('non-empty-string', strtolower($nonEmpty)); + assertType('string', mb_strtoupper($s)); + assertType('non-empty-string', mb_strtoupper($nonEmpty)); + assertType('string', mb_strtolower($s)); + assertType('non-empty-string', mb_strtolower($nonEmpty)); + assertType('string', lcfirst($s)); + assertType('non-empty-string', lcfirst($nonEmpty)); + assertType('string', ucfirst($s)); + assertType('non-empty-string', ucfirst($nonEmpty)); + assertType('string', ucwords($s)); + assertType('non-empty-string', ucwords($nonEmpty)); + assertType('string', htmlspecialchars($s)); + assertType('non-empty-string', htmlspecialchars($nonEmpty)); + + assertType('string', sprintf($s)); + assertType('non-empty-string', sprintf($nonEmpty)); + assertType('string', vsprintf($s, [])); + assertType('non-empty-string', vsprintf($nonEmpty, [])); + + assertType('0', strlen('')); + assertType('int<0, max>', strlen($s)); + assertType('int<1, max>', strlen($nonEmpty)); + } + +} From 3dc41f35f96321d41d04e6d3a91d27da59c9d3cf Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 14 Jul 2021 16:42:34 +0200 Subject: [PATCH 0042/1284] Improve Exception stubs --- stubs/Exception.stub | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stubs/Exception.stub b/stubs/Exception.stub index 27270cb9b7..ec3b18f927 100644 --- a/stubs/Exception.stub +++ b/stubs/Exception.stub @@ -27,7 +27,7 @@ interface Throwable public function getLine(); /** - * @return mixed[] + * @return list> * @throws void */ public function getTrace(); @@ -79,7 +79,7 @@ class Exception implements Throwable final public function getLine() {} /** - * @return mixed[] + * @return list> * @throws void */ final public function getTrace() {} @@ -126,7 +126,7 @@ class Error implements Throwable final public function getLine() {} /** - * @return mixed[] + * @return list> * @throws void */ final public function getTrace() {} From 4220e431da3e51c7a537b0ae26a825b0f3804fa1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 14 Jul 2021 22:08:27 +0200 Subject: [PATCH 0043/1284] Invalidating expressions without regexes --- src/Analyser/MutatingScope.php | 36 +++++------ src/Analyser/NodeScopeResolver.php | 4 +- .../Analyser/NodeScopeResolverTest.php | 1 + tests/PHPStan/Analyser/data/bug-5129.php | 63 +++++++++++++++++++ 4 files changed, 80 insertions(+), 24 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/bug-5129.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 5708e5cb9d..745b15585b 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -25,6 +25,7 @@ use PhpParser\Node\Scalar\EncapsedStringPart; use PhpParser\Node\Scalar\LNumber; use PhpParser\Node\Scalar\String_; +use PhpParser\NodeFinder; use PHPStan\Node\ExecutionEndNode; use PHPStan\Parser\Parser; use PHPStan\Reflection\ClassMemberReflection; @@ -3479,17 +3480,13 @@ public function unsetExpression(Expr $expr): self ); } - $args = [new Node\Arg($expr->var)]; - $arrays = TypeUtils::getArrays($varType); $scope = $this; if (count($arrays) > 0) { $scope = $scope->specifyExpressionType($expr->var, TypeCombinator::union(...$arrays)); } - return $scope->invalidateExpression($expr->var) - ->invalidateExpression(new FuncCall(new Name\FullyQualified('count'), $args)) - ->invalidateExpression(new FuncCall(new Name('count'), $args)); + return $scope->invalidateExpression($expr->var); } return $this; @@ -3609,28 +3606,25 @@ public function invalidateExpression(Expr $expressionToInvalidate, bool $require $moreSpecificTypeHolders = $this->moreSpecificTypes; $nativeExpressionTypes = $this->nativeExpressionTypes; $invalidated = false; + $nodeFinder = new NodeFinder(); foreach (array_keys($moreSpecificTypeHolders) as $exprString) { $exprString = (string) $exprString; - if (Strings::startsWith($exprString, $exprStringToInvalidate)) { - if ($exprString === $exprStringToInvalidate && $requireMoreCharacters) { - continue; - } - $nextLetter = substr($exprString, strlen($exprStringToInvalidate), 1); - if (Strings::match($nextLetter, '#[a-zA-Z_0-9\x7f-\xff]#') === null) { - unset($moreSpecificTypeHolders[$exprString]); - unset($nativeExpressionTypes[$exprString]); - $invalidated = true; - continue; - } + $expr = $this->parser->parseString('findFirst([$expr->expr], function (Node $node) use ($exprStringToInvalidate): bool { + if (!$node instanceof Expr) { + return false; + } + + return $this->getNodeKey($node) === $exprStringToInvalidate; + }); + if ($found === null) { continue; } - $matches = array_column($matches, 0); - - if (!in_array($exprStringToInvalidate, $matches, true)) { + if ($requireMoreCharacters && $exprString === $exprStringToInvalidate) { continue; } diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 36dfc08262..01135ec990 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -1858,9 +1858,7 @@ function (MutatingScope $scope) use ($expr, $nodeCallback, $context): Expression ) { $arrayArg = $expr->args[0]->value; $constantArrays = TypeUtils::getConstantArrays($scope->getType($arrayArg)); - $scope = $scope->invalidateExpression($arrayArg) - ->invalidateExpression(new FuncCall(new Name\FullyQualified('count'), [$expr->args[0]])) - ->invalidateExpression(new FuncCall(new Name('count'), [$expr->args[0]])); + $scope = $scope->invalidateExpression($arrayArg); if (count($constantArrays) > 0) { $resultArrayTypes = []; diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index a6afe8cc63..98c8d7740b 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -442,6 +442,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/bug-4415.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5259.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5293.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5129.php'); } /** diff --git a/tests/PHPStan/Analyser/data/bug-5129.php b/tests/PHPStan/Analyser/data/bug-5129.php new file mode 100644 index 0000000000..205faaaeab --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-5129.php @@ -0,0 +1,63 @@ +foo = ''; + assertType('0', strlen($this->foo)); + if (strlen($this->foo) > 0) { + return; + } + + assertType('0', strlen($this->foo)); + + $this->foo = 'x'; + assertType('int<1, max>', strlen($this->foo)); + if (strlen($this->foo) > 0) { + return; + } + + assertType('0', strlen($this->foo)); + + $this->foo = $s; + assertType('int<0, max>', strlen($this->foo)); + } + + public function sayHello2(string $s): void + { + $this->foo = ''; + if (!$this->isFoo($this->foo)) { + return; + } + + assertType('true', $this->isFoo($this->foo)); + + $this->foo = 'x'; + assertType('bool', $this->isFoo($this->foo)); + if (!$this->isFoo($this->foo)) { + return; + } + assertType('true', $this->isFoo($this->foo)); + + $this->foo = $s; + assertType('bool', $this->isFoo($this->foo)); + if (!$this->isFoo($this->foo)) { + return; + } + assertType('true', $this->isFoo($this->foo)); + } + + public function isFoo(string $s): bool + { + return strlen($s) % 3; + } + +} From 19cefd18e85186d84851930e21ac09a3e55343a2 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 14 Jul 2021 22:55:03 +0200 Subject: [PATCH 0044/1284] Fixes --- src/Analyser/MutatingScope.php | 10 +++++++++- src/Analyser/TypeSpecifier.php | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 745b15585b..a55a6dc7cd 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -3609,7 +3609,12 @@ public function invalidateExpression(Expr $expressionToInvalidate, bool $require $nodeFinder = new NodeFinder(); foreach (array_keys($moreSpecificTypeHolders) as $exprString) { $exprString = (string) $exprString; - $expr = $this->parser->parseString('parser->parseString('getNodeKey($node) === $exprStringToInvalidate; }); diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 5cbfd59ea6..375ec1e81b 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -919,7 +919,7 @@ public function create( ?Scope $scope = null ): SpecifiedTypes { - if ($expr instanceof New_ || $expr instanceof Instanceof_) { + if ($expr instanceof New_ || $expr instanceof Instanceof_ || $expr instanceof Expr\List_) { return new SpecifiedTypes(); } From 26cbd076bf6b53930154ee352ef4de3569ced7a9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 14 Jul 2021 23:03:11 +0200 Subject: [PATCH 0045/1284] Regression test Closes https://github.com/phpstan/phpstan/issues/4970 --- .../Analyser/NodeScopeResolverTest.php | 1 + tests/PHPStan/Analyser/data/bug-4970.php | 29 +++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 tests/PHPStan/Analyser/data/bug-4970.php diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 98c8d7740b..ecc5f625cd 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -443,6 +443,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5259.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5293.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5129.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4970.php'); } /** diff --git a/tests/PHPStan/Analyser/data/bug-4970.php b/tests/PHPStan/Analyser/data/bug-4970.php new file mode 100644 index 0000000000..b4ac3cf0ea --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-4970.php @@ -0,0 +1,29 @@ +importFile)) { + return 1; + } + assertType('true', \file_exists($this->importFile)); + $this->importFile = '/b'; + assertType('bool', \file_exists($this->importFile)); + + if (\file_exists($this->importFile)) { + echo 'test'; + } + + return \file_exists($this->importFile) ? 0 : 1; + } +} From 9d68817cacdc87e509eb82ca5f54d01c6b2af047 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 14 Jul 2021 23:09:11 +0200 Subject: [PATCH 0046/1284] Memory optimization --- src/Analyser/LazyScopeFactory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Analyser/LazyScopeFactory.php b/src/Analyser/LazyScopeFactory.php index d9ac4ca85b..23abbeeedd 100644 --- a/src/Analyser/LazyScopeFactory.php +++ b/src/Analyser/LazyScopeFactory.php @@ -89,7 +89,7 @@ public function create( $this->container->getByType(Standard::class), $this->container->getByType(TypeSpecifier::class), $this->container->getByType(PropertyReflectionFinder::class), - $this->container->getByType(\PHPStan\Parser\Parser::class), + $this->container->getService('currentPhpVersionSimpleParser'), $this->container->getByType(NodeScopeResolver::class), $context, $declareStrictTypes, From 39bd9379f1341b9fc6c853665f853eb74078aca6 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 15 Jul 2021 09:06:42 +0200 Subject: [PATCH 0047/1284] Optimization --- src/Analyser/MutatingScope.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index a55a6dc7cd..5e2f28465e 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -3603,6 +3603,7 @@ public function assignExpression(Expr $expr, Type $type): self public function invalidateExpression(Expr $expressionToInvalidate, bool $requireMoreCharacters = false): self { $exprStringToInvalidate = $this->getNodeKey($expressionToInvalidate); + $expressionToInvalidateClass = get_class($expressionToInvalidate); $moreSpecificTypeHolders = $this->moreSpecificTypes; $nativeExpressionTypes = $this->nativeExpressionTypes; $invalidated = false; @@ -3618,11 +3619,8 @@ public function invalidateExpression(Expr $expressionToInvalidate, bool $require if (!$expr instanceof Node\Stmt\Expression) { throw new \PHPStan\ShouldNotHappenException(); } - $found = $nodeFinder->findFirst([$expr->expr], function (Node $node) use ($exprStringToInvalidate): bool { - if (!$node instanceof Expr) { - return false; - } - if ($node instanceof EncapsedStringPart) { + $found = $nodeFinder->findFirst([$expr->expr], function (Node $node) use ($expressionToInvalidateClass, $exprStringToInvalidate): bool { + if (!$node instanceof $expressionToInvalidateClass) { return false; } From 4f9488eb2e710f68a15632ee4115a4314f51a6f0 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 15 Jul 2021 13:43:38 +0200 Subject: [PATCH 0048/1284] Cover FileHelper methods with BC promise --- src/File/FileHelper.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/File/FileHelper.php b/src/File/FileHelper.php index b351f1e11f..eac8c5accc 100644 --- a/src/File/FileHelper.php +++ b/src/File/FileHelper.php @@ -19,6 +19,7 @@ public function getWorkingDirectory(): string return $this->workingDirectory; } + /** @api */ public function absolutizePath(string $path): string { if (DIRECTORY_SEPARATOR === '/') { @@ -37,6 +38,7 @@ public function absolutizePath(string $path): string return rtrim($this->getWorkingDirectory(), '/\\') . DIRECTORY_SEPARATOR . ltrim($path, '/\\'); } + /** @api */ public function normalizePath(string $originalPath, string $directorySeparator = DIRECTORY_SEPARATOR): string { $matches = \Nette\Utils\Strings::match($originalPath, '~^([a-z]+)\\:\\/\\/(.+)~'); From 84503ce886e4e760de2211b1996c53c6aa63ba69 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 15 Jul 2021 14:57:12 +0200 Subject: [PATCH 0049/1284] str_pad() function return type extension --- conf/config.neon | 5 ++ .../Php/StrPadFunctionReturnTypeExtension.php | 53 +++++++++++++++++++ .../Analyser/data/non-empty-string.php | 5 ++ 3 files changed, 63 insertions(+) create mode 100644 src/Type/Php/StrPadFunctionReturnTypeExtension.php diff --git a/conf/config.neon b/conf/config.neon index 8814df2cd1..12f6b7950d 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1265,6 +1265,11 @@ services: tags: - phpstan.broker.dynamicFunctionReturnTypeExtension + - + class: PHPStan\Type\Php\StrPadFunctionReturnTypeExtension + tags: + - phpstan.broker.dynamicFunctionReturnTypeExtension + - class: PHPStan\Type\Php\ParseUrlFunctionDynamicReturnTypeExtension tags: diff --git a/src/Type/Php/StrPadFunctionReturnTypeExtension.php b/src/Type/Php/StrPadFunctionReturnTypeExtension.php new file mode 100644 index 0000000000..37323de1ca --- /dev/null +++ b/src/Type/Php/StrPadFunctionReturnTypeExtension.php @@ -0,0 +1,53 @@ +getName() === 'str_pad'; + } + + public function getTypeFromFunctionCall( + FunctionReflection $functionReflection, + FuncCall $functionCall, + Scope $scope + ): \PHPStan\Type\Type + { + $args = $functionCall->args; + if (count($args) < 2) { + return new StringType(); + } + + $inputType = $scope->getType($args[0]->value); + $lengthType = $scope->getType($args[1]->value); + + if ($inputType->isNonEmptyString()->yes()) { + return new IntersectionType([ + new StringType(), + new AccessoryNonEmptyStringType(), + ]); + } + + if (IntegerRangeType::fromInterval(1, null)->isSuperTypeOf($lengthType)->yes()) { + return new IntersectionType([ + new StringType(), + new AccessoryNonEmptyStringType(), + ]); + } + + return new StringType(); + } + +} diff --git a/tests/PHPStan/Analyser/data/non-empty-string.php b/tests/PHPStan/Analyser/data/non-empty-string.php index a1e13ec551..18e06ca43c 100644 --- a/tests/PHPStan/Analyser/data/non-empty-string.php +++ b/tests/PHPStan/Analyser/data/non-empty-string.php @@ -304,6 +304,11 @@ public function doFoo(string $s, string $nonEmpty) assertType('0', strlen('')); assertType('int<0, max>', strlen($s)); assertType('int<1, max>', strlen($nonEmpty)); + + assertType('non-empty-string', str_pad($nonEmpty, 0)); + assertType('non-empty-string', str_pad($nonEmpty, 1)); + assertType('string', str_pad($s, 0)); + assertType('non-empty-string', str_pad($s, 1)); } } From ce807244a864a17f65bee2dc63cf93e81a69d1a1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 15 Jul 2021 15:01:04 +0200 Subject: [PATCH 0050/1284] More specific str_repeat parameter tpye --- resources/functionMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index ee3a252edd..f7d718e024 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -11693,7 +11693,7 @@ 'str_getcsv' => ['array', 'input'=>'string', 'delimiter='=>'string', 'enclosure='=>'string', 'escape='=>'string'], 'str_ireplace' => ['string|string[]', 'search'=>'string|array', 'replace'=>'string|array', 'subject'=>'string|array', '&w_replace_count='=>'int'], 'str_pad' => ['string', 'input'=>'string', 'pad_length'=>'int', 'pad_string='=>'string', 'pad_type='=>'int'], -'str_repeat' => ['string', 'input'=>'string', 'multiplier'=>'int'], +'str_repeat' => ['string', 'input'=>'string', 'multiplier'=>'0|positive-int'], 'str_replace' => ['string|array', 'search'=>'string|array', 'replace'=>'string|array', 'subject'=>'string|array', '&w_replace_count='=>'int'], 'str_rot13' => ['string', 'str'=>'string'], 'str_shuffle' => ['string', 'str'=>'string'], From 57d765f6729eee91a67e5f8b14d8601d10cde23e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 15 Jul 2021 15:08:58 +0200 Subject: [PATCH 0051/1284] str_repeat() function return type extension --- conf/config.neon | 5 ++ .../StrRepeatFunctionReturnTypeExtension.php | 54 +++++++++++++++++++ .../Analyser/data/non-empty-string.php | 9 +++- 3 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 src/Type/Php/StrRepeatFunctionReturnTypeExtension.php diff --git a/conf/config.neon b/conf/config.neon index 12f6b7950d..40a8c57fab 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1270,6 +1270,11 @@ services: tags: - phpstan.broker.dynamicFunctionReturnTypeExtension + - + class: PHPStan\Type\Php\StrRepeatFunctionReturnTypeExtension + tags: + - phpstan.broker.dynamicFunctionReturnTypeExtension + - class: PHPStan\Type\Php\ParseUrlFunctionDynamicReturnTypeExtension tags: diff --git a/src/Type/Php/StrRepeatFunctionReturnTypeExtension.php b/src/Type/Php/StrRepeatFunctionReturnTypeExtension.php new file mode 100644 index 0000000000..33bf751c0b --- /dev/null +++ b/src/Type/Php/StrRepeatFunctionReturnTypeExtension.php @@ -0,0 +1,54 @@ +getName() === 'str_repeat'; + } + + public function getTypeFromFunctionCall( + FunctionReflection $functionReflection, + FuncCall $functionCall, + Scope $scope + ): \PHPStan\Type\Type + { + $args = $functionCall->args; + if (count($args) < 2) { + return new StringType(); + } + + $inputType = $scope->getType($args[0]->value); + $multiplierType = $scope->getType($args[1]->value); + + if ((new ConstantIntegerType(0))->isSuperTypeOf($multiplierType)->yes()) { + return new ConstantStringType(''); + } + + if ($inputType->isNonEmptyString()->yes()) { + if (IntegerRangeType::fromInterval(1, null)->isSuperTypeOf($multiplierType)->yes()) { + return new IntersectionType([ + new StringType(), + new AccessoryNonEmptyStringType(), + ]); + } + } + + return new StringType(); + } + +} diff --git a/tests/PHPStan/Analyser/data/non-empty-string.php b/tests/PHPStan/Analyser/data/non-empty-string.php index 18e06ca43c..7325fe1152 100644 --- a/tests/PHPStan/Analyser/data/non-empty-string.php +++ b/tests/PHPStan/Analyser/data/non-empty-string.php @@ -277,7 +277,7 @@ class MoreNonEmptyStringFunctions /** * @param non-empty-string $nonEmpty */ - public function doFoo(string $s, string $nonEmpty) + public function doFoo(string $s, string $nonEmpty, int $i) { assertType('string', strtoupper($s)); assertType('non-empty-string', strtoupper($nonEmpty)); @@ -309,6 +309,13 @@ public function doFoo(string $s, string $nonEmpty) assertType('non-empty-string', str_pad($nonEmpty, 1)); assertType('string', str_pad($s, 0)); assertType('non-empty-string', str_pad($s, 1)); + + assertType('non-empty-string', str_repeat($nonEmpty, 1)); + assertType('\'\'', str_repeat($nonEmpty, 0)); + assertType('string', str_repeat($nonEmpty, $i)); + assertType('\'\'', str_repeat($s, 0)); + assertType('string', str_repeat($s, 1)); + assertType('string', str_repeat($s, $i)); } } From 3967ddbdf0970a5445bd518e05f0e609d94bf461 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 15 Jul 2021 15:28:01 +0200 Subject: [PATCH 0052/1284] Try cancelling timer sooner --- src/Parallel/Process.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Parallel/Process.php b/src/Parallel/Process.php index 8510b92bc8..e44cc6d1f2 100644 --- a/src/Parallel/Process.php +++ b/src/Parallel/Process.php @@ -130,6 +130,7 @@ public function quit(): void public function bindConnection(ReadableStreamInterface $out, WritableStreamInterface $in): void { $out->on('data', function (array $json): void { + $this->cancelTimer(); if ($json['action'] !== 'result') { return; } From 2bfd1e9bbf79d87c6af054f75446ee716346f85d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 15 Jul 2021 15:31:43 +0200 Subject: [PATCH 0053/1284] Revert More specific str_repeat parameter type This reverts commit ce807244a864a17f65bee2dc63cf93e81a69d1a1. --- resources/functionMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index f7d718e024..ee3a252edd 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -11693,7 +11693,7 @@ 'str_getcsv' => ['array', 'input'=>'string', 'delimiter='=>'string', 'enclosure='=>'string', 'escape='=>'string'], 'str_ireplace' => ['string|string[]', 'search'=>'string|array', 'replace'=>'string|array', 'subject'=>'string|array', '&w_replace_count='=>'int'], 'str_pad' => ['string', 'input'=>'string', 'pad_length'=>'int', 'pad_string='=>'string', 'pad_type='=>'int'], -'str_repeat' => ['string', 'input'=>'string', 'multiplier'=>'0|positive-int'], +'str_repeat' => ['string', 'input'=>'string', 'multiplier'=>'int'], 'str_replace' => ['string|array', 'search'=>'string|array', 'replace'=>'string|array', 'subject'=>'string|array', '&w_replace_count='=>'int'], 'str_rot13' => ['string', 'str'=>'string'], 'str_shuffle' => ['string', 'str'=>'string'], From 6530013b4ca0c1f44beedd8f8467fd79e7d7f189 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 16 Jul 2021 17:06:26 +0200 Subject: [PATCH 0054/1284] Cover non-empty-string in url-functions (#575) * Cover non-empty-string in url-functions * Added url-functions in NonEmptyStringFunctionsReturnTypeExtension --- .../Php/NonEmptyStringFunctionsReturnTypeExtension.php | 4 ++++ tests/PHPStan/Analyser/data/non-empty-string.php | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/src/Type/Php/NonEmptyStringFunctionsReturnTypeExtension.php b/src/Type/Php/NonEmptyStringFunctionsReturnTypeExtension.php index 5a09c19d88..9e2ea7783d 100644 --- a/src/Type/Php/NonEmptyStringFunctionsReturnTypeExtension.php +++ b/src/Type/Php/NonEmptyStringFunctionsReturnTypeExtension.php @@ -25,6 +25,10 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo 'ucfirst', 'ucwords', 'htmlspecialchars', + 'urlencode', + 'urldecode', + 'rawurlencode', + 'rawurldecode', 'vsprintf', ], true); } diff --git a/tests/PHPStan/Analyser/data/non-empty-string.php b/tests/PHPStan/Analyser/data/non-empty-string.php index 7325fe1152..13ea39c118 100644 --- a/tests/PHPStan/Analyser/data/non-empty-string.php +++ b/tests/PHPStan/Analyser/data/non-empty-string.php @@ -295,6 +295,15 @@ public function doFoo(string $s, string $nonEmpty, int $i) assertType('non-empty-string', ucwords($nonEmpty)); assertType('string', htmlspecialchars($s)); assertType('non-empty-string', htmlspecialchars($nonEmpty)); + + assertType('string', urlencode($s)); + assertType('non-empty-string', urlencode($nonEmpty)); + assertType('string', urldecode($s)); + assertType('non-empty-string', urldecode($nonEmpty)); + assertType('string', rawurlencode($s)); + assertType('non-empty-string', rawurlencode($nonEmpty)); + assertType('string', rawurldecode($s)); + assertType('non-empty-string', rawurldecode($nonEmpty)); assertType('string', sprintf($s)); assertType('non-empty-string', sprintf($nonEmpty)); From 882e2a22c519fc39bde8320dd34fc57e512606fa Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 17 Jul 2021 21:44:31 +0200 Subject: [PATCH 0055/1284] Cover non-empty-string in htmlentities() (#574) * Cover non-empty-string in htmlentities * Added htmlentities in NonEmptyStringFunctionsReturnTypeExtension --- src/Type/Php/NonEmptyStringFunctionsReturnTypeExtension.php | 1 + tests/PHPStan/Analyser/data/non-empty-string.php | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/Type/Php/NonEmptyStringFunctionsReturnTypeExtension.php b/src/Type/Php/NonEmptyStringFunctionsReturnTypeExtension.php index 9e2ea7783d..17d77f39e0 100644 --- a/src/Type/Php/NonEmptyStringFunctionsReturnTypeExtension.php +++ b/src/Type/Php/NonEmptyStringFunctionsReturnTypeExtension.php @@ -25,6 +25,7 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo 'ucfirst', 'ucwords', 'htmlspecialchars', + 'htmlentities', 'urlencode', 'urldecode', 'rawurlencode', diff --git a/tests/PHPStan/Analyser/data/non-empty-string.php b/tests/PHPStan/Analyser/data/non-empty-string.php index 13ea39c118..a235ea6ef8 100644 --- a/tests/PHPStan/Analyser/data/non-empty-string.php +++ b/tests/PHPStan/Analyser/data/non-empty-string.php @@ -295,6 +295,8 @@ public function doFoo(string $s, string $nonEmpty, int $i) assertType('non-empty-string', ucwords($nonEmpty)); assertType('string', htmlspecialchars($s)); assertType('non-empty-string', htmlspecialchars($nonEmpty)); + assertType('string', htmlentities($s)); + assertType('non-empty-string', htmlentities($nonEmpty)); assertType('string', urlencode($s)); assertType('non-empty-string', urlencode($nonEmpty)); From 3166ba31a8768979a40e55f1bbfea9ba4449a3d8 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 18 Jul 2021 08:32:45 +0200 Subject: [PATCH 0056/1284] Filter by falsey condition after the while loop --- src/Analyser/NodeScopeResolver.php | 2 +- .../Analyser/LegacyNodeScopeResolverTest.php | 2 +- .../Analyser/NodeScopeResolverTest.php | 1 + tests/PHPStan/Analyser/data/bug-5322.php | 35 +++++++++++++++++++ 4 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/bug-5322.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 01135ec990..936475068d 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -893,7 +893,7 @@ private function processStmtNode( $bodyScopeMaybeRan = $bodyScope; $bodyScope = $this->processExprNode($stmt->cond, $bodyScope, $nodeCallback, ExpressionContext::createDeep())->getTruthyScope(); $finalScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, $nodeCallback)->filterOutLoopExitPoints(); - $finalScope = $finalScopeResult->getScope(); + $finalScope = $finalScopeResult->getScope()->filterByFalseyValue($stmt->cond); foreach ($finalScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { $finalScope = $finalScope->mergeWith($continueExitPoint->getScope()); } diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index d35ded7879..2b60b66c78 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -435,7 +435,7 @@ public function dataAssignInIf(): array $testScope, 'frame', TrinaryLogic::createYes(), - 'mixed', + 'mixed~null', ], [ $testScope, diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index ecc5f625cd..9780198030 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -444,6 +444,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5293.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5129.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4970.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5322.php'); } /** diff --git a/tests/PHPStan/Analyser/data/bug-5322.php b/tests/PHPStan/Analyser/data/bug-5322.php new file mode 100644 index 0000000000..71965701da --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-5322.php @@ -0,0 +1,35 @@ +produceIntOrNull(); + } + + assertType('int', $int); + } + + function doBar() + { + $int = $this->produceIntOrNull(); + while (!is_int($int)) { + $int = $this->produceIntOrNull(); + } + + assertType('int', $int); + } + +} From 0492220f79322987dbb29c59a81216b38934478f Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 19 Jul 2021 14:47:52 +0200 Subject: [PATCH 0057/1284] Cover non-empty-array in `array_combine` --- src/Type/Php/ArrayCombineFunctionReturnTypeExtension.php | 6 ++++++ tests/PHPStan/Analyser/data/non-empty-array.php | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/src/Type/Php/ArrayCombineFunctionReturnTypeExtension.php b/src/Type/Php/ArrayCombineFunctionReturnTypeExtension.php index d1cdd2df95..34d2fda545 100644 --- a/src/Type/Php/ArrayCombineFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayCombineFunctionReturnTypeExtension.php @@ -8,6 +8,7 @@ use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\ParametersAcceptorSelector; +use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\ArrayType; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantBooleanType; @@ -15,6 +16,7 @@ use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\MixedType; use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; use PHPStan\Type\UnionType; class ArrayCombineFunctionReturnTypeExtension implements \PHPStan\Type\DynamicFunctionReturnTypeExtension @@ -69,6 +71,10 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $valuesParamType instanceof ArrayType ? $valuesParamType->getItemType() : new MixedType() ); + if ($keysParamType->isIterableAtLeastOnce()->yes() && $valuesParamType->isIterableAtLeastOnce()->yes()) { + $arrayType = TypeCombinator::intersect($arrayType, new NonEmptyArrayType()); + } + if ($this->phpVersion->throwsTypeErrorForInternalFunctions()) { return $arrayType; } diff --git a/tests/PHPStan/Analyser/data/non-empty-array.php b/tests/PHPStan/Analyser/data/non-empty-array.php index fa054e9259..5f67ed70e6 100644 --- a/tests/PHPStan/Analyser/data/non-empty-array.php +++ b/tests/PHPStan/Analyser/data/non-empty-array.php @@ -34,4 +34,13 @@ public function doFoo( assertType('mixed', $invalidList2); } + /** + * @param non-empty-array $array + * @param non-empty-list $list + */ + public function arrayFunctions($array, $list): void + { + assertType('array&nonEmpty', array_combine($array, $array)); + assertType('array&nonEmpty', array_combine($list, $list)); + } } From 9ee914ba1a7aff57a11e3215079cb9e7c0ea2d4c Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 19 Jul 2021 16:11:50 +0200 Subject: [PATCH 0058/1284] Cover non-empty-string in `substr` --- conf/config.neon | 5 ++ .../Php/SubstrDynamicReturnTypeExtension.php | 59 +++++++++++++++++++ .../Analyser/data/non-empty-string.php | 38 ++++++++++-- 3 files changed, 96 insertions(+), 6 deletions(-) create mode 100644 src/Type/Php/SubstrDynamicReturnTypeExtension.php diff --git a/conf/config.neon b/conf/config.neon index 40a8c57fab..c87e525cce 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1275,6 +1275,11 @@ services: tags: - phpstan.broker.dynamicFunctionReturnTypeExtension + - + class: PHPStan\Type\Php\SubstrDynamicReturnTypeExtension + tags: + - phpstan.broker.dynamicFunctionReturnTypeExtension + - class: PHPStan\Type\Php\ParseUrlFunctionDynamicReturnTypeExtension tags: diff --git a/src/Type/Php/SubstrDynamicReturnTypeExtension.php b/src/Type/Php/SubstrDynamicReturnTypeExtension.php new file mode 100644 index 0000000000..f86d919eab --- /dev/null +++ b/src/Type/Php/SubstrDynamicReturnTypeExtension.php @@ -0,0 +1,59 @@ +getName() === 'substr'; + } + + public function getTypeFromFunctionCall( + FunctionReflection $functionReflection, + FuncCall $functionCall, + Scope $scope + ): \PHPStan\Type\Type + { + $args = $functionCall->args; + if (count($args) === 0) { + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } + + if (count($args) >= 2) { + $string = $scope->getType($args[0]->value); + $offset = $scope->getType($args[1]->value); + + $negativeOffset = IntegerRangeType::fromInterval(null, -1)->isSuperTypeOf($offset)->yes(); + $zeroOffset = (new ConstantIntegerType(0))->isSuperTypeOf($offset)->yes(); + $positiveLength = false; + + if (count($args) === 3) { + $length = $scope->getType($args[2]->value); + $positiveLength = IntegerRangeType::fromInterval(1, null)->isSuperTypeOf($length)->yes(); + } + + if ($string->isNonEmptyString()->yes() && ($negativeOffset || $zeroOffset && $positiveLength)) { + return new IntersectionType([ + new StringType(), + new AccessoryNonEmptyStringType(), + ]); + } + } + + return new StringType(); + } + +} diff --git a/tests/PHPStan/Analyser/data/non-empty-string.php b/tests/PHPStan/Analyser/data/non-empty-string.php index a235ea6ef8..a233259c5a 100644 --- a/tests/PHPStan/Analyser/data/non-empty-string.php +++ b/tests/PHPStan/Analyser/data/non-empty-string.php @@ -134,6 +134,29 @@ public function doEmpty2(string $s): void } } + /** + * @param non-empty-string $nonEmpty + * @param positive-int $positiveInt + * @param 1|2|3 $postiveRange + * @param -1|-2|-3 $negativeRange + */ + public function doSubstr(string $s, $nonEmpty, $positiveInt, $postiveRange, $negativeRange): void + { + assertType('string', substr($s, 5)); + + assertType('string', substr($s, -5)); + assertType('non-empty-string', substr($nonEmpty, -5)); + assertType('non-empty-string', substr($nonEmpty, $negativeRange)); + + assertType('string', substr($s, 0, 5)); + assertType('non-empty-string', substr($nonEmpty, 0, 5)); + assertType('non-empty-string', substr($nonEmpty, 0, $postiveRange)); + + assertType('string', substr($nonEmpty, 0, -5)); + + assertType('string', substr($s, 0, $positiveInt)); + assertType('non-empty-string', substr($nonEmpty, 0, $positiveInt)); + } } class ImplodingStrings @@ -186,7 +209,7 @@ public function doFoo4(string $s, array $nonEmptyArrayWithNonEmptyStrings): void public function sayHello(): void { // coming from issue #5291 - $s = array(1,2); + $s = array(1, 2); assertType('non-empty-string', implode("a", $s)); } @@ -194,7 +217,8 @@ public function sayHello(): void /** * @param non-empty-string $glue */ - public function nonE($glue, array $a) { + public function nonE($glue, array $a) + { // coming from issue #5291 if (empty($a)) { return "xyz"; @@ -206,7 +230,7 @@ public function nonE($glue, array $a) { public function sayHello2(): void { // coming from issue #5291 - $s = array(1,2); + $s = array(1, 2); assertType('non-empty-string', join("a", $s)); } @@ -214,7 +238,8 @@ public function sayHello2(): void /** * @param non-empty-string $glue */ - public function nonE2($glue, array $a) { + public function nonE2($glue, array $a) + { // coming from issue #5291 if (empty($a)) { return "xyz"; @@ -228,7 +253,8 @@ public function nonE2($glue, array $a) { class LiteralString { - function x(string $tableName, string $original): void { + function x(string $tableName, string $original): void + { assertType('non-empty-string', "from `$tableName`"); } @@ -297,7 +323,7 @@ public function doFoo(string $s, string $nonEmpty, int $i) assertType('non-empty-string', htmlspecialchars($nonEmpty)); assertType('string', htmlentities($s)); assertType('non-empty-string', htmlentities($nonEmpty)); - + assertType('string', urlencode($s)); assertType('non-empty-string', urlencode($nonEmpty)); assertType('string', urldecode($s)); From 0be769d2d7c56831c3a1181207f83697631170c6 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 19 Jul 2021 18:04:30 +0200 Subject: [PATCH 0059/1284] Cover non-empty-array in `array_merge` --- ...ayMergeFunctionDynamicReturnTypeExtension.php | 16 +++++++++++++++- .../Analyser/LegacyNodeScopeResolverTest.php | 12 ++++++------ tests/PHPStan/Analyser/data/non-empty-array.php | 5 +++++ 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php index fa7703cd9c..ef48894937 100644 --- a/src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php @@ -6,6 +6,7 @@ use PHPStan\Analyser\Scope; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\ParametersAcceptorSelector; +use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\ArrayType; use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\Type; @@ -29,6 +30,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $keyTypes = []; $valueTypes = []; + $nonEmpty = false; foreach ($functionCall->args as $arg) { $argType = $scope->getType($arg->value); if ($arg->unpack) { @@ -42,12 +44,24 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $keyTypes[] = TypeUtils::generalizeType($argType->getIterableKeyType(), GeneralizePrecision::moreSpecific()); $valueTypes[] = $argType->getIterableValueType(); + + if (!$argType->isIterableAtLeastOnce()->yes()) { + continue; + } + + $nonEmpty = true; } - return new ArrayType( + $arrayType = new ArrayType( TypeCombinator::union(...$keyTypes), TypeCombinator::union(...$valueTypes) ); + + if ($nonEmpty) { + $arrayType = TypeCombinator::intersect($arrayType, new NonEmptyArrayType()); + } + + return $arrayType; } } diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 2b60b66c78..0eeefe63b9 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -5129,7 +5129,7 @@ public function dataArrayFunctions(): array 'array_values($generalStringKeys)', ], [ - 'array', + 'array&nonEmpty', 'array_merge($stringOrIntegerKeys)', ], [ @@ -5137,23 +5137,23 @@ public function dataArrayFunctions(): array 'array_merge($generalStringKeys, $generalDateTimeValues)', ], [ - 'array', + 'array&nonEmpty', 'array_merge($generalStringKeys, $stringOrIntegerKeys)', ], [ - 'array', + 'array&nonEmpty', 'array_merge($stringOrIntegerKeys, $generalStringKeys)', ], [ - 'array', + 'array&nonEmpty', 'array_merge($stringKeys, $stringOrIntegerKeys)', ], [ - 'array', + 'array&nonEmpty', 'array_merge($stringOrIntegerKeys, $stringKeys)', ], [ - 'array', + 'array&nonEmpty', 'array_merge(array("color" => "red", 2, 4), array("a", "b", "color" => "green", "shape" => "trapezoid", 4))', ], [ diff --git a/tests/PHPStan/Analyser/data/non-empty-array.php b/tests/PHPStan/Analyser/data/non-empty-array.php index 5f67ed70e6..421e01b3f2 100644 --- a/tests/PHPStan/Analyser/data/non-empty-array.php +++ b/tests/PHPStan/Analyser/data/non-empty-array.php @@ -42,5 +42,10 @@ public function arrayFunctions($array, $list): void { assertType('array&nonEmpty', array_combine($array, $array)); assertType('array&nonEmpty', array_combine($list, $list)); + + assertType('array&nonEmpty', array_merge($array)); + assertType('array&nonEmpty', array_merge([], $array)); + assertType('array&nonEmpty', array_merge($array, [])); + assertType('array&nonEmpty', array_merge($array, $array)); } } From fc7d7b41b03bf7de06fc60800d3a5459f9ccc410 Mon Sep 17 00:00:00 2001 From: "John Paul E. Balandan, CPA" <51850998+paulbalandan@users.noreply.github.com> Date: Tue, 20 Jul 2021 02:10:59 +0800 Subject: [PATCH 0060/1284] Fix return type of `Memcached::deleteMulti` --- resources/functionMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index ee3a252edd..5db68060ee 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -6436,7 +6436,7 @@ 'Memcached::decrementByKey' => ['int|false', 'server_key'=>'string', 'key'=>'string', 'offset='=>'int', 'initial_value='=>'int', 'expiry='=>'int'], 'Memcached::delete' => ['bool', 'key'=>'string', 'time='=>'int'], 'Memcached::deleteByKey' => ['bool', 'server_key'=>'string', 'key'=>'string', 'time='=>'int'], -'Memcached::deleteMulti' => ['bool', 'keys'=>'array', 'time='=>'int'], +'Memcached::deleteMulti' => ['array', 'keys'=>'array', 'time='=>'int'], 'Memcached::deleteMultiByKey' => ['bool', 'server_key'=>'string', 'keys'=>'array', 'time='=>'int'], 'Memcached::fetch' => ['array'], 'Memcached::fetchAll' => ['array'], From c5f9d2400ee430a4850a87186a751d1e1d9fc9be Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 20 Jul 2021 13:28:04 +0200 Subject: [PATCH 0061/1284] Fix overriding Memcache::get() and MemcachePool::get() --- resources/functionMap.php | 4 ++-- src/Reflection/SignatureMap/SignatureMapParser.php | 2 +- .../Rules/Methods/MethodSignatureRuleTest.php | 11 +++++++++++ .../Rules/Methods/data/memcache-pool-get.php | 13 +++++++++++++ 4 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 tests/PHPStan/Rules/Methods/data/memcache-pool-get.php diff --git a/resources/functionMap.php b/resources/functionMap.php index 5db68060ee..59374884c9 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -6412,7 +6412,7 @@ 'Memcache::decrement' => ['int', 'key'=>'string', 'value='=>'int'], 'Memcache::delete' => ['bool', 'key'=>'string', 'timeout='=>'int'], 'Memcache::flush' => ['bool'], -'Memcache::get' => ['string|array|false', 'key'=>'string', 'flags='=>'array', 'keys='=>'array'], +'Memcache::get' => ['string|array|false', 'key'=>'string', '&flags='=>'array', '&keys='=>'array'], 'Memcache::getExtendedStats' => ['array', 'type='=>'string', 'slabid='=>'int', 'limit='=>'int'], 'Memcache::getServerStatus' => ['int', 'host'=>'string', 'port='=>'int'], 'Memcache::getStats' => ['array', 'type='=>'string', 'slabid='=>'int', 'limit='=>'int'], @@ -6479,7 +6479,7 @@ 'MemcachePool::decrement' => ['int', 'key'=>'string', 'value='=>'int'], 'MemcachePool::delete' => ['bool', 'key'=>'string', 'timeout='=>'int'], 'MemcachePool::flush' => ['bool'], -'MemcachePool::get' => ['string|array|false', 'key'=>'string', 'flags='=>'array', 'keys='=>'array'], +'MemcachePool::get' => ['string|array|false', 'key'=>'string', '&flags='=>'array', '&keys='=>'array'], 'MemcachePool::getExtendedStats' => ['array', 'type='=>'string', 'slabid='=>'int', 'limit='=>'int'], 'MemcachePool::getServerStatus' => ['int', 'host'=>'string', 'port='=>'int'], 'MemcachePool::getStats' => ['array', 'type='=>'string', 'slabid='=>'int', 'limit='=>'int'], diff --git a/src/Reflection/SignatureMap/SignatureMapParser.php b/src/Reflection/SignatureMap/SignatureMapParser.php index 2a4e203d85..166025c41c 100644 --- a/src/Reflection/SignatureMap/SignatureMapParser.php +++ b/src/Reflection/SignatureMap/SignatureMapParser.php @@ -97,7 +97,7 @@ private function getParameterInfoFromName(string $parameterNameString): array } if (strpos($reference, '&rw') === 0) { $passedByReference = PassedByReference::createReadsArgument(); - } elseif (strpos($reference, '&w') === 0) { + } elseif (strpos($reference, '&w') === 0 || strpos($reference, '&') === 0) { $passedByReference = PassedByReference::createCreatesNewVariable(); } else { $passedByReference = PassedByReference::createNo(); diff --git a/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php b/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php index 080a6e4a3e..536dff3540 100644 --- a/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php @@ -351,4 +351,15 @@ public function testBug4854(): void $this->analyse([__DIR__ . '/data/bug-4854.php'], []); } + public function testMemcachePoolGet(): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires static reflection.'); + } + + $this->reportMaybes = true; + $this->reportStatic = true; + $this->analyse([__DIR__ . '/data/memcache-pool-get.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/memcache-pool-get.php b/tests/PHPStan/Rules/Methods/data/memcache-pool-get.php new file mode 100644 index 0000000000..711560b787 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/memcache-pool-get.php @@ -0,0 +1,13 @@ + Date: Tue, 20 Jul 2021 17:21:53 +0200 Subject: [PATCH 0062/1284] Bleeding edge - check classes extending `@final` classes --- conf/bleedingEdge.neon | 1 + conf/config.level0.neon | 8 ++++++- conf/config.neon | 4 +++- .../ExistingClassInClassExtendsRule.php | 12 +++++++++- .../ExistingClassInClassExtendsRuleTest.php | 17 ++++++++++++- .../Classes/data/extends-final-by-tag.php | 24 +++++++++++++++++++ 6 files changed, 62 insertions(+), 4 deletions(-) create mode 100644 tests/PHPStan/Rules/Classes/data/extends-final-by-tag.php diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 1742270d01..2c4bc61dfc 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -23,5 +23,6 @@ parameters: neverInGenericReturnType: true validateOverridingMethodsInStubs: true crossCheckInterfaces: true + finalByPhpDocTag: true stubFiles: - ../stubs/arrayFunctions.stub diff --git a/conf/config.level0.neon b/conf/config.level0.neon index 5df0e46ec7..e13224c8d9 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -42,7 +42,6 @@ rules: - PHPStan\Rules\Classes\DuplicateDeclarationRule - PHPStan\Rules\Classes\ExistingClassesInClassImplementsRule - PHPStan\Rules\Classes\ExistingClassesInInterfaceExtendsRule - - PHPStan\Rules\Classes\ExistingClassInClassExtendsRule - PHPStan\Rules\Classes\ExistingClassInTraitUseRule - PHPStan\Rules\Classes\InstantiationRule - PHPStan\Rules\Classes\InvalidPromotedPropertiesRule @@ -98,6 +97,13 @@ services: - class: PHPStan\Rules\Api\PhpStanNamespaceIn3rdPartyPackageRule + - + class: PHPStan\Rules\Classes\ExistingClassInClassExtendsRule + arguments: + checkFinalByPhpDocTag: %featureToggles.finalByPhpDocTag% + tags: + - phpstan.rules.rule + - class: PHPStan\Rules\Classes\ExistingClassInInstanceOfRule tags: diff --git a/conf/config.neon b/conf/config.neon index c87e525cce..2ffb0dd6be 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -46,6 +46,7 @@ parameters: neverInGenericReturnType: false validateOverridingMethodsInStubs: false crossCheckInterfaces: false + finalByPhpDocTag: false fileExtensions: - php checkAlwaysTrueCheckTypeFunctionCall: false @@ -219,7 +220,8 @@ parametersSchema: deepInspectTypes: bool(), neverInGenericReturnType: bool(), validateOverridingMethodsInStubs: bool(), - crossCheckInterfaces: bool() + crossCheckInterfaces: bool(), + finalByPhpDocTag: bool() ]) fileExtensions: listOf(string()) checkAlwaysTrueCheckTypeFunctionCall: bool() diff --git a/src/Rules/Classes/ExistingClassInClassExtendsRule.php b/src/Rules/Classes/ExistingClassInClassExtendsRule.php index 53e049e9d1..c500cc1ee0 100644 --- a/src/Rules/Classes/ExistingClassInClassExtendsRule.php +++ b/src/Rules/Classes/ExistingClassInClassExtendsRule.php @@ -19,13 +19,17 @@ class ExistingClassInClassExtendsRule implements \PHPStan\Rules\Rule private ReflectionProvider $reflectionProvider; + private bool $checkFinalByPhpDocTag; + public function __construct( ClassCaseSensitivityCheck $classCaseSensitivityCheck, - ReflectionProvider $reflectionProvider + ReflectionProvider $reflectionProvider, + bool $checkFinalByPhpDocTag = false ) { $this->classCaseSensitivityCheck = $classCaseSensitivityCheck; $this->reflectionProvider = $reflectionProvider; + $this->checkFinalByPhpDocTag = $checkFinalByPhpDocTag; } public function getNodeType(): string @@ -72,6 +76,12 @@ public function processNode(Node $node, Scope $scope): array $currentClassName !== null ? sprintf('Class %s', $currentClassName) : 'Anonymous class', $extendedClassName ))->nonIgnorable()->build(); + } elseif ($this->checkFinalByPhpDocTag && $reflection->isFinal()) { + $messages[] = RuleErrorBuilder::message(sprintf( + '%s extends @final class %s.', + $currentClassName !== null ? sprintf('Class %s', $currentClassName) : 'Anonymous class', + $extendedClassName + ))->build(); } } diff --git a/tests/PHPStan/Rules/Classes/ExistingClassInClassExtendsRuleTest.php b/tests/PHPStan/Rules/Classes/ExistingClassInClassExtendsRuleTest.php index 836894513f..43c2b2242d 100644 --- a/tests/PHPStan/Rules/Classes/ExistingClassInClassExtendsRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ExistingClassInClassExtendsRuleTest.php @@ -16,7 +16,8 @@ protected function getRule(): Rule $broker = $this->createReflectionProvider(); return new ExistingClassInClassExtendsRule( new ClassCaseSensitivityCheck($broker), - $broker + $broker, + true ); } @@ -27,6 +28,10 @@ public function testRule(): void 'Class ExtendsImplements\Foo referenced with incorrect case: ExtendsImplements\FOO.', 15, ], + [ + 'Class ExtendsImplements\ExtendsFinalWithAnnotation extends @final class ExtendsImplements\FinalWithAnnotation.', + 43, + ], ]); } @@ -61,4 +66,14 @@ public function testRuleExtendsError(): void ]); } + public function testFinalByTag(): void + { + $this->analyse([__DIR__ . '/data/extends-final-by-tag.php'], [ + [ + 'Class ExtendsFinalByTag\Bar2 extends @final class ExtendsFinalByTag\Bar.', + 21, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Classes/data/extends-final-by-tag.php b/tests/PHPStan/Rules/Classes/data/extends-final-by-tag.php new file mode 100644 index 0000000000..501c6e8f00 --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/extends-final-by-tag.php @@ -0,0 +1,24 @@ + Date: Tue, 20 Jul 2021 17:26:48 +0200 Subject: [PATCH 0063/1284] Cover non-empty-array in `array_flip` --- conf/config.neon | 5 ++ .../ArrayFlipFunctionReturnTypeExtension.php | 52 +++++++++++++++++++ .../Analyser/NodeScopeResolverTest.php | 1 + tests/PHPStan/Analyser/data/array-flip.php | 43 +++++++++++++++ .../PHPStan/Analyser/data/non-empty-array.php | 6 ++- 5 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 src/Type/Php/ArrayFlipFunctionReturnTypeExtension.php create mode 100644 tests/PHPStan/Analyser/data/array-flip.php diff --git a/conf/config.neon b/conf/config.neon index 2ffb0dd6be..a15d7c0c2b 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -930,6 +930,11 @@ services: tags: - phpstan.broker.dynamicFunctionReturnTypeExtension + - + class: PHPStan\Type\Php\ArrayFlipFunctionReturnTypeExtension + tags: + - phpstan.broker.dynamicFunctionReturnTypeExtension + - class: PHPStan\Type\Php\ArrayKeyDynamicReturnTypeExtension tags: diff --git a/src/Type/Php/ArrayFlipFunctionReturnTypeExtension.php b/src/Type/Php/ArrayFlipFunctionReturnTypeExtension.php new file mode 100644 index 0000000000..71a499216e --- /dev/null +++ b/src/Type/Php/ArrayFlipFunctionReturnTypeExtension.php @@ -0,0 +1,52 @@ +getName() === 'array_flip'; + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + if (count($functionCall->args) !== 1) { + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } + + $array = $functionCall->args[0]->value; + $argType = $scope->getType($array); + + if ($argType->isArray()->yes()) { + $keyType = $argType->getIterableKeyType(); + $itemType = $argType->getIterableValueType(); + + $itemType = ArrayType::castToArrayKeyType($itemType); + + $flippedArrayType = new ArrayType( + $itemType, + $keyType + ); + + if ($argType->isIterableAtLeastOnce()->yes()) { + $flippedArrayType = TypeCombinator::intersect($flippedArrayType, new NonEmptyArrayType()); + } + + return $flippedArrayType; + } + + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } + +} diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 9780198030..2c604cb8f6 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -264,6 +264,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/infer-array-key.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/offset-value-after-assign.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2112.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/array-flip.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/array-map-closure.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/array-sum.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4573.php'); diff --git a/tests/PHPStan/Analyser/data/array-flip.php b/tests/PHPStan/Analyser/data/array-flip.php new file mode 100644 index 0000000000..2275170d0d --- /dev/null +++ b/tests/PHPStan/Analyser/data/array-flip.php @@ -0,0 +1,43 @@ +', $flip); +} + +/** + * @param mixed[] $list + */ +function foo3($list) +{ + $flip = array_flip($list); + + assertType('array', $flip); +} + +/** + * @param array $array + */ +function foo4($array) +{ + $flip = array_flip($array); + assertType('array<1|2|3, int>', $flip); +} + + +/** + * @param array<1|2|3, string> $array + */ +function foo5($array) +{ + $flip = array_flip($array); + assertType('array', $flip); +} diff --git a/tests/PHPStan/Analyser/data/non-empty-array.php b/tests/PHPStan/Analyser/data/non-empty-array.php index 421e01b3f2..316620e1af 100644 --- a/tests/PHPStan/Analyser/data/non-empty-array.php +++ b/tests/PHPStan/Analyser/data/non-empty-array.php @@ -37,8 +37,9 @@ public function doFoo( /** * @param non-empty-array $array * @param non-empty-list $list + * @param non-empty-array $stringArray */ - public function arrayFunctions($array, $list): void + public function arrayFunctions($array, $list, $stringArray): void { assertType('array&nonEmpty', array_combine($array, $array)); assertType('array&nonEmpty', array_combine($list, $list)); @@ -47,5 +48,8 @@ public function arrayFunctions($array, $list): void assertType('array&nonEmpty', array_merge([], $array)); assertType('array&nonEmpty', array_merge($array, [])); assertType('array&nonEmpty', array_merge($array, $array)); + + assertType('array&nonEmpty', array_flip($array)); + assertType('array&nonEmpty', array_flip($stringArray)); } } From dca48f34bcec87971ceaa4643eef02ed9b2fd812 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 21 Jul 2021 13:45:36 +0200 Subject: [PATCH 0064/1284] Update nikic/php-parser to 4.12.0 --- composer.json | 4 ++-- composer.lock | 45 ++++++++++++++++++--------------------------- 2 files changed, 20 insertions(+), 29 deletions(-) diff --git a/composer.json b/composer.json index 7515daab9a..bdcce0555f 100644 --- a/composer.json +++ b/composer.json @@ -21,9 +21,9 @@ "nette/neon": "^3.0", "nette/schema": "^1.0", "nette/utils": "^3.1.3", - "nikic/php-parser": "dev-master#c758510a37218d631fd10f67bca5bccbfef864fa as 4.11.0", + "nikic/php-parser": "4.12.0", "ondram/ci-detector": "^3.4.0", - "ondrejmirtes/better-reflection": "4.3.61", + "ondrejmirtes/better-reflection": "4.3.62", "phpstan/php-8-stubs": "^0.1.21", "phpstan/phpdoc-parser": "^0.5.5", "react/child-process": "^0.6.1", diff --git a/composer.lock b/composer.lock index 362e4c1859..37f04bcf98 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1444c37e9e0463764d3e43fec7b20f44", + "content-hash": "2fa566c509af2b7b1355123d666e838b", "packages": [ { "name": "clue/block-react", @@ -1345,7 +1345,7 @@ }, "require-dev": { "friendsofphp/php-cs-fixer": "@stable", - "nikic/php-parser": "@stable", + "nikic/php-parser": "dev-master", "php": "^8.0", "phpdocumentor/reflection-docblock": "@stable", "phpunit/phpunit": "@stable" @@ -1376,7 +1376,7 @@ "support": { "source": "https://github.com/JetBrains/phpstorm-stubs/tree/master" }, - "time": "2021-05-18T14:05:15+00:00" + "time": "2021-07-21T11:04:10+00:00" }, { "name": "nette/bootstrap", @@ -1946,16 +1946,16 @@ }, { "name": "nikic/php-parser", - "version": "dev-master", + "version": "v4.12.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "c758510a37218d631fd10f67bca5bccbfef864fa" + "reference": "6608f01670c3cc5079e18c1dab1104e002579143" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/c758510a37218d631fd10f67bca5bccbfef864fa", - "reference": "c758510a37218d631fd10f67bca5bccbfef864fa", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6608f01670c3cc5079e18c1dab1104e002579143", + "reference": "6608f01670c3cc5079e18c1dab1104e002579143", "shasum": "" }, "require": { @@ -1966,7 +1966,6 @@ "ircmaxell/php-yacc": "^0.0.7", "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" }, - "default-branch": true, "bin": [ "bin/php-parse" ], @@ -1997,9 +1996,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/master" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.12.0" }, - "time": "2021-07-09T14:52:58+00:00" + "time": "2021-07-21T10:44:31+00:00" }, { "name": "ondram/ci-detector", @@ -2075,22 +2074,22 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "4.3.61", + "version": "4.3.62", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "d624985e59112ae000d9456dd261c857d284bdc6" + "reference": "348a8777d11b9dd88f13e0d99a84d1671a8cf8a7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/d624985e59112ae000d9456dd261c857d284bdc6", - "reference": "d624985e59112ae000d9456dd261c857d284bdc6", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/348a8777d11b9dd88f13e0d99a84d1671a8cf8a7", + "reference": "348a8777d11b9dd88f13e0d99a84d1671a8cf8a7", "shasum": "" }, "require": { "ext-json": "*", "jetbrains/phpstorm-stubs": "dev-master#0a73df114cdea7f30c8b5f6fbfbf8e6839a89e88", - "nikic/php-parser": "4.11.0", + "nikic/php-parser": "^4.12.0", "php": ">=7.1.0" }, "require-dev": { @@ -2139,9 +2138,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/4.3.61" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/4.3.62" }, - "time": "2021-07-03T14:42:18+00:00" + "time": "2021-07-21T11:44:15+00:00" }, { "name": "phpstan/php-8-stubs", @@ -6386,18 +6385,10 @@ "time": "2021-03-09T10:59:23+00:00" } ], - "aliases": [ - { - "package": "nikic/php-parser", - "version": "9999999-dev", - "alias": "4.11.0", - "alias_normalized": "4.11.0.0" - } - ], + "aliases": [], "minimum-stability": "dev", "stability-flags": { - "jetbrains/phpstorm-stubs": 20, - "nikic/php-parser": 20 + "jetbrains/phpstorm-stubs": 20 }, "prefer-stable": true, "prefer-lowest": false, From da3790efa9f5ee96c3b7905342904fe3651a29af Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 22 Jul 2021 18:01:03 +0200 Subject: [PATCH 0065/1284] Fix overriding throw point with void --- src/Analyser/NodeScopeResolver.php | 7 +++- .../PHPStan/Rules/Exceptions/Bug5364Test.php | 38 +++++++++++++++++++ tests/PHPStan/Rules/Exceptions/bug-5364.neon | 6 +++ .../Rules/Exceptions/data/bug-5364.php | 20 ++++++++++ 4 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Rules/Exceptions/Bug5364Test.php create mode 100644 tests/PHPStan/Rules/Exceptions/bug-5364.neon create mode 100644 tests/PHPStan/Rules/Exceptions/data/bug-5364.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 936475068d..6b9b4a664b 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -1442,7 +1442,12 @@ private function getOverridingThrowPoints(Node\Stmt $statement, MutatingScope $s $throwsTag = $resolvedPhpDoc->getThrowsTag(); if ($throwsTag !== null) { - return [ThrowPoint::createExplicit($scope, $throwsTag->getType(), $statement, false)]; + $throwsType = $throwsTag->getType(); + if ($throwsType instanceof VoidType) { + return []; + } + + return [ThrowPoint::createExplicit($scope, $throwsType, $statement, false)]; } } diff --git a/tests/PHPStan/Rules/Exceptions/Bug5364Test.php b/tests/PHPStan/Rules/Exceptions/Bug5364Test.php new file mode 100644 index 0000000000..88e1e3dcf6 --- /dev/null +++ b/tests/PHPStan/Rules/Exceptions/Bug5364Test.php @@ -0,0 +1,38 @@ + + */ +class Bug5364Test extends RuleTestCase +{ + + protected function getRule(): \PHPStan\Rules\Rule + { + return new MissingCheckedExceptionInMethodThrowsRule( + new MissingCheckedExceptionInThrowsCheck(new DefaultExceptionTypeResolver( + $this->createReflectionProvider(), + [], + [], + [], + [] + )) + ); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/bug-5364.php'], []); + } + + public static function getAdditionalConfigFiles(): array + { + return [ + __DIR__ . '/bug-5364.neon', + ]; + } + +} diff --git a/tests/PHPStan/Rules/Exceptions/bug-5364.neon b/tests/PHPStan/Rules/Exceptions/bug-5364.neon new file mode 100644 index 0000000000..9fa19a4a89 --- /dev/null +++ b/tests/PHPStan/Rules/Exceptions/bug-5364.neon @@ -0,0 +1,6 @@ +parameters: + implicitThrows: false + exceptions: + check: + missingCheckedExceptionInThrows: true + tooWideThrowType: true diff --git a/tests/PHPStan/Rules/Exceptions/data/bug-5364.php b/tests/PHPStan/Rules/Exceptions/data/bug-5364.php new file mode 100644 index 0000000000..b52c6a1d36 --- /dev/null +++ b/tests/PHPStan/Rules/Exceptions/data/bug-5364.php @@ -0,0 +1,20 @@ +test(); + } + +} From cacc8bba5bc9a755e915bb5433b7e7782c683de6 Mon Sep 17 00:00:00 2001 From: Dylan T Date: Fri, 23 Jul 2021 17:07:14 +0100 Subject: [PATCH 0066/1284] Fixed stub types for SplFixedArray --- stubs/ArrayObject.stub | 8 ++++---- .../PHPStan/Analyser/NodeScopeResolverTest.php | 1 + .../data/splfixedarray-iterator-types.php | 17 +++++++++++++++++ 3 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/splfixedarray-iterator-types.php diff --git a/stubs/ArrayObject.stub b/stubs/ArrayObject.stub index ac5766994f..37714725f4 100644 --- a/stubs/ArrayObject.stub +++ b/stubs/ArrayObject.stub @@ -88,9 +88,9 @@ class ArrayObject implements IteratorAggregate, ArrayAccess /** * @template TValue - * @implements Iterator - * @implements IteratorAggregate - * @implements ArrayAccess + * @implements Iterator + * @implements IteratorAggregate + * @implements ArrayAccess */ class SplFixedArray implements Iterator, IteratorAggregate, ArrayAccess, Countable { @@ -102,7 +102,7 @@ class SplFixedArray implements Iterator, IteratorAggregate, ArrayAccess, Countab public static function fromArray(array $array, bool $save_indexes = true): SplFixedArray { } /** - * @return array + * @return array */ public function toArray(): array { } } diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 2c604cb8f6..d8344c689f 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -446,6 +446,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5129.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4970.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5322.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/splfixedarray-iterator-types.php'); } /** diff --git a/tests/PHPStan/Analyser/data/splfixedarray-iterator-types.php b/tests/PHPStan/Analyser/data/splfixedarray-iterator-types.php new file mode 100644 index 0000000000..4c512bea06 --- /dev/null +++ b/tests/PHPStan/Analyser/data/splfixedarray-iterator-types.php @@ -0,0 +1,17 @@ + + */ + public $array; + + public function dump() : void{ + foreach($this->array as $id => $v){ + \PHPStan\Testing\assertType('int|null', $this->array[$id]); + \PHPStan\Testing\assertType('int|null', $v); + } + } +} From 51e4c1d6d01212bea5a77e9d6c3748157245097c Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 25 Jul 2021 14:39:07 +0100 Subject: [PATCH 0067/1284] Updated socket_select() signature for PHP 8.0 --- resources/functionMap_php80delta.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/resources/functionMap_php80delta.php b/resources/functionMap_php80delta.php index 132d3ae252..2ac96d34f3 100644 --- a/resources/functionMap_php80delta.php +++ b/resources/functionMap_php80delta.php @@ -81,6 +81,7 @@ 'PhpToken::getTokenName' => ['string'], 'proc_get_status' => ['array{command: string, pid: int, running: bool, signaled: bool, stopped: bool, exitcode: int, termsig: int, stopsig: int}', 'process'=>'resource'], 'socket_addrinfo_lookup' => ['AddressInfo[]', 'node'=>'string', 'service='=>'mixed', 'hints='=>'array'], + 'socket_select' => ['int|false', '&rw_read'=>'Socket[]|null', '&rw_write'=>'Socket[]|null', '&rw_except'=>'Socket[]|null', 'seconds'=>'int|null', 'microseconds='=>'int'], 'sodium_crypto_aead_chacha20poly1305_ietf_decrypt' => ['string|false', 'confidential_message'=>'string', 'public_message'=>'string', 'nonce'=>'string', 'key'=>'string'], 'str_contains' => ['bool', 'haystack'=>'string', 'needle'=>'string'], 'str_split' => ['array', 'str'=>'string', 'split_length='=>'int'], @@ -205,6 +206,7 @@ 'png2wbmp' => ['bool', 'pngname'=>'string', 'wbmpname'=>'string', 'dest_height'=>'int', 'dest_width'=>'int', 'threshold'=>'int'], 'proc_get_status' => ['array{command: string, pid: int, running: bool, signaled: bool, stopped: bool, exitcode: int, termsig: int, stopsig: int}|false', 'process'=>'resource'], 'read_exif_data' => ['array', 'filename'=>'string', 'sections_needed='=>'string', 'sub_arrays='=>'bool', 'read_thumbnail='=>'bool'], + 'socket_select' => ['int|false', '&rw_read_fds'=>'resource[]|null', '&rw_write_fds'=>'resource[]|null', '&rw_except_fds'=>'resource[]|null', 'tv_sec'=>'int|null', 'tv_usec='=>'int|null'], 'sodium_crypto_aead_chacha20poly1305_ietf_decrypt' => ['?string|?false', 'confidential_message'=>'string', 'public_message'=>'string', 'nonce'=>'string', 'key'=>'string'], 'SplFileObject::fgetss' => ['string|false', 'allowable_tags='=>'string'], 'strchr' => ['string|false', 'haystack'=>'string', 'needle'=>'string|int', 'before_needle='=>'bool'], From 1e7ceae933f07e5a250b61ed94799e6c2ea8daa2 Mon Sep 17 00:00:00 2001 From: Dylan T Date: Sun, 25 Jul 2021 20:33:02 +0100 Subject: [PATCH 0068/1284] proc_open() accepts list for $command in 7.4+ --- resources/functionMap_php74delta.php | 1 + .../Functions/CallToFunctionParametersRuleTest.php | 14 ++++++++++++++ tests/PHPStan/Rules/Functions/data/proc_open.php | 6 ++++++ 3 files changed, 21 insertions(+) create mode 100644 tests/PHPStan/Rules/Functions/data/proc_open.php diff --git a/resources/functionMap_php74delta.php b/resources/functionMap_php74delta.php index 656d998997..73349639fe 100644 --- a/resources/functionMap_php74delta.php +++ b/resources/functionMap_php74delta.php @@ -55,6 +55,7 @@ 'strip_tags' => ['string', 'str'=>'string', 'allowable_tags='=>'string|array'], 'WeakReference::create' => ['WeakReference', 'referent'=>'object'], 'WeakReference::get' => ['?object'], + 'proc_open' => ['resource|false', 'command'=>'string|list', 'descriptorspec'=>'array', '&w_pipes'=>'resource[]', 'cwd='=>'?string', 'env='=>'?array', 'other_options='=>'array'], ], 'old' => [ 'implode\'2' => ['string', 'pieces'=>'array', 'glue'=>'string'], diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 5557501c16..f63cbdbeb2 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -797,4 +797,18 @@ public function testExplode(): void ]); } + public function testProcOpen(): void + { + if (PHP_VERSION_ID < 70400) { + $this->markTestSkipped('Test requires PHP 7.4.'); + } + + $this->analyse([__DIR__ . '/data/proc_open.php'], [ + [ + 'Parameter #1 $command of function proc_open expects array|string, array given.', + 6, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/proc_open.php b/tests/PHPStan/Rules/Functions/data/proc_open.php new file mode 100644 index 0000000000..045641e382 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/proc_open.php @@ -0,0 +1,6 @@ + 'bogus', 'in' => 'here'], [], $pipes); From 1501229bb601298c9bc0914433cfa98e9dd050e6 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 26 Jul 2021 09:28:03 +0200 Subject: [PATCH 0069/1284] Custom PHAR prefix namespace with Git commit --- compiler/build/scoper.inc.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/compiler/build/scoper.inc.php b/compiler/build/scoper.inc.php index f2e2ae8151..3c1509cebc 100644 --- a/compiler/build/scoper.inc.php +++ b/compiler/build/scoper.inc.php @@ -23,8 +23,13 @@ $stubs[] = $file->getPathName(); } +exec('git rev-parse --short HEAD', $gitCommitOutputLines, $gitExitCode); +if ($gitExitCode !== 0) { + die('Could not get Git commit'); +} + return [ - 'prefix' => null, + 'prefix' => sprintf('_PHPStan_%s', $gitCommitOutputLines[0]), 'finders' => [], 'files-whitelist' => $stubs, 'patchers' => [ From 7c02a439ce4e536f0d9c3a3a2eba26bf07c10652 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 26 Jul 2021 09:42:02 +0200 Subject: [PATCH 0070/1284] Test create_function() reported as nonexistent on PHP 8 --- .../CallToNonExistentFunctionRuleTest.php | 24 +++++++++++++++++++ .../Rules/Functions/data/create_function.php | 5 ++++ 2 files changed, 29 insertions(+) create mode 100644 tests/PHPStan/Rules/Functions/data/create_function.php diff --git a/tests/PHPStan/Rules/Functions/CallToNonExistentFunctionRuleTest.php b/tests/PHPStan/Rules/Functions/CallToNonExistentFunctionRuleTest.php index 6b9e2cfe01..ef1abc2ea3 100644 --- a/tests/PHPStan/Rules/Functions/CallToNonExistentFunctionRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToNonExistentFunctionRuleTest.php @@ -97,4 +97,28 @@ public function testMatchExprAnalysis(): void ]); } + public function testCreateFunctionPhp8(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->analyse([__DIR__ . '/data/create_function.php'], [ + [ + 'Function create_function not found.', + 4, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + ]); + } + + public function testCreateFunctionPhp7(): void + { + if (PHP_VERSION_ID >= 80000) { + $this->markTestSkipped('Test requires PHP 7.x.'); + } + + $this->analyse([__DIR__ . '/data/create_function.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/create_function.php b/tests/PHPStan/Rules/Functions/data/create_function.php new file mode 100644 index 0000000000..8983c89301 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/create_function.php @@ -0,0 +1,5 @@ + Date: Mon, 26 Jul 2021 09:42:24 +0200 Subject: [PATCH 0071/1284] Report nonexistent function even without entry in PHP 8 delta --- resources/functionMap_php80delta.php | 1 - 1 file changed, 1 deletion(-) diff --git a/resources/functionMap_php80delta.php b/resources/functionMap_php80delta.php index 2ac96d34f3..591c424706 100644 --- a/resources/functionMap_php80delta.php +++ b/resources/functionMap_php80delta.php @@ -152,7 +152,6 @@ 'bcpowmod' => ['?string', 'base'=>'string', 'exponent'=>'string', 'modulus'=>'string', 'scale='=>'int'], 'com_load_typelib' => ['bool', 'typelib_name'=>'string', 'case_insensitive='=>'bool'], 'count_chars' => ['array|false|string', 'input'=>'string', 'mode='=>'int'], - 'create_function' => ['string', 'args'=>'string', 'code'=>'string'], 'date_add' => ['DateTime|false', 'object'=>'DateTime', 'interval'=>'DateInterval'], 'date_date_set' => ['DateTime|false', 'object'=>'DateTime', 'year'=>'int', 'month'=>'int', 'day'=>'int'], 'date_diff' => ['DateInterval|false', 'obj1'=>'DateTimeInterface', 'obj2'=>'DateTimeInterface', 'absolute='=>'bool'], From 560652088406d7461c2c4ad4897784e33f8ab312 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 26 Jul 2021 17:02:51 +0200 Subject: [PATCH 0072/1284] Simplify FileCacheStorage --- src/Cache/FileCacheStorage.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Cache/FileCacheStorage.php b/src/Cache/FileCacheStorage.php index 1ef9950590..709cbdf00d 100644 --- a/src/Cache/FileCacheStorage.php +++ b/src/Cache/FileCacheStorage.php @@ -40,8 +40,9 @@ private function makeDir(string $directory): void */ public function load(string $key, string $variableKey) { - return (function (string $key, string $variableKey) { - [,, $filePath] = $this->getFilePaths($key); + [,, $filePath] = $this->getFilePaths($key); + + return (static function () use ($variableKey, $filePath) { if (!is_file($filePath)) { return null; } @@ -55,7 +56,7 @@ public function load(string $key, string $variableKey) } return $cacheItem->getData(); - })($key, $variableKey); + })(); } /** From 111bda69191437a17ae9659a6820d4f23c222240 Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Tue, 27 Jul 2021 16:49:38 +0200 Subject: [PATCH 0073/1284] ResultCacheManager: read also config.rules (#594) * ResultCacheManager: read also config.rules * Support old PHP --- src/Analyser/ResultCache/ResultCacheManager.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Analyser/ResultCache/ResultCacheManager.php b/src/Analyser/ResultCache/ResultCacheManager.php index a00e958339..aab1f449cd 100644 --- a/src/Analyser/ResultCache/ResultCacheManager.php +++ b/src/Analyser/ResultCache/ResultCacheManager.php @@ -606,7 +606,10 @@ private function getProjectExtensionFiles(?array $projectConfig, array $dependen $this->alreadyProcessed = []; $projectExtensionFiles = []; if ($projectConfig !== null) { - $services = $projectConfig['services'] ?? []; + $services = array_merge( + $projectConfig['services'] ?? [], + $projectConfig['rules'] ?? [] + ); foreach ($services as $service) { $classes = $this->getClassesFromConfigDefinition($service); if (is_array($service)) { From d88b568cae5759e56cdc1c21433f002db07aefaf Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 27 Jul 2021 16:59:30 +0200 Subject: [PATCH 0074/1284] Result cache - notice change in class constant PHPDoc --- .../ExportedClassConstantNode.php | 24 ++++++++++++++++--- src/Dependency/ExportedNodeResolver.php | 15 +++++++++++- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/Dependency/ExportedNode/ExportedClassConstantNode.php b/src/Dependency/ExportedNode/ExportedClassConstantNode.php index 4e75697de7..16e5a29848 100644 --- a/src/Dependency/ExportedNode/ExportedClassConstantNode.php +++ b/src/Dependency/ExportedNode/ExportedClassConstantNode.php @@ -16,12 +16,15 @@ class ExportedClassConstantNode implements ExportedNode, JsonSerializable private bool $private; - public function __construct(string $name, string $value, bool $public, bool $private) + private ?ExportedPhpDocNode $phpDoc; + + public function __construct(string $name, string $value, bool $public, bool $private, ?ExportedPhpDocNode $phpDoc) { $this->name = $name; $this->value = $value; $this->public = $public; $this->private = $private; + $this->phpDoc = $phpDoc; } public function equals(ExportedNode $node): bool @@ -30,6 +33,18 @@ public function equals(ExportedNode $node): bool return false; } + if ($this->phpDoc === null) { + if ($node->phpDoc !== null) { + return false; + } + } elseif ($node->phpDoc !== null) { + if (!$this->phpDoc->equals($node->phpDoc)) { + return false; + } + } else { + return false; + } + return $this->name === $node->name && $this->value === $node->value && $this->public === $node->public @@ -46,7 +61,8 @@ public static function __set_state(array $properties): ExportedNode $properties['name'], $properties['value'], $properties['public'], - $properties['private'] + $properties['private'], + $properties['phpDoc'] ); } @@ -60,7 +76,8 @@ public static function decode(array $data): ExportedNode $data['name'], $data['value'], $data['public'], - $data['private'] + $data['private'], + $data['phpDoc'], ); } @@ -76,6 +93,7 @@ public function jsonSerialize() 'value' => $this->value, 'public' => $this->public, 'private' => $this->private, + 'phpDoc' => $this->phpDoc, ], ]; } diff --git a/src/Dependency/ExportedNodeResolver.php b/src/Dependency/ExportedNodeResolver.php index 0b3981c736..baaa367c79 100644 --- a/src/Dependency/ExportedNodeResolver.php +++ b/src/Dependency/ExportedNodeResolver.php @@ -194,11 +194,24 @@ public function resolve(string $fileName, \PhpParser\Node $node): ?ExportedNode return null; } + $classNode = $parentNode->getAttribute('parent'); + if (!$classNode instanceof Class_ || !isset($classNode->namespacedName)) { + return null; + } + + $docComment = $parentNode->getDocComment(); + return new ExportedClassConstantNode( $node->name->toString(), $this->printer->prettyPrintExpr($node->value), $parentNode->isPublic(), - $parentNode->isPrivate() + $parentNode->isPrivate(), + $this->exportPhpDocNode( + $fileName, + $classNode->namespacedName->toString(), + null, + $docComment !== null ? $docComment->getText() : null + ), ); } From c19506b58e334a8b8ccaaefdfe2beabfa3c064a1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 27 Jul 2021 17:05:19 +0200 Subject: [PATCH 0075/1284] Result cache - notice change in readonly property (PHP 8.1) --- .../ExportedNode/ExportedPropertyNode.php | 16 ++++++++++++---- src/Dependency/ExportedNodeResolver.php | 3 ++- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/Dependency/ExportedNode/ExportedPropertyNode.php b/src/Dependency/ExportedNode/ExportedPropertyNode.php index 37aee5a044..b5bf696d47 100644 --- a/src/Dependency/ExportedNode/ExportedPropertyNode.php +++ b/src/Dependency/ExportedNode/ExportedPropertyNode.php @@ -20,13 +20,16 @@ class ExportedPropertyNode implements JsonSerializable, ExportedNode private bool $static; + private bool $readonly; + public function __construct( string $name, ?ExportedPhpDocNode $phpDoc, ?string $type, bool $public, bool $private, - bool $static + bool $static, + bool $readonly ) { $this->name = $name; @@ -35,6 +38,7 @@ public function __construct( $this->public = $public; $this->private = $private; $this->static = $static; + $this->readonly = $readonly; } public function equals(ExportedNode $node): bool @@ -59,7 +63,8 @@ public function equals(ExportedNode $node): bool && $this->type === $node->type && $this->public === $node->public && $this->private === $node->private - && $this->static === $node->static; + && $this->static === $node->static + && $this->readonly === $node->readonly; } /** @@ -74,7 +79,8 @@ public static function __set_state(array $properties): ExportedNode $properties['type'], $properties['public'], $properties['private'], - $properties['static'] + $properties['static'], + $properties['readonly'] ); } @@ -90,7 +96,8 @@ public static function decode(array $data): ExportedNode $data['type'], $data['public'], $data['private'], - $data['static'] + $data['static'], + $data['readonly'] ); } @@ -108,6 +115,7 @@ public function jsonSerialize() 'public' => $this->public, 'private' => $this->private, 'static' => $this->static, + 'readonly' => $this->readonly, ], ]; } diff --git a/src/Dependency/ExportedNodeResolver.php b/src/Dependency/ExportedNodeResolver.php index baaa367c79..c452627a3b 100644 --- a/src/Dependency/ExportedNodeResolver.php +++ b/src/Dependency/ExportedNodeResolver.php @@ -180,7 +180,8 @@ public function resolve(string $fileName, \PhpParser\Node $node): ?ExportedNode $this->printType($parentNode->type), $parentNode->isPublic(), $parentNode->isPrivate(), - $parentNode->isStatic() + $parentNode->isStatic(), + $parentNode->isReadonly() ); } From 092d8e3bbf013fe52db81ec7f09385bed2db9f63 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 27 Jul 2021 17:06:45 +0200 Subject: [PATCH 0076/1284] Result cache - notice change in final class constant (PHP 8.1) --- .../ExportedNode/ExportedClassConstantNode.php | 11 +++++++++-- src/Dependency/ExportedNodeResolver.php | 1 + 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Dependency/ExportedNode/ExportedClassConstantNode.php b/src/Dependency/ExportedNode/ExportedClassConstantNode.php index 16e5a29848..3caab6fb50 100644 --- a/src/Dependency/ExportedNode/ExportedClassConstantNode.php +++ b/src/Dependency/ExportedNode/ExportedClassConstantNode.php @@ -16,14 +16,17 @@ class ExportedClassConstantNode implements ExportedNode, JsonSerializable private bool $private; + private bool $final; + private ?ExportedPhpDocNode $phpDoc; - public function __construct(string $name, string $value, bool $public, bool $private, ?ExportedPhpDocNode $phpDoc) + public function __construct(string $name, string $value, bool $public, bool $private, bool $final, ?ExportedPhpDocNode $phpDoc) { $this->name = $name; $this->value = $value; $this->public = $public; $this->private = $private; + $this->final = $final; $this->phpDoc = $phpDoc; } @@ -48,7 +51,8 @@ public function equals(ExportedNode $node): bool return $this->name === $node->name && $this->value === $node->value && $this->public === $node->public - && $this->private === $node->private; + && $this->private === $node->private + && $this->final === $node->final; } /** @@ -62,6 +66,7 @@ public static function __set_state(array $properties): ExportedNode $properties['value'], $properties['public'], $properties['private'], + $properties['final'], $properties['phpDoc'] ); } @@ -77,6 +82,7 @@ public static function decode(array $data): ExportedNode $data['value'], $data['public'], $data['private'], + $data['final'], $data['phpDoc'], ); } @@ -93,6 +99,7 @@ public function jsonSerialize() 'value' => $this->value, 'public' => $this->public, 'private' => $this->private, + 'final' => $this->final, 'phpDoc' => $this->phpDoc, ], ]; diff --git a/src/Dependency/ExportedNodeResolver.php b/src/Dependency/ExportedNodeResolver.php index c452627a3b..8c04d434e5 100644 --- a/src/Dependency/ExportedNodeResolver.php +++ b/src/Dependency/ExportedNodeResolver.php @@ -207,6 +207,7 @@ public function resolve(string $fileName, \PhpParser\Node $node): ?ExportedNode $this->printer->prettyPrintExpr($node->value), $parentNode->isPublic(), $parentNode->isPrivate(), + $parentNode->isFinal(), $this->exportPhpDocNode( $fileName, $classNode->namespacedName->toString(), From 0471f87df9c22b0f20e9fa47fdf3e0d67d878480 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 27 Jul 2021 17:35:49 +0200 Subject: [PATCH 0077/1284] Fix --- src/Dependency/ExportedNode/ExportedClassConstantNode.php | 2 +- src/Dependency/ExportedNodeResolver.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Dependency/ExportedNode/ExportedClassConstantNode.php b/src/Dependency/ExportedNode/ExportedClassConstantNode.php index 3caab6fb50..e9c0523abf 100644 --- a/src/Dependency/ExportedNode/ExportedClassConstantNode.php +++ b/src/Dependency/ExportedNode/ExportedClassConstantNode.php @@ -83,7 +83,7 @@ public static function decode(array $data): ExportedNode $data['public'], $data['private'], $data['final'], - $data['phpDoc'], + $data['phpDoc'] !== null ? ExportedPhpDocNode::decode($data['phpDoc']['data']) : null ); } diff --git a/src/Dependency/ExportedNodeResolver.php b/src/Dependency/ExportedNodeResolver.php index 8c04d434e5..855da7f885 100644 --- a/src/Dependency/ExportedNodeResolver.php +++ b/src/Dependency/ExportedNodeResolver.php @@ -213,7 +213,7 @@ public function resolve(string $fileName, \PhpParser\Node $node): ?ExportedNode $classNode->namespacedName->toString(), null, $docComment !== null ? $docComment->getText() : null - ), + ) ); } From 48ec184015023796ec32234d918fbda305606c51 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 29 Jul 2021 17:08:36 +0200 Subject: [PATCH 0078/1284] Do not rely on registered exception handler when debugging --- src/Command/AnalyseCommand.php | 41 +++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/src/Command/AnalyseCommand.php b/src/Command/AnalyseCommand.php index 4a110d3bdc..1cbd1ed38d 100644 --- a/src/Command/AnalyseCommand.php +++ b/src/Command/AnalyseCommand.php @@ -224,17 +224,36 @@ protected function execute(InputInterface $input, OutputInterface $output): int throw new \PHPStan\ShouldNotHappenException(); } - $analysisResult = $application->analyse( - $files, - $onlyFiles, - $inceptionResult->getStdOutput(), - $inceptionResult->getErrorOutput(), - $inceptionResult->isDefaultLevelUsed(), - $debug, - $inceptionResult->getProjectConfigFile(), - $inceptionResult->getProjectConfigArray(), - $input - ); + try { + $analysisResult = $application->analyse( + $files, + $onlyFiles, + $inceptionResult->getStdOutput(), + $inceptionResult->getErrorOutput(), + $inceptionResult->isDefaultLevelUsed(), + $debug, + $inceptionResult->getProjectConfigFile(), + $inceptionResult->getProjectConfigArray(), + $input + ); + } catch (\Throwable $t) { + if ($debug) { + $inceptionResult->getStdOutput()->writeRaw(sprintf( + 'Uncaught %s: %s in %s:%d', + get_class($t), + $t->getMessage(), + $t->getFile(), + $t->getLine() + )); + $inceptionResult->getStdOutput()->writeLineFormatted(''); + $inceptionResult->getStdOutput()->writeRaw($t->getTraceAsString()); + $inceptionResult->getStdOutput()->writeLineFormatted(''); + + return $inceptionResult->handleReturn(1); + } + + throw $t; + } if ($generateBaselineFile !== null) { if (!$analysisResult->hasErrors()) { From 28ac8169eaa31196e78dfa33c6bb1297fb3624c1 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 30 Jul 2021 09:09:09 +0200 Subject: [PATCH 0079/1284] github-actions: declare timeout for job execution --- .github/workflows/backward-compatibility.yml | 1 + .github/workflows/compiler-tests.yml | 1 + .github/workflows/e2e-tests.yml | 2 ++ .github/workflows/lint.yml | 3 +++ .github/workflows/phar.yml | 1 + .github/workflows/static-analysis.yml | 5 ++++- .github/workflows/tests.yml | 3 +++ 7 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/backward-compatibility.yml b/.github/workflows/backward-compatibility.yml index 1e60de1f30..25ad88fe2f 100644 --- a/.github/workflows/backward-compatibility.yml +++ b/.github/workflows/backward-compatibility.yml @@ -16,6 +16,7 @@ jobs: name: "Backward Compatibility" runs-on: "ubuntu-latest" + timeout-minutes: 30 steps: - name: "Checkout" diff --git a/.github/workflows/compiler-tests.yml b/.github/workflows/compiler-tests.yml index dd87983600..2771ea134c 100644 --- a/.github/workflows/compiler-tests.yml +++ b/.github/workflows/compiler-tests.yml @@ -16,6 +16,7 @@ jobs: name: "Compiler Tests" runs-on: "ubuntu-latest" + timeout-minutes: 30 steps: - name: "Checkout" diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 649cfc9cfc..45f8c5e34d 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -16,6 +16,7 @@ jobs: name: "Result cache E2E tests" runs-on: ${{ matrix.operating-system }} + timeout-minutes: 30 strategy: fail-fast: false @@ -46,6 +47,7 @@ jobs: e2e-tests: name: "E2E tests" runs-on: "ubuntu-latest" + timeout-minutes: 30 strategy: matrix: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 22c3bd4f36..ff8c179983 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -15,6 +15,7 @@ jobs: lint: name: "Lint" runs-on: "ubuntu-latest" + timeout-minutes: 30 strategy: fail-fast: false @@ -53,6 +54,7 @@ jobs: name: "Coding Standard" runs-on: "ubuntu-latest" + timeout-minutes: 30 strategy: matrix: @@ -85,6 +87,7 @@ jobs: name: "Dependency Analysis" runs-on: "ubuntu-latest" + timeout-minutes: 30 strategy: matrix: diff --git a/.github/workflows/phar.yml b/.github/workflows/phar.yml index a1a99ee8a6..685bcda26c 100644 --- a/.github/workflows/phar.yml +++ b/.github/workflows/phar.yml @@ -13,6 +13,7 @@ jobs: compile: name: "Compile PHAR" runs-on: "ubuntu-latest" + timeout-minutes: 30 steps: - name: "Checkout" diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 7e829b1f7b..8512f5a8e4 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -15,6 +15,7 @@ jobs: static-analysis: name: "PHPStan" runs-on: ${{ matrix.operating-system }} + timeout-minutes: 30 strategy: fail-fast: false @@ -59,7 +60,8 @@ jobs: static-analysis-with-result-cache: name: "PHPStan with result cache" - runs-on: ubuntu-latest + runs-on: "ubuntu-latest" + timeout-minutes: 30 strategy: matrix: @@ -105,6 +107,7 @@ jobs: name: "Generate baseline" runs-on: "ubuntu-latest" + timeout-minutes: 30 strategy: matrix: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 63439d983c..1c03934271 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -15,6 +15,7 @@ jobs: tests: name: "Tests" runs-on: ${{ matrix.operating-system }} + timeout-minutes: 30 strategy: fail-fast: false @@ -55,6 +56,7 @@ jobs: tests-old-phpunit: name: "Tests with old PHPUnit" runs-on: ${{ matrix.operating-system }} + timeout-minutes: 30 strategy: fail-fast: false @@ -97,6 +99,7 @@ jobs: name: "Tests with code coverage" runs-on: "ubuntu-latest" + timeout-minutes: 30 steps: - name: "Checkout" From d21813408ea3b613ade22e14f37f5f635be9ed73 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 28 Jul 2021 16:34:10 +0200 Subject: [PATCH 0080/1284] Added integer range phpdoc support --- src/PhpDoc/TypeNodeResolver.php | 21 ++++++++++++ .../Analyser/data/integer-range-types.php | 33 +++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index 2c9963c000..37298c94ee 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -474,6 +474,27 @@ private function resolveGenericTypeNode(GenericTypeNode $typeNode, NameScope $na } return new ErrorType(); + } elseif ($mainTypeName === 'int') { + if (count($genericTypes) === 2) { // int, int<1, 3> + + if ($genericTypes[0] instanceof ConstantIntegerType) { + $min = $genericTypes[0]->getValue(); + } elseif ($typeNode->genericTypes[0] instanceof IdentifierTypeNode && $typeNode->genericTypes[0]->name === 'min') { + $min = null; + } else { + return new ErrorType(); + } + + if ($genericTypes[1] instanceof ConstantIntegerType) { + $max = $genericTypes[1]->getValue(); + } elseif ($typeNode->genericTypes[1] instanceof IdentifierTypeNode && $typeNode->genericTypes[1]->name === 'max') { + $max = null; + } else { + return new ErrorType(); + } + + return IntegerRangeType::fromInterval($min, $max); + } } $mainType = $this->resolveIdentifierTypeNode($typeNode->type, $nameScope); diff --git a/tests/PHPStan/Analyser/data/integer-range-types.php b/tests/PHPStan/Analyser/data/integer-range-types.php index 794c339935..53a37bd563 100644 --- a/tests/PHPStan/Analyser/data/integer-range-types.php +++ b/tests/PHPStan/Analyser/data/integer-range-types.php @@ -156,3 +156,36 @@ function (int $a, int $b, int $c): void { assertType('int', $b * $c); assertType('int', $a * $b * $c); }; + +class X { + /** + * @var int<0, 100> + */ + public $percentage; + /** + * @var int + */ + public $min; + /** + * @var int<0, max> + */ + public $max; + + /** + * @var int<0, something> + */ + public $error1; + /** + * @var int + */ + public $error2; + + public function supportsPhpdocIntegerRange() { + assertType('int<0, 100>', $this->percentage); + assertType('int', $this->min); + assertType('int<0, max>', $this->max); + + assertType('*ERROR*', $this->error1); + assertType('*ERROR*', $this->error2); + } +} From 5f494f900cff92b51f14a83e1703a4880ac23f82 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 30 Jul 2021 09:17:31 +0200 Subject: [PATCH 0081/1284] IntegerRangeType - test edgecase --- tests/PHPStan/Analyser/data/integer-range-types.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/PHPStan/Analyser/data/integer-range-types.php b/tests/PHPStan/Analyser/data/integer-range-types.php index 53a37bd563..d25e0d801c 100644 --- a/tests/PHPStan/Analyser/data/integer-range-types.php +++ b/tests/PHPStan/Analyser/data/integer-range-types.php @@ -180,6 +180,11 @@ class X { */ public $error2; + /** + * @var int + */ + public $int; + public function supportsPhpdocIntegerRange() { assertType('int<0, 100>', $this->percentage); assertType('int', $this->min); @@ -187,5 +192,6 @@ public function supportsPhpdocIntegerRange() { assertType('*ERROR*', $this->error1); assertType('*ERROR*', $this->error2); + assertType('int', $this->int); } } From 91fde71dc649e07b5ee19a565eb021e6205a7cbe Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 30 Jul 2021 09:18:40 +0200 Subject: [PATCH 0082/1284] Update PHP 8 stubs --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index bdcce0555f..a448265b73 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "nikic/php-parser": "4.12.0", "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "4.3.62", - "phpstan/php-8-stubs": "^0.1.21", + "phpstan/php-8-stubs": "^0.1.22", "phpstan/phpdoc-parser": "^0.5.5", "react/child-process": "^0.6.1", "react/event-loop": "^1.1", diff --git a/composer.lock b/composer.lock index 37f04bcf98..f337543c91 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "2fa566c509af2b7b1355123d666e838b", + "content-hash": "5e9962680324731ed9a8faa87bf9a9a7", "packages": [ { "name": "clue/block-react", @@ -2144,16 +2144,16 @@ }, { "name": "phpstan/php-8-stubs", - "version": "0.1.21", + "version": "0.1.22", "source": { "type": "git", "url": "https://github.com/phpstan/php-8-stubs.git", - "reference": "687165b0b4b0ef908278c03e65b2e1c05ecf8e92" + "reference": "1e74d25745ce476a360423607be6250c94a50e2f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/687165b0b4b0ef908278c03e65b2e1c05ecf8e92", - "reference": "687165b0b4b0ef908278c03e65b2e1c05ecf8e92", + "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/1e74d25745ce476a360423607be6250c94a50e2f", + "reference": "1e74d25745ce476a360423607be6250c94a50e2f", "shasum": "" }, "type": "library", @@ -2170,9 +2170,9 @@ "description": "PHP stubs extracted from php-src", "support": { "issues": "https://github.com/phpstan/php-8-stubs/issues", - "source": "https://github.com/phpstan/php-8-stubs/tree/0.1.21" + "source": "https://github.com/phpstan/php-8-stubs/tree/0.1.22" }, - "time": "2021-06-06T16:07:43+00:00" + "time": "2021-07-29T00:44:48+00:00" }, { "name": "phpstan/phpdoc-parser", From 8f2e45ccfca7f6e04fbe125772085aa04ee17147 Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Wed, 28 Jul 2021 14:53:35 +0200 Subject: [PATCH 0083/1284] ResultCacheManager: introduce flag checkDependenciesOfProjectExtensionFiles --- conf/config.neon | 3 +++ src/Analyser/ResultCache/ResultCacheManager.php | 15 +++++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index a15d7c0c2b..50675c5164 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -114,6 +114,7 @@ parameters: memoryLimitFile: %tmpDir%/.memory_limit tempResultCachePath: %tmpDir%/resultCaches resultCachePath: %tmpDir%/resultCache.php + resultCacheChecksProjectExtensionFilesDependencies: false staticReflectionClassNamePatterns: - '#^PhpParser\\#' - '#^PHPStan\\#' @@ -305,6 +306,7 @@ parametersSchema: memoryLimitFile: string() tempResultCachePath: string() resultCachePath: string() + resultCacheChecksProjectExtensionFilesDependencies: bool() staticReflectionClassNamePatterns: listOf(string()) dynamicConstantNames: listOf(string()) customRulesetUsed: bool() @@ -477,6 +479,7 @@ services: bootstrapFiles: %bootstrapFiles% scanFiles: %scanFiles% scanDirectories: %scanDirectories% + checkDependenciesOfProjectExtensionFiles: %resultCacheChecksProjectExtensionFilesDependencies% - class: PHPStan\Analyser\ResultCache\ResultCacheClearer diff --git a/src/Analyser/ResultCache/ResultCacheManager.php b/src/Analyser/ResultCache/ResultCacheManager.php index aab1f449cd..71ae46d78c 100644 --- a/src/Analyser/ResultCache/ResultCacheManager.php +++ b/src/Analyser/ResultCache/ResultCacheManager.php @@ -62,6 +62,8 @@ class ResultCacheManager /** @var array */ private array $alreadyProcessed = []; + private bool $checkDependenciesOfProjectExtensionFiles; + /** * @param ExportedNodeFetcher $exportedNodeFetcher * @param FileFinder $scanFileFinder @@ -92,7 +94,8 @@ public function __construct( array $bootstrapFiles, array $scanFiles, array $scanDirectories, - array $fileReplacements + array $fileReplacements, + bool $checkDependenciesOfProjectExtensionFiles ) { $this->exportedNodeFetcher = $exportedNodeFetcher; @@ -109,6 +112,7 @@ public function __construct( $this->scanFiles = $scanFiles; $this->scanDirectories = $scanDirectories; $this->fileReplacements = $fileReplacements; + $this->checkDependenciesOfProjectExtensionFiles = $checkDependenciesOfProjectExtensionFiles; } /** @@ -688,9 +692,12 @@ private function getAllDependencies(string $fileName, array $dependencies): arra $this->alreadyProcessed[$fileName] = true; $files = [$fileName]; - foreach ($dependencies[$fileName] as $fileDep) { - foreach ($this->getAllDependencies($fileDep, $dependencies) as $fileDep2) { - $files[] = $fileDep2; + + if ($this->checkDependenciesOfProjectExtensionFiles) { + foreach ($dependencies[$fileName] as $fileDep) { + foreach ($this->getAllDependencies($fileDep, $dependencies) as $fileDep2) { + $files[] = $fileDep2; + } } } From cdf9cb50129502217aca5a3d9ee3ed0c3795a148 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 30 Jul 2021 11:18:09 +0200 Subject: [PATCH 0084/1284] Scalar type in PHPDoc can mean an existing class --- src/PhpDoc/TypeNodeResolver.php | 6 +++ .../Rules/Generics/ClassAncestorsRuleTest.php | 5 ++ .../Rules/Generics/data/scalar-class-name.php | 49 +++++++++++++++++++ 3 files changed, 60 insertions(+) create mode 100644 tests/PHPStan/Rules/Generics/data/scalar-class-name.php diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index 37298c94ee..68bc957b54 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -156,6 +156,12 @@ private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameSco return new BenevolentUnionType([new IntegerType(), new StringType()]); case 'scalar': + $type = $this->tryResolvePseudoTypeClassType($typeNode, $nameScope); + + if ($type !== null) { + return $type; + } + return new UnionType([new IntegerType(), new FloatType(), new StringType(), new BooleanType()]); case 'number': diff --git a/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php b/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php index ee5db5befc..3a121c2fe8 100644 --- a/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php +++ b/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php @@ -216,4 +216,9 @@ public function testCrossCheckInterfaces(): void ]); } + public function testScalarClassName(): void + { + $this->analyse([__DIR__ . '/data/scalar-class-name.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Generics/data/scalar-class-name.php b/tests/PHPStan/Rules/Generics/data/scalar-class-name.php new file mode 100644 index 0000000000..dc194c1da5 --- /dev/null +++ b/tests/PHPStan/Rules/Generics/data/scalar-class-name.php @@ -0,0 +1,49 @@ + + */ +class The implements Scalar +{ + /** + * @param T $subject + * @param Closure(T): mixed $context + */ + public function __construct( + /** + * @var T + */ + private mixed $subject, + /** + * @var Closure(T): mixed + */ + private Closure $context + ) { + } + /** + * @return T + */ + public function value(): mixed + { + ($this->context)($this->subject); + return $this->subject; + } +} From 419bc0d5d2838ef88b711990e51bf60243e86816 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 30 Jul 2021 11:34:36 +0200 Subject: [PATCH 0085/1284] Fix --- .../Rules/Generics/data/scalar-class-name.php | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/tests/PHPStan/Rules/Generics/data/scalar-class-name.php b/tests/PHPStan/Rules/Generics/data/scalar-class-name.php index dc194c1da5..363bbd154e 100644 --- a/tests/PHPStan/Rules/Generics/data/scalar-class-name.php +++ b/tests/PHPStan/Rules/Generics/data/scalar-class-name.php @@ -9,7 +9,7 @@ interface Scalar /** * @return T */ - public function value(): mixed; + public function value(); } namespace MaxGoryunov\SavingIterator\Fakes; @@ -23,25 +23,32 @@ public function value(): mixed; */ class The implements Scalar { + /** + * @var T + */ + private $subject; + + /** + * @var Closure(T): mixed + */ + private $context; + /** * @param T $subject * @param Closure(T): mixed $context */ public function __construct( - /** - * @var T - */ - private mixed $subject, - /** - * @var Closure(T): mixed - */ - private Closure $context - ) { + $subject, + $context + ) + { + $this->subject = $subject; + $this->context = $context; } /** * @return T */ - public function value(): mixed + public function value() { ($this->context)($this->subject); return $this->subject; From f3d449fd8ba37132a1bff54db94707a78a0ba913 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Fri, 30 Jul 2021 11:40:52 +0200 Subject: [PATCH 0086/1284] DateTime|DateTimeInterface union accepts DateTimeInterface --- src/Rules/RuleLevelHelper.php | 5 +- src/Type/UnionType.php | 10 ++++ .../Rules/Methods/CallMethodsRuleTest.php | 8 +++ .../Rules/Methods/data/call-methods.php | 54 +++++++++++++++++++ 4 files changed, 74 insertions(+), 3 deletions(-) diff --git a/src/Rules/RuleLevelHelper.php b/src/Rules/RuleLevelHelper.php index 240258921c..7fc9724b81 100644 --- a/src/Rules/RuleLevelHelper.php +++ b/src/Rules/RuleLevelHelper.php @@ -75,7 +75,8 @@ public function accepts(Type $acceptingType, Type $acceptedType, bool $strictTyp $acceptedType = TypeCombinator::removeNull($acceptedType); } - if ($acceptingType instanceof UnionType && !$acceptedType instanceof CompoundType) { + $accepts = $acceptingType->accepts($acceptedType, $strictTypes); + if (!$accepts->yes() && $acceptingType instanceof UnionType && !$acceptedType instanceof CompoundType) { foreach ($acceptingType->getTypes() as $innerType) { if (self::accepts($innerType, $acceptedType, $strictTypes)) { return true; @@ -103,8 +104,6 @@ public function accepts(Type $acceptingType, Type $acceptedType, bool $strictTyp ); } - $accepts = $acceptingType->accepts($acceptedType, $strictTypes); - return $this->checkUnionTypes ? $accepts->yes() : !$accepts->no(); } diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index d951eb6d27..272daeab81 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -70,6 +70,16 @@ public function getReferencedClasses(): array public function accepts(Type $type, bool $strictTypes): TrinaryLogic { + if ( + $type->equals(new ObjectType(\DateTimeInterface::class)) + && $this->accepts( + new UnionType([new ObjectType(\DateTime::class), new ObjectType(\DateTimeImmutable::class)]), + $strictTypes + )->yes() + ) { + return TrinaryLogic::createYes(); + } + if ($type instanceof CompoundType && !$type instanceof CallableType) { return CompoundTypeHelper::accepts($type, $this, $strictTypes); } diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index ca301bc9e8..2ff8deac41 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -493,6 +493,10 @@ public function testCallMethods(): void 'Parameter #1 $test of method Test\NumericStringParam::sayHello() expects string&numeric, \'abc\' given.', 1658, ], + [ + 'Parameter #1 $date of method Test\HelloWorld3::sayHello() expects array|int, DateTimeInterface given.', + 1732, + ], ]); } @@ -767,6 +771,10 @@ public function testCallMethodsOnThisOnly(): void 'Parameter #1 $test of method Test\NumericStringParam::sayHello() expects string&numeric, \'abc\' given.', 1658, ], + [ + 'Parameter #1 $date of method Test\HelloWorld3::sayHello() expects array|int, DateTimeInterface given.', + 1732, + ], ]); } diff --git a/tests/PHPStan/Rules/Methods/data/call-methods.php b/tests/PHPStan/Rules/Methods/data/call-methods.php index 3d5669648c..f2aa4b3ec5 100644 --- a/tests/PHPStan/Rules/Methods/data/call-methods.php +++ b/tests/PHPStan/Rules/Methods/data/call-methods.php @@ -1678,3 +1678,57 @@ public function openStatically(): void } } + +class HelloWorld +{ + /** + * @param \DateTime|\DateTimeImmutable|int $date + */ + public function sayHello($date): void + { + } + + /** + * @param \DateTimeInterface|int $d + */ + public function foo($d): void + { + $this->sayHello($d); + } +} + +class HelloWorld2 +{ + /** + * @param \DateTime|\DateTimeImmutable $date + */ + public function sayHello($date): void + { + } + + /** + * @param \DateTimeInterface $d + */ + public function foo($d): void + { + $this->sayHello($d); + } +} + +class HelloWorld3 +{ + /** + * @param array<\DateTime|\DateTimeImmutable>|int $date + */ + public function sayHello($date): void + { + } + + /** + * @param \DateTimeInterface $d + */ + public function foo($d): void + { + $this->sayHello($d); + } +} From abb4378d635d1b8caaa2b5bd50e0304eec396f5f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 30 Jul 2021 13:17:45 +0200 Subject: [PATCH 0087/1284] Make default parallel.processTimeout 10x bigger --- conf/config.neon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/config.neon b/conf/config.neon index 50675c5164..ac0f76e3da 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -84,7 +84,7 @@ parameters: scanDirectories: [] parallel: jobSize: 20 - processTimeout: 60.0 + processTimeout: 600.0 maximumNumberOfProcesses: 32 minimumNumberOfJobsPerProcess: 2 buffer: 134217728 # 128 MB From b04e60ac5ab7551f1609b8cb3a84aea6a8c05671 Mon Sep 17 00:00:00 2001 From: Andrii Date: Fri, 30 Jul 2021 14:55:39 +0300 Subject: [PATCH 0088/1284] change return type for chr() --- resources/functionMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 59374884c9..4771a1c329 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -944,7 +944,7 @@ 'chmod' => ['bool', 'filename'=>'string', 'mode'=>'int'], 'chop' => ['string', 'str'=>'string', 'character_mask='=>'string'], 'chown' => ['bool', 'filename'=>'string', 'user'=>'string|int'], -'chr' => ['string', 'ascii'=>'int'], +'chr' => ['non-empty-string', 'ascii'=>'int'], 'chroot' => ['bool', 'directory'=>'string'], 'chunk_split' => ['string', 'str'=>'string', 'chunklen='=>'int', 'ending='=>'string'], 'class_alias' => ['bool', 'user_class_name'=>'string', 'alias_name'=>'string', 'autoload='=>'bool'], From e10a7aac373e8b6f21b430034fc693300c2bbb69 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 30 Jul 2021 19:31:00 +0200 Subject: [PATCH 0089/1284] Cover non-empty-string in more string fuctions --- .../Php/NonEmptyStringFunctionsReturnTypeExtension.php | 4 ++++ tests/PHPStan/Analyser/data/non-empty-string.php | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/src/Type/Php/NonEmptyStringFunctionsReturnTypeExtension.php b/src/Type/Php/NonEmptyStringFunctionsReturnTypeExtension.php index 17d77f39e0..f4afe77ea8 100644 --- a/src/Type/Php/NonEmptyStringFunctionsReturnTypeExtension.php +++ b/src/Type/Php/NonEmptyStringFunctionsReturnTypeExtension.php @@ -17,6 +17,10 @@ class NonEmptyStringFunctionsReturnTypeExtension implements DynamicFunctionRetur public function isFunctionSupported(FunctionReflection $functionReflection): bool { return in_array($functionReflection->getName(), [ + 'addslashes', + 'addcslashes', + 'escapeshellarg', + 'escapeshellcmd', 'strtoupper', 'strtolower', 'mb_strtoupper', diff --git a/tests/PHPStan/Analyser/data/non-empty-string.php b/tests/PHPStan/Analyser/data/non-empty-string.php index a233259c5a..457229c9ba 100644 --- a/tests/PHPStan/Analyser/data/non-empty-string.php +++ b/tests/PHPStan/Analyser/data/non-empty-string.php @@ -305,6 +305,16 @@ class MoreNonEmptyStringFunctions */ public function doFoo(string $s, string $nonEmpty, int $i) { + assertType('string', addslashes($s)); + assertType('non-empty-string', addslashes($nonEmpty)); + assertType('string', addcslashes($s)); + assertType('non-empty-string', addcslashes($nonEmpty)); + + assertType('string', escapeshellarg($s)); + assertType('non-empty-string', escapeshellarg($nonEmpty)); + assertType('string', escapeshellcmd($s)); + assertType('non-empty-string', escapeshellcmd($nonEmpty)); + assertType('string', strtoupper($s)); assertType('non-empty-string', strtoupper($nonEmpty)); assertType('string', strtolower($s)); From 39aa45feab3b7a0f0945bc65f8daccd5cda5d2b0 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 11 Aug 2021 19:58:54 +0100 Subject: [PATCH 0090/1284] ZEND_ dynamic constants --- conf/config.neon | 2 ++ 1 file changed, 2 insertions(+) diff --git a/conf/config.neon b/conf/config.neon index ac0f76e3da..c28b72036a 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -160,6 +160,8 @@ parameters: - PHP_SHLIB_SUFFIX - PHP_FD_SETSIZE - OPENSSL_VERSION_NUMBER + - ZEND_DEBUG_BUILD + - ZEND_THREAD_SAFE editorUrl: null extensions: From eb15c4671a115ffb2ed5ec83d2ddd84e3c7e3d86 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 12 Aug 2021 13:09:42 +0200 Subject: [PATCH 0091/1284] Added rand() dynamic return type extension --- .../Php/RandomIntFunctionReturnTypeExtension.php | 8 ++++++-- tests/PHPStan/Analyser/AnalyserIntegrationTest.php | 9 +++++++-- .../PHPStan/Analyser/LegacyNodeScopeResolverTest.php | 2 +- .../data/do-not-remember-impure-functions.php | 8 ++++---- tests/PHPStan/Analyser/data/random-int.php | 3 +++ tests/PHPStan/Analyser/data/strval.php | 2 +- .../StrictComparisonOfDifferentTypesRuleTest.php | 12 ++++++++++-- 7 files changed, 32 insertions(+), 12 deletions(-) diff --git a/src/Type/Php/RandomIntFunctionReturnTypeExtension.php b/src/Type/Php/RandomIntFunctionReturnTypeExtension.php index 21faac5e72..6f5b9762bc 100644 --- a/src/Type/Php/RandomIntFunctionReturnTypeExtension.php +++ b/src/Type/Php/RandomIntFunctionReturnTypeExtension.php @@ -16,13 +16,17 @@ class RandomIntFunctionReturnTypeExtension implements \PHPStan\Type\DynamicFunct public function isFunctionSupported(FunctionReflection $functionReflection): bool { - return $functionReflection->getName() === 'random_int'; + return in_array($functionReflection->getName(), ['random_int', 'rand'], true); } public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type { + if ($functionReflection->getName() === 'rand' && count($functionCall->args) === 0) { + return IntegerRangeType::fromInterval(0, null); + } + if (count($functionCall->args) < 2) { - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + return ParametersAcceptorSelector::selectFromArgs($scope, $functionCall->args, $functionReflection->getVariants())->getReturnType(); } $minType = $scope->getType($functionCall->args[0]->value)->toInteger(); diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 900a615005..dffd663c06 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -219,7 +219,8 @@ public function testBug2823(): void public function testTwoSameClassesInSingleFile(): void { $errors = $this->runAnalyse(__DIR__ . '/data/two-same-classes.php'); - $this->assertCount(4, $errors); + $this->assertCount(5, $errors); + $error = $errors[0]; $this->assertSame('Property TwoSame\Foo::$prop (string) does not accept default value of type int.', $error->getMessage()); $this->assertSame(9, $error->getLine()); @@ -229,10 +230,14 @@ public function testTwoSameClassesInSingleFile(): void $this->assertSame(13, $error->getLine()); $error = $errors[2]; + $this->assertSame('If condition is always false.', $error->getMessage()); + $this->assertSame(18, $error->getLine()); + + $error = $errors[3]; $this->assertSame('Property TwoSame\Foo::$prop (int) does not accept default value of type string.', $error->getMessage()); $this->assertSame(25, $error->getLine()); - $error = $errors[3]; + $error = $errors[4]; $this->assertSame('Property TwoSame\Foo::$prop2 (int) does not accept default value of type string.', $error->getMessage()); $this->assertSame(28, $error->getLine()); } diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 0eeefe63b9..27983a2c6a 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -8317,7 +8317,7 @@ public function dataFilterVarUnchanged(): array 'filter_var(3.27, FILTER_VALIDATE_FLOAT, FILTER_NULL_ON_FAILURE)', ], [ - 'int', + 'int<0, max>', 'filter_var(rand(), FILTER_VALIDATE_INT)', ], [ diff --git a/tests/PHPStan/Analyser/data/do-not-remember-impure-functions.php b/tests/PHPStan/Analyser/data/do-not-remember-impure-functions.php index 498a37a7de..4b086bc15c 100644 --- a/tests/PHPStan/Analyser/data/do-not-remember-impure-functions.php +++ b/tests/PHPStan/Analyser/data/do-not-remember-impure-functions.php @@ -11,18 +11,18 @@ public function doFoo() { function (): void { if (rand(0, 1)) { - assertType('int', rand(0, 1)); + assertType('int<0, 1>', rand(0, 1)); } }; function (): void { if (rand(0, 1) === 0) { - assertType('int', rand(0, 1)); + assertType('int<0, 1>', rand(0, 1)); } }; function (): void { - assertType('\'foo\'|int|int<1, max>', rand(0, 1) ?: 'foo'); - assertType('\'foo\'|int', rand(0, 1) ? rand(0, 1) : 'foo'); + assertType('1|\'foo\'', rand(0, 1) ?: 'foo'); + assertType('\'foo\'|int<0, 1>', rand(0, 1) ? rand(0, 1) : 'foo'); }; } diff --git a/tests/PHPStan/Analyser/data/random-int.php b/tests/PHPStan/Analyser/data/random-int.php index 709f9282f0..4f501352d3 100644 --- a/tests/PHPStan/Analyser/data/random-int.php +++ b/tests/PHPStan/Analyser/data/random-int.php @@ -37,3 +37,6 @@ function (int $i) { assertType('int<-5, 5>', random_int(random_int(-5, 0), random_int(0, 5))); assertType('int', random_int(random_int(PHP_INT_MIN, 0), random_int(0, PHP_INT_MAX))); + +assertType('int<-5, 5>', rand(-5, 5)); +assertType('int<0, max>', rand()); diff --git a/tests/PHPStan/Analyser/data/strval.php b/tests/PHPStan/Analyser/data/strval.php index 593bcf5065..d85d37eec1 100644 --- a/tests/PHPStan/Analyser/data/strval.php +++ b/tests/PHPStan/Analyser/data/strval.php @@ -36,7 +36,7 @@ function intvalTest(string $string): void assertType('1', intval(true)); assertType('0|1', intval(rand(0, 1) === 0)); assertType('42', intval(42)); - assertType('int', intval(rand())); + assertType('int<0, max>', intval(rand())); assertType('int', intval(rand() * 0.1)); assertType('0', intval([])); assertType('1', intval([null])); diff --git a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php index d7e007e1ad..154ca46df3 100644 --- a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php @@ -167,7 +167,11 @@ public function testStrictComparison(): void 466, ], [ - 'Strict comparison using === between int and \'foo\' will always evaluate to false.', + 'Strict comparison using === between int<0, 1> and 100 will always evaluate to false.', + 622, + ], + [ + 'Strict comparison using === between 100 and \'foo\' will always evaluate to false.', 624, ], [ @@ -341,7 +345,11 @@ public function testStrictComparisonWithoutAlwaysTrue(): void 466, ], [ - 'Strict comparison using === between int and \'foo\' will always evaluate to false.', + 'Strict comparison using === between int<0, 1> and 100 will always evaluate to false.', + 622, + ], + [ + 'Strict comparison using === between 100 and \'foo\' will always evaluate to false.', 624, ], [ From dec054d330550d4706870b3cdbbd5c51e2880876 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 12 Aug 2021 15:19:55 +0200 Subject: [PATCH 0092/1284] PDOStatement implements IteratorAggregate on PHP 8 --- stubs/PDOStatement.stub | 3 ++- .../Methods/MissingMethodReturnTypehintRuleTest.php | 5 +++++ tests/PHPStan/Rules/Methods/data/bug-5436.php | 11 +++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Rules/Methods/data/bug-5436.php diff --git a/stubs/PDOStatement.stub b/stubs/PDOStatement.stub index 5c63cbce7d..79637d8370 100644 --- a/stubs/PDOStatement.stub +++ b/stubs/PDOStatement.stub @@ -2,9 +2,10 @@ /** * @implements Traversable> + * @implements IteratorAggregate> * @link https://php.net/manual/en/class.pdostatement.php */ -class PDOStatement implements Traversable +class PDOStatement implements Traversable, IteratorAggregate { } diff --git a/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php b/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php index 5ca9f7ae6b..ac0e206740 100644 --- a/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php @@ -77,4 +77,9 @@ public function testBug5089(): void $this->analyse([__DIR__ . '/data/bug-5089.php'], []); } + public function testBug5436(): void + { + $this->analyse([__DIR__ . '/data/bug-5436.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-5436.php b/tests/PHPStan/Rules/Methods/data/bug-5436.php new file mode 100644 index 0000000000..0cbba7cffa --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-5436.php @@ -0,0 +1,11 @@ + Date: Thu, 12 Aug 2021 14:38:24 +0100 Subject: [PATCH 0093/1284] Added stub for WeakMap --- conf/config.neon | 1 + stubs/WeakReference.stub | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/conf/config.neon b/conf/config.neon index c28b72036a..63806b1850 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -39,6 +39,7 @@ parameters: skipCheckGenericClasses: - RecursiveIterator - RecursiveArrayIterator + - WeakMap rememberFunctionValues: false preciseExceptionTracking: false apiRules: false diff --git a/stubs/WeakReference.stub b/stubs/WeakReference.stub index 7a71b77065..5f23dbca9c 100644 --- a/stubs/WeakReference.stub +++ b/stubs/WeakReference.stub @@ -16,3 +16,14 @@ final class WeakReference /** @return ?T */ public function get() {} } + + +/** + * @template TKey of object + * @template TValue + * @implements \ArrayAccess + * @implements \IteratorAggregate + */ +final class WeakMap implements \ArrayAccess, \Countable, \IteratorAggregate +{ +} From 68eab2379d682972975d51721f31571b1b74d644 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Mon, 9 Aug 2021 04:03:28 +0900 Subject: [PATCH 0094/1284] Add ReturnTypeWillChange stub refs https://github.com/phpstan/phpstan/issues/5444 --- stubs/runtime/Attribute.php | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/stubs/runtime/Attribute.php b/stubs/runtime/Attribute.php index d248c52cfe..e41f42cfc4 100644 --- a/stubs/runtime/Attribute.php +++ b/stubs/runtime/Attribute.php @@ -1,10 +1,6 @@ Date: Mon, 9 Aug 2021 08:22:45 -0300 Subject: [PATCH 0095/1284] Remove wrong return type of `imagejpeg` GD function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maurício Meneghini Fauth --- resources/functionMap.php | 1 - resources/functionMap_php80delta.php | 2 -- 2 files changed, 3 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 4771a1c329..7b25d35560 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -4602,7 +4602,6 @@ 'imageinterlace' => ['int', 'im'=>'resource', 'interlace='=>'int'], 'imageistruecolor' => ['bool', 'im'=>'resource'], 'imagejpeg' => ['bool', 'im'=>'resource', 'filename='=>'string|resource|null', 'quality='=>'int'], -'imagejpeg\'1' => ['string|false', 'im'=>'resource', 'filename='=>'null', 'quality='=>'int'], 'imagelayereffect' => ['bool', 'im'=>'resource', 'effect'=>'int'], 'imageline' => ['bool', 'im'=>'resource', 'x1'=>'int', 'y1'=>'int', 'x2'=>'int', 'y2'=>'int', 'col'=>'int'], 'imageloadfont' => ['int|false', 'filename'=>'string'], diff --git a/resources/functionMap_php80delta.php b/resources/functionMap_php80delta.php index 591c424706..f1735f2a4e 100644 --- a/resources/functionMap_php80delta.php +++ b/resources/functionMap_php80delta.php @@ -66,7 +66,6 @@ 'imagegrabscreen' => ['false|object'], 'imagegrabwindow' => ['false|object', 'window_handle'=>'int', 'client_area='=>'int'], 'imagejpeg' => ['bool', 'im'=>'GdImage', 'filename='=>'string|resource|null', 'quality='=>'int'], - 'imagejpeg\'1' => ['string|false', 'im'=>'GdImage', 'filename='=>'null', 'quality='=>'int'], 'imagerotate' => ['false|object', 'src_im'=>'resource', 'angle'=>'float', 'bgdcolor'=>'int', 'ignoretransparent='=>'int'], 'imagescale' => ['false|object', 'im'=>'resource', 'new_width'=>'int', 'new_height='=>'int', 'method='=>'int'], 'mb_decode_numericentity' => ['string|false', 'string'=>'string', 'convmap'=>'array', 'encoding='=>'string'], @@ -191,7 +190,6 @@ 'imagegrabscreen' => ['false|resource'], 'imagegrabwindow' => ['false|resource', 'window_handle'=>'int', 'client_area='=>'int'], 'imagejpeg' => ['bool', 'im'=>'resource', 'filename='=>'string|resource|null', 'quality='=>'int'], - 'imagejpeg\'1' => ['string|false', 'im'=>'resource', 'filename='=>'null', 'quality='=>'int'], 'imagerotate' => ['resource|false', 'src_im'=>'resource', 'angle'=>'float', 'bgdcolor'=>'int', 'ignoretransparent='=>'int'], 'imagescale' => ['resource|false', 'im'=>'resource', 'new_width'=>'int', 'new_height='=>'int', 'method='=>'int'], 'implode\'1' => ['string', 'pieces'=>'array'], From 35a66a27998c8f97aedba435fd1231b27f4a0214 Mon Sep 17 00:00:00 2001 From: Simon Hammes Date: Thu, 12 Aug 2021 17:04:27 +0200 Subject: [PATCH 0096/1284] Assure GitLab error formatter have integer line properties --- .../ErrorFormatter/GitlabErrorFormatter.php | 2 +- src/Testing/ErrorFormatterTestCase.php | 3 +- .../ErrorFormatter/GitlabFormatterTest.php | 64 +++++++++++++++++++ 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/src/Command/ErrorFormatter/GitlabErrorFormatter.php b/src/Command/ErrorFormatter/GitlabErrorFormatter.php index 16713dd779..5bc495d6d2 100644 --- a/src/Command/ErrorFormatter/GitlabErrorFormatter.php +++ b/src/Command/ErrorFormatter/GitlabErrorFormatter.php @@ -41,7 +41,7 @@ public function formatErrors(AnalysisResult $analysisResult, Output $output): in 'location' => [ 'path' => $this->relativePathHelper->getRelativePath($fileSpecificError->getFile()), 'lines' => [ - 'begin' => $fileSpecificError->getLine(), + 'begin' => $fileSpecificError->getLine() ?? 0, ], ], ]; diff --git a/src/Testing/ErrorFormatterTestCase.php b/src/Testing/ErrorFormatterTestCase.php index 24f38c5848..d80ae4d44d 100644 --- a/src/Testing/ErrorFormatterTestCase.php +++ b/src/Testing/ErrorFormatterTestCase.php @@ -60,7 +60,7 @@ protected function getOutputContent(): string protected function getAnalysisResult(int $numFileErrors, int $numGenericErrors): AnalysisResult { - if ($numFileErrors > 4 || $numFileErrors < 0 || $numGenericErrors > 2 || $numGenericErrors < 0) { + if ($numFileErrors > 5 || $numFileErrors < 0 || $numGenericErrors > 2 || $numGenericErrors < 0) { throw new \PHPStan\ShouldNotHappenException(); } @@ -69,6 +69,7 @@ protected function getAnalysisResult(int $numFileErrors, int $numGenericErrors): new Error('Foo', self::DIRECTORY_PATH . '/foo.php', 1), new Error("Bar\nBar2", self::DIRECTORY_PATH . '/foo.php', 5), new Error("Bar\nBar2", self::DIRECTORY_PATH . '/folder with unicode 😃/file name with "spaces" and unicode 😃.php', 2), + new Error("Bar\nBar2", self::DIRECTORY_PATH . '/foo.php', null), ], 0, $numFileErrors); $genericErrors = array_slice([ diff --git a/tests/PHPStan/Command/ErrorFormatter/GitlabFormatterTest.php b/tests/PHPStan/Command/ErrorFormatter/GitlabFormatterTest.php index cadb05c53d..554e0a81c0 100644 --- a/tests/PHPStan/Command/ErrorFormatter/GitlabFormatterTest.php +++ b/tests/PHPStan/Command/ErrorFormatter/GitlabFormatterTest.php @@ -111,6 +111,70 @@ public function dataFormatterOutputProvider(): iterable ]', ]; + yield [ + 'Multiple file errors, including error with line=null', + 1, + 5, + 0, + '[ + { + "description": "Bar\nBar2", + "fingerprint": "034b4afbfb347494c14e396ed8327692f58be4cd27e8aff5f19f4194934db7c9", + "severity": "major", + "location": { + "path": "with space/and unicode 😃/project/folder with unicode 😃/file name with \"spaces\" and unicode 😃.php", + "lines": { + "begin": 2 + } + } + }, + { + "description": "Foo", + "fingerprint": "e82b7e1f1d4255352b19ecefa9116a12f129c7edb4351cf2319285eccdb1565e", + "severity": "major", + "location": { + "path": "with space/and unicode 😃/project/folder with unicode 😃/file name with \"spaces\" and unicode 😃.php", + "lines": { + "begin": 4 + } + } + }, + { + "description": "Bar\nBar2", + "fingerprint": "52d22d9e64bd6c6257b7a0d170ed8c99482043aeedd68c52bac081a80da9800a", + "severity": "major", + "location": { + "path": "with space/and unicode \ud83d\ude03/project/foo.php", + "lines": { + "begin": 0 + } + } + }, + { + "description": "Foo", + "fingerprint": "93c79740ed8c6fbaac2087e54d6f6f67fc0918e3ff77840530f32e19857ef63c", + "severity": "major", + "location": { + "path": "with space/and unicode \ud83d\ude03/project/foo.php", + "lines": { + "begin": 1 + } + } + }, + { + "description": "Bar\nBar2", + "fingerprint": "829f6c782152fdac840b39208c5b519d18e51bff2c601b6197812fffb8bcd9ed", + "severity": "major", + "location": { + "path": "with space/and unicode \ud83d\ude03/project/foo.php", + "lines": { + "begin": 5 + } + } + } +]', + ]; + yield [ 'Multiple generic errors', 1, From 6a33de94e447fdfdb8a67ac374e971dc2673fc5a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 15 Aug 2021 10:46:42 +0200 Subject: [PATCH 0097/1284] Fix inferring template type from non-empty-string --- src/Type/Constant/ConstantStringType.php | 6 +- src/Type/Generic/TemplateTypeHelper.php | 5 ++ .../Analyser/NodeScopeResolverTest.php | 1 + tests/PHPStan/Analyser/data/generics.php | 8 +- .../CallToFunctionParametersRuleTest.php | 8 +- .../Rules/Methods/CallMethodsRuleTest.php | 17 +++++ tests/PHPStan/Rules/Methods/data/bug-5372.php | 75 +++++++++++++++++++ 7 files changed, 114 insertions(+), 6 deletions(-) create mode 100644 tests/PHPStan/Rules/Methods/data/bug-5372.php diff --git a/src/Type/Constant/ConstantStringType.php b/src/Type/Constant/ConstantStringType.php index d2e45eff54..77e2a5b1e0 100644 --- a/src/Type/Constant/ConstantStringType.php +++ b/src/Type/Constant/ConstantStringType.php @@ -298,7 +298,11 @@ public function append(self $otherString): self public function generalize(?GeneralizePrecision $precision = null): Type { if ($this->isClassString) { - return new ClassStringType(); + if ($precision !== null && $precision->isMoreSpecific()) { + return new ClassStringType(); + } + + return new StringType(); } if ($this->getValue() !== '' && $precision !== null && $precision->isMoreSpecific()) { diff --git a/src/Type/Generic/TemplateTypeHelper.php b/src/Type/Generic/TemplateTypeHelper.php index a466b8f9ed..21fe7664c4 100644 --- a/src/Type/Generic/TemplateTypeHelper.php +++ b/src/Type/Generic/TemplateTypeHelper.php @@ -6,6 +6,7 @@ use PHPStan\Type\ConstantType; use PHPStan\Type\ErrorType; use PHPStan\Type\GeneralizePrecision; +use PHPStan\Type\StringType; use PHPStan\Type\Type; use PHPStan\Type\TypeTraverser; @@ -70,6 +71,10 @@ public static function generalizeType(Type $type): Type return $type->generalize(GeneralizePrecision::lessSpecific()); } + if ($type->isNonEmptyString()->yes()) { + return new StringType(); + } + return $traverse($type); }); } diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index d8344c689f..41fab64c49 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -447,6 +447,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4970.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5322.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/splfixedarray-iterator-types.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/bug-5372.php'); } /** diff --git a/tests/PHPStan/Analyser/data/generics.php b/tests/PHPStan/Analyser/data/generics.php index 17a5b9e3ca..672237b853 100644 --- a/tests/PHPStan/Analyser/data/generics.php +++ b/tests/PHPStan/Analyser/data/generics.php @@ -147,9 +147,15 @@ function f($a, $b) */ function testF($arrayOfInt, $callableOrNull) { - assertType('array', f($arrayOfInt, function (int $a): string { + assertType('Closure(int): string&numeric', function (int $a): string { + return (string)$a; + }); + assertType('array', f($arrayOfInt, function (int $a): string { return (string)$a; })); + assertType('Closure(mixed): string', function ($a): string { + return (string)$a; + }); assertType('array', f($arrayOfInt, function ($a): string { return (string)$a; })); diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index f63cbdbeb2..2285c6d271 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -563,11 +563,11 @@ public function testArrayReduceCallback(): void 5, ], [ - 'Parameter #2 $callback of function array_reduce expects callable(non-empty-string|null, int): non-empty-string|null, Closure(string, int): non-empty-string given.', + 'Parameter #2 $callback of function array_reduce expects callable(string|null, int): string|null, Closure(string, int): non-empty-string given.', 13, ], [ - 'Parameter #2 $callback of function array_reduce expects callable(non-empty-string|null, int): non-empty-string|null, Closure(string, int): non-empty-string given.', + 'Parameter #2 $callback of function array_reduce expects callable(string|null, int): string|null, Closure(string, int): non-empty-string given.', 22, ], ]); @@ -584,11 +584,11 @@ public function testArrayReduceArrowFunctionCallback(): void 5, ], [ - 'Parameter #2 $callback of function array_reduce expects callable(non-empty-string|null, int): non-empty-string|null, Closure(string, int): non-empty-string given.', + 'Parameter #2 $callback of function array_reduce expects callable(string|null, int): string|null, Closure(string, int): non-empty-string given.', 11, ], [ - 'Parameter #2 $callback of function array_reduce expects callable(non-empty-string|null, int): non-empty-string|null, Closure(string, int): non-empty-string given.', + 'Parameter #2 $callback of function array_reduce expects callable(string|null, int): string|null, Closure(string, int): non-empty-string given.', 18, ], ]); diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 2ff8deac41..1548d92209 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -2029,4 +2029,21 @@ public function testNonEmptyStringVerbosity(): void ]); } + public function testBug5372(): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { + $this->markTestSkipped('Test requires PHP 7.4.'); + } + + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/bug-5372.php'], [ + [ + 'Parameter #1 $list of method Bug5372\Foo::takesStrings() expects Bug5372\Collection, Bug5372\Collection given.', + 72, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-5372.php b/tests/PHPStan/Rules/Methods/data/bug-5372.php new file mode 100644 index 0000000000..21edc02fae --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-5372.php @@ -0,0 +1,75 @@ += 7.4 + +namespace Bug5372; + +use function PHPStan\Testing\assertType; + +/** + * @template TKey of array-key + * @template T + */ +class Collection +{ + + /** @var array */ + private $values; + + /** + * @param array $values + */ + public function __construct(array $values) + { + $this->values = $values; + } + + /** + * @template V + * + * @param callable(T): V $callback + * + * @return self + */ + public function map(callable $callback): self { + return new self(array_map($callback, $this->values)); + } + + /** + * @template V of string + * + * @param callable(T): V $callback + * + * @return self + */ + public function map2(callable $callback): self { + return new self(array_map($callback, $this->values)); + } +} + +class Foo +{ + + /** @param Collection $list */ + function takesStrings(Collection $list): void { + echo serialize($list); + } + + /** @param class-string $classString */ + public function doFoo(string $classString) + { + $col = new Collection(['foo', 'bar']); + assertType('Bug5372\Collection', $col); + + $newCol = $col->map(static fn(string $var): string => $var . 'bar'); + assertType('Bug5372\Collection', $newCol); + $this->takesStrings($newCol); + + $newCol = $col->map(static fn(string $var): string => $classString); + assertType('Bug5372\Collection', $newCol); + $this->takesStrings($newCol); + + $newCol = $col->map2(static fn(string $var): string => $classString); + assertType('Bug5372\Collection', $newCol); + $this->takesStrings($newCol); + } + +} From aa9e2e8f4969ea89976e5781f0381b8201944b55 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 15 Aug 2021 11:40:28 +0200 Subject: [PATCH 0098/1284] AppendedArrayKeyTypeRule - more precise type description --- src/Rules/Arrays/AppendedArrayKeyTypeRule.php | 3 ++- .../Arrays/AppendedArrayKeyTypeRuleTest.php | 8 ++++++++ .../Rules/Arrays/data/appended-array-key.php | 18 ++++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/Rules/Arrays/AppendedArrayKeyTypeRule.php b/src/Rules/Arrays/AppendedArrayKeyTypeRule.php index 1dd50f00c1..792e3b436b 100644 --- a/src/Rules/Arrays/AppendedArrayKeyTypeRule.php +++ b/src/Rules/Arrays/AppendedArrayKeyTypeRule.php @@ -76,10 +76,11 @@ public function processNode(\PhpParser\Node $node, Scope $scope): array } if (!$arrayType->getIterableKeyType()->isSuperTypeOf($keyType)->yes()) { + $verbosity = VerbosityLevel::getRecommendedLevelByType($arrayType->getIterableKeyType(), $keyType); return [ RuleErrorBuilder::message(sprintf( 'Array (%s) does not accept key %s.', - $arrayType->describe(VerbosityLevel::typeOnly()), + $arrayType->describe($verbosity), $keyType->describe(VerbosityLevel::value()) ))->build(), ]; diff --git a/tests/PHPStan/Rules/Arrays/AppendedArrayKeyTypeRuleTest.php b/tests/PHPStan/Rules/Arrays/AppendedArrayKeyTypeRuleTest.php index 502df29bc4..8804531ec5 100644 --- a/tests/PHPStan/Rules/Arrays/AppendedArrayKeyTypeRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/AppendedArrayKeyTypeRuleTest.php @@ -45,6 +45,14 @@ public function testRule(): void 'Array (array) does not accept key 1.', 46, ], + [ + 'Array (array<1|2|3, string>) does not accept key int.', + 80, + ], + [ + 'Array (array<1|2|3, string>) does not accept key 4.', + 85, + ], ]); } diff --git a/tests/PHPStan/Rules/Arrays/data/appended-array-key.php b/tests/PHPStan/Rules/Arrays/data/appended-array-key.php index 3381fabab9..650e62e736 100644 --- a/tests/PHPStan/Rules/Arrays/data/appended-array-key.php +++ b/tests/PHPStan/Rules/Arrays/data/appended-array-key.php @@ -68,3 +68,21 @@ public function doBar() } } + +class MorePreciseKey +{ + + /** @var array<1|2|3, string> */ + private $test; + + public function doFoo(int $i): void + { + $this->test[$i] = 'foo'; + } + + public function doBar(): void + { + $this->test[4] = 'foo'; + } + +} From 7172e5fc42154dbc73e78068bf12dbe3e27f9af5 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 15 Aug 2021 11:48:54 +0200 Subject: [PATCH 0099/1284] Fix casting non-empty-string array key type --- src/Type/ArrayType.php | 2 +- .../Analyser/NodeScopeResolverTest.php | 6 ++++- tests/PHPStan/Analyser/data/bug-5219.php | 2 +- .../Analyser/data/non-empty-string.php | 6 ++--- .../Arrays/AppendedArrayKeyTypeRuleTest.php | 5 ++++ .../PHPStan/Rules/Arrays/data/bug-5372_2.php | 23 +++++++++++++++++++ 6 files changed, 38 insertions(+), 6 deletions(-) create mode 100644 tests/PHPStan/Rules/Arrays/data/bug-5372_2.php diff --git a/src/Type/ArrayType.php b/src/Type/ArrayType.php index 1696294001..48ffdd6e15 100644 --- a/src/Type/ArrayType.php +++ b/src/Type/ArrayType.php @@ -317,7 +317,7 @@ public static function castToArrayKeyType(Type $offsetType): Type return new IntegerType(); } - if ($offsetType instanceof StringType) { + if ($offsetType instanceof StringType || $offsetType->isNonEmptyString()->yes()) { return $offsetType; } diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 41fab64c49..a63b4f8c95 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -447,7 +447,11 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4970.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5322.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/splfixedarray-iterator-types.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/bug-5372.php'); + + if (PHP_VERSION_ID >= 70400 || self::$useStaticReflectionProvider) { + yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/bug-5372.php'); + } + yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Arrays/data/bug-5372_2.php'); } /** diff --git a/tests/PHPStan/Analyser/data/bug-5219.php b/tests/PHPStan/Analyser/data/bug-5219.php index c79e9f59a2..ab75d306a5 100644 --- a/tests/PHPStan/Analyser/data/bug-5219.php +++ b/tests/PHPStan/Analyser/data/bug-5219.php @@ -12,7 +12,7 @@ protected function foo(string $message): void $header = sprintf('%s-%s', '', implode('-', ['x'])); assertType('non-empty-string', $header); - assertType('array&nonEmpty', [$header => $message]); + assertType('array&nonEmpty', [$header => $message]); } protected function bar(string $message): void diff --git a/tests/PHPStan/Analyser/data/non-empty-string.php b/tests/PHPStan/Analyser/data/non-empty-string.php index 457229c9ba..a951ca8af0 100644 --- a/tests/PHPStan/Analyser/data/non-empty-string.php +++ b/tests/PHPStan/Analyser/data/non-empty-string.php @@ -282,7 +282,7 @@ public function doFoo(array $a, string $s): void $a[$s] = 2; // there might be non-empty-string that becomes a number instead - assertType('array&nonEmpty', $a); + assertType('array&nonEmpty', $a); } /** @@ -309,12 +309,12 @@ public function doFoo(string $s, string $nonEmpty, int $i) assertType('non-empty-string', addslashes($nonEmpty)); assertType('string', addcslashes($s)); assertType('non-empty-string', addcslashes($nonEmpty)); - + assertType('string', escapeshellarg($s)); assertType('non-empty-string', escapeshellarg($nonEmpty)); assertType('string', escapeshellcmd($s)); assertType('non-empty-string', escapeshellcmd($nonEmpty)); - + assertType('string', strtoupper($s)); assertType('non-empty-string', strtoupper($nonEmpty)); assertType('string', strtolower($s)); diff --git a/tests/PHPStan/Rules/Arrays/AppendedArrayKeyTypeRuleTest.php b/tests/PHPStan/Rules/Arrays/AppendedArrayKeyTypeRuleTest.php index 8804531ec5..6bdc105a48 100644 --- a/tests/PHPStan/Rules/Arrays/AppendedArrayKeyTypeRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/AppendedArrayKeyTypeRuleTest.php @@ -56,4 +56,9 @@ public function testRule(): void ]); } + public function testBug5372Two(): void + { + $this->analyse([__DIR__ . '/data/bug-5372_2.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Arrays/data/bug-5372_2.php b/tests/PHPStan/Rules/Arrays/data/bug-5372_2.php new file mode 100644 index 0000000000..5e6caebd42 --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/bug-5372_2.php @@ -0,0 +1,23 @@ + */ + private $map = []; + + /** + * @param array $values + */ + public function __construct(array $values) + { + assertType('array', $values); + foreach ($values as $v) { + assertType('non-empty-string', $v); + $this->map[$v] = 'whatever'; + } + } +} From a862042f92aa5843920eb98ee262f068c2f3e796 Mon Sep 17 00:00:00 2001 From: Jakub Trmota Date: Mon, 9 Aug 2021 18:19:55 +0200 Subject: [PATCH 0100/1284] fix return type for gzeof() --- resources/functionMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 7b25d35560..b0e289435e 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -3727,7 +3727,7 @@ 'gzdecode' => ['string|false', 'data'=>'string', 'length='=>'int'], 'gzdeflate' => ['string|false', 'data'=>'string', 'level='=>'int', 'encoding='=>'int'], 'gzencode' => ['string|false', 'data'=>'string', 'level='=>'int', 'encoding_mode='=>'int'], -'gzeof' => ['int', 'zp'=>'resource'], +'gzeof' => ['bool', 'zp'=>'resource'], 'gzfile' => ['array|false', 'filename'=>'string', 'use_include_path='=>'int'], 'gzgetc' => ['string|false', 'zp'=>'resource'], 'gzgets' => ['string|false', 'zp'=>'resource', 'length='=>'int'], From cc06de9fc22f04e1040576e93cda053a69b69799 Mon Sep 17 00:00:00 2001 From: Simon Date: Mon, 9 Aug 2021 21:45:51 +0200 Subject: [PATCH 0101/1284] Revert "Skip ErrorFormatterTestCase on Windows & PHP 8" This reverts commit c42d29e2f0c1c2f6a28f16d4885f5a06efb35562. The upstream bug is fixed: - https://github.com/symfony/symfony/issues/37508 - https://github.com/php/php-src/pull/5863 --- src/Testing/ErrorFormatterTestCase.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Testing/ErrorFormatterTestCase.php b/src/Testing/ErrorFormatterTestCase.php index d80ae4d44d..94cf71da56 100644 --- a/src/Testing/ErrorFormatterTestCase.php +++ b/src/Testing/ErrorFormatterTestCase.php @@ -22,9 +22,6 @@ abstract class ErrorFormatterTestCase extends \PHPStan\Testing\TestCase private function getOutputStream(): StreamOutput { - if (PHP_VERSION_ID >= 80000 && DIRECTORY_SEPARATOR === '\\') { - $this->markTestSkipped('Skipped because of https://github.com/symfony/symfony/issues/37508'); - } if ($this->outputStream === null) { $resource = fopen('php://memory', 'w', false); if ($resource === false) { From c8c371bea40d8eb6956611a65906e756e1e33a05 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 15 Aug 2021 15:34:26 +0200 Subject: [PATCH 0102/1284] Regression test Closes https://github.com/phpstan/phpstan/issues/4842 --- ...nexistentOffsetInArrayDimFetchRuleTest.php | 5 +++ tests/PHPStan/Rules/Arrays/data/bug-4842.php | 31 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 tests/PHPStan/Rules/Arrays/data/bug-4842.php diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index a8f34fdc8d..9c042af244 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -298,4 +298,9 @@ public function testBug3700(): void $this->analyse([__DIR__ . '/data/bug-3700.php'], []); } + public function testBug4842(): void + { + $this->analyse([__DIR__ . '/data/bug-4842.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Arrays/data/bug-4842.php b/tests/PHPStan/Rules/Arrays/data/bug-4842.php new file mode 100644 index 0000000000..5230a347f9 --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/bug-4842.php @@ -0,0 +1,31 @@ +mappings = $mappings; + } + + /** + * @param "21021200"|"asd" $code + */ + function foo(string $code): string + { + if (isset($this->mappings[$code])) { + return (string)$this->mappings[$code]; + } + + throw new \RuntimeException(); + } +} From 353367c543ffec4b8754397444ed8fd09e9ef1bb Mon Sep 17 00:00:00 2001 From: Lctrs Date: Sat, 31 Jul 2021 01:06:03 +0200 Subject: [PATCH 0103/1284] Cover non-empty-array in array_map() --- .../ArrayMapFunctionReturnTypeExtension.php | 18 ++++-- .../Analyser/NodeScopeResolverTest.php | 1 + tests/PHPStan/Analyser/data/array-map.php | 62 +++++++++++++++++++ 3 files changed, 75 insertions(+), 6 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/array-map.php diff --git a/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php b/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php index e21cdbffe0..38db53df30 100644 --- a/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php @@ -6,6 +6,7 @@ use PHPStan\Analyser\Scope; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\ParametersAcceptorSelector; +use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\ArrayType; use PHPStan\Type\Constant\ConstantArrayTypeBuilder; use PHPStan\Type\MixedType; @@ -37,6 +38,10 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, )->getReturnType(); } + $mappedArrayType = new ArrayType( + new MixedType(), + $valueType + ); $arrayType = $scope->getType($functionCall->args[1]->value); $constantArrays = TypeUtils::getConstantArrays($arrayType); if (count($constantArrays) > 0) { @@ -52,18 +57,19 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $arrayTypes[] = $returnedArrayBuilder->getArray(); } - return TypeCombinator::union(...$arrayTypes); + $mappedArrayType = TypeCombinator::union(...$arrayTypes); } elseif ($arrayType->isArray()->yes()) { - return TypeCombinator::intersect(new ArrayType( + $mappedArrayType = TypeCombinator::intersect(new ArrayType( $arrayType->getIterableKeyType(), $valueType ), ...TypeUtils::getAccessoryTypes($arrayType)); } - return new ArrayType( - new MixedType(), - $valueType - ); + if ($arrayType->isIterableAtLeastOnce()->yes()) { + $mappedArrayType = TypeCombinator::intersect($mappedArrayType, new NonEmptyArrayType()); + } + + return $mappedArrayType; } } diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index a63b4f8c95..81fa51bee6 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -265,6 +265,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/offset-value-after-assign.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2112.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/array-flip.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/array-map.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/array-map-closure.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/array-sum.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4573.php'); diff --git a/tests/PHPStan/Analyser/data/array-map.php b/tests/PHPStan/Analyser/data/array-map.php new file mode 100644 index 0000000000..97767fb116 --- /dev/null +++ b/tests/PHPStan/Analyser/data/array-map.php @@ -0,0 +1,62 @@ + $array + */ +function foo(array $array): void { + $mapped = array_map( + static function(string $string): string { + return (string) $string; + }, + $array + ); + + assertType('array', $mapped); +} + +/** + * @param non-empty-array $array + */ +function foo2(array $array): void { + $mapped = array_map( + static function(string $string): string { + return (string) $string; + }, + $array + ); + + assertType('array&nonEmpty', $mapped); +} + +/** + * @param list $array + */ +function foo3(array $array): void { + $mapped = array_map( + static function(string $string): string { + return (string) $string; + }, + $array + ); + + assertType('array', $mapped); +} + +/** + * @param non-empty-list $array + */ +function foo4(array $array): void { + $mapped = array_map( + static function(string $string): string { + return (string) $string; + }, + $array + ); + + assertType('array&nonEmpty', $mapped); +} From 853ef3f6171a47f6a8cd8aeaeef700c8ef1ecea6 Mon Sep 17 00:00:00 2001 From: Jean-Luc Herren Date: Thu, 12 Aug 2021 10:48:43 +0200 Subject: [PATCH 0104/1284] Fix openssl_seal()'s $iv parameter The $iv parameter is an output-only parameter, the value passed in is not used. --- resources/functionMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index b0e289435e..67ddd3f695 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -8116,7 +8116,7 @@ 'openssl_public_decrypt' => ['bool', 'data'=>'string', '&w_decrypted'=>'string', 'key'=>'string|resource', 'padding='=>'int'], 'openssl_public_encrypt' => ['bool', 'data'=>'string', '&w_crypted'=>'string', 'key'=>'string|resource', 'padding='=>'int'], 'openssl_random_pseudo_bytes' => ['string|false', 'length'=>'int', '&w_crypto_strong='=>'bool'], -'openssl_seal' => ['int|false', 'data'=>'string', '&w_sealed_data'=>'string', '&w_env_keys'=>'array', 'pub_key_ids'=>'array', 'method='=>'string', '&rw_iv='=>'string'], +'openssl_seal' => ['int|false', 'data'=>'string', '&w_sealed_data'=>'string', '&w_env_keys'=>'array', 'pub_key_ids'=>'array', 'method='=>'string', '&w_iv='=>'string'], 'openssl_sign' => ['bool', 'data'=>'string', '&w_signature'=>'string', 'priv_key_id'=>'resource|string', 'signature_alg='=>'int|string'], 'openssl_spki_export' => ['string|null|false', 'spkac'=>'string'], 'openssl_spki_export_challenge' => ['string|null|false', 'spkac'=>'string'], From cee6f684d0e57091435dfb03ce7d597c438fe29a Mon Sep 17 00:00:00 2001 From: Trevor Rowbotham Date: Mon, 8 Mar 2021 16:07:07 -0500 Subject: [PATCH 0105/1284] Add dynamic return type extension for mb_substitute_character --- conf/config.neon | 5 + src/Php/PhpVersion.php | 25 ++++ ...uteCharacterDynamicReturnTypeExtension.php | 139 ++++++++++++++++++ .../Analyser/NodeScopeResolverTest.php | 8 + .../data/mb_substitute_character-php71.php | 21 +++ .../data/mb_substitute_character-php8.php | 21 +++ .../Analyser/data/mb_substitute_character.php | 21 +++ 7 files changed, 240 insertions(+) create mode 100644 src/Type/Php/MbSubstituteCharacterDynamicReturnTypeExtension.php create mode 100644 tests/PHPStan/Analyser/data/mb_substitute_character-php71.php create mode 100644 tests/PHPStan/Analyser/data/mb_substitute_character-php8.php create mode 100644 tests/PHPStan/Analyser/data/mb_substitute_character.php diff --git a/conf/config.neon b/conf/config.neon index 63806b1850..c06a1f887d 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1253,6 +1253,11 @@ services: tags: - phpstan.broker.dynamicFunctionReturnTypeExtension + - + class: PHPStan\Type\Php\MbSubstituteCharacterDynamicReturnTypeExtension + tags: + - phpstan.broker.dynamicFunctionReturnTypeExtension + - class: PHPStan\Type\Php\MicrotimeFunctionReturnTypeExtension tags: diff --git a/src/Php/PhpVersion.php b/src/Php/PhpVersion.php index a43b6d7d79..a8090db932 100644 --- a/src/Php/PhpVersion.php +++ b/src/Php/PhpVersion.php @@ -97,9 +97,34 @@ public function throwsTypeErrorForInternalFunctions(): bool return $this->versionId >= 80000; } + public function throwsValueErrorForInternalFunctions(): bool + { + return $this->versionId >= 80000; + } + public function supportsHhPrintfSpecifier(): bool { return $this->versionId >= 80000; } + public function isEmptyStringValidAliasForNoneInMbSubstituteCharacter(): bool + { + return $this->versionId < 80000; + } + + public function supportsAllUnicodeScalarCodePointsInMbSubstituteCharacter(): bool + { + return $this->versionId >= 70200; + } + + public function isNumericStringValidArgInMbSubstituteCharacter(): bool + { + return $this->versionId < 80000; + } + + public function isNullValidArgInMbSubstituteCharacter(): bool + { + return $this->versionId >= 80000; + } + } diff --git a/src/Type/Php/MbSubstituteCharacterDynamicReturnTypeExtension.php b/src/Type/Php/MbSubstituteCharacterDynamicReturnTypeExtension.php new file mode 100644 index 0000000000..ae3352a29a --- /dev/null +++ b/src/Type/Php/MbSubstituteCharacterDynamicReturnTypeExtension.php @@ -0,0 +1,139 @@ +phpVersion = $phpVersion; + } + + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return $functionReflection->getName() === 'mb_substitute_character'; + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + $minCodePoint = $this->phpVersion->getVersionId() < 80000 ? 1 : 0; + $maxCodePoint = $this->phpVersion->supportsAllUnicodeScalarCodePointsInMbSubstituteCharacter() ? 0x10FFFF : 0xFFFE; + $ranges = []; + + if ($this->phpVersion->supportsAllUnicodeScalarCodePointsInMbSubstituteCharacter()) { + // Surrogates aren't valid in PHP 7.2+ + $ranges[] = IntegerRangeType::fromInterval($minCodePoint, 0xD7FF); + $ranges[] = IntegerRangeType::fromInterval(0xE000, $maxCodePoint); + } else { + $ranges[] = IntegerRangeType::fromInterval($minCodePoint, $maxCodePoint); + } + + if (!isset($functionCall->args[0])) { + return TypeCombinator::union( + new ConstantStringType('none'), + new ConstantStringType('long'), + new ConstantStringType('entity'), + ...$ranges + ); + } + + $argType = $scope->getType($functionCall->args[0]->value); + $isString = (new StringType())->isSuperTypeOf($argType); + $isNull = (new NullType())->isSuperTypeOf($argType); + $isInteger = (new IntegerType())->isSuperTypeOf($argType); + + if ($isString->no() && $isNull->no() && $isInteger->no()) { + if ($this->phpVersion->throwsTypeErrorForInternalFunctions()) { + return new NeverType(); + } + + return new BooleanType(); + } + + if ($isInteger->yes()) { + $invalidRanges = []; + + foreach ($ranges as $range) { + $isInRange = $range->isSuperTypeOf($argType); + + if ($isInRange->yes()) { + return new ConstantBooleanType(true); + } + + $invalidRanges[] = $isInRange->no(); + } + + if ($argType instanceof ConstantIntegerType || !in_array(false, $invalidRanges, true)) { + if ($this->phpVersion->throwsValueErrorForInternalFunctions()) { + return new NeverType(); + } + + return new ConstantBooleanType(false); + } + } elseif ($isString->yes()) { + if ($argType->isNonEmptyString()->no()) { + // The empty string was a valid alias for "none" in PHP < 8. + if ($this->phpVersion->isEmptyStringValidAliasForNoneInMbSubstituteCharacter()) { + return new ConstantBooleanType(true); + } + + return new NeverType(); + } + + if (!$this->phpVersion->isNumericStringValidArgInMbSubstituteCharacter() && $argType->isNumericString()->yes()) { + return new NeverType(); + } + + if ($argType instanceof ConstantStringType) { + $value = strtolower($argType->getValue()); + + if ($value === 'none' || $value === 'long' || $value === 'entity') { + return new ConstantBooleanType(true); + } + + if ($argType->isNumericString()->yes()) { + $codePoint = (int) $value; + $isValid = $codePoint >= $minCodePoint && $codePoint <= $maxCodePoint; + + if ($this->phpVersion->supportsAllUnicodeScalarCodePointsInMbSubstituteCharacter()) { + $isValid = $isValid && ($codePoint < 0xD800 || $codePoint > 0xDFFF); + } + + return new ConstantBooleanType($isValid); + } + + if ($this->phpVersion->throwsValueErrorForInternalFunctions()) { + return new NeverType(); + } + + return new ConstantBooleanType(false); + } + } elseif ($isNull->yes()) { + // The $substitute_character arg is nullable in PHP 8+ + return new ConstantBooleanType($this->phpVersion->isNullValidArgInMbSubstituteCharacter()); + } + + return new BooleanType(); + } + +} diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 81fa51bee6..38df6cf224 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -453,6 +453,14 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/bug-5372.php'); } yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Arrays/data/bug-5372_2.php'); + + if (PHP_VERSION_ID >= 80000) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/mb_substitute_character-php8.php'); + } elseif (PHP_VERSION_ID < 70200) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/mb_substitute_character-php71.php'); + } else { + yield from $this->gatherAssertTypes(__DIR__ . '/data/mb_substitute_character.php'); + } } /** diff --git a/tests/PHPStan/Analyser/data/mb_substitute_character-php71.php b/tests/PHPStan/Analyser/data/mb_substitute_character-php71.php new file mode 100644 index 0000000000..0ba0e9ab4e --- /dev/null +++ b/tests/PHPStan/Analyser/data/mb_substitute_character-php71.php @@ -0,0 +1,21 @@ +', mb_substitute_character()); +\PHPStan\Testing\assertType('true', mb_substitute_character('')); +\PHPStan\Testing\assertType('false', mb_substitute_character(null)); +\PHPStan\Testing\assertType('true', mb_substitute_character('none')); +\PHPStan\Testing\assertType('true', mb_substitute_character('long')); +\PHPStan\Testing\assertType('true', mb_substitute_character('entity')); +\PHPStan\Testing\assertType('false', mb_substitute_character('foo')); +\PHPStan\Testing\assertType('true', mb_substitute_character('123')); +\PHPStan\Testing\assertType('true', mb_substitute_character('123.4')); +\PHPStan\Testing\assertType('true', mb_substitute_character(0xFFFD)); +\PHPStan\Testing\assertType('false', mb_substitute_character(0x10FFFF)); +\PHPStan\Testing\assertType('false', mb_substitute_character(-1)); +\PHPStan\Testing\assertType('false', mb_substitute_character(0x110000)); +\PHPStan\Testing\assertType('bool', mb_substitute_character($undefined)); +\PHPStan\Testing\assertType('bool', mb_substitute_character(new stdClass())); +\PHPStan\Testing\assertType('bool', mb_substitute_character(function () {})); +\PHPStan\Testing\assertType('true', mb_substitute_character(rand(0xD800, 0xDFFF))); +\PHPStan\Testing\assertType('bool', mb_substitute_character(rand(0, 0xDFFF))); +\PHPStan\Testing\assertType('bool', mb_substitute_character(rand(0xD800, 0x10FFFF))); diff --git a/tests/PHPStan/Analyser/data/mb_substitute_character-php8.php b/tests/PHPStan/Analyser/data/mb_substitute_character-php8.php new file mode 100644 index 0000000000..b53353bd3a --- /dev/null +++ b/tests/PHPStan/Analyser/data/mb_substitute_character-php8.php @@ -0,0 +1,21 @@ +|int<57344, 1114111>', mb_substitute_character()); +\PHPStan\Testing\assertType('*NEVER*', mb_substitute_character('')); +\PHPStan\Testing\assertType('true', mb_substitute_character(null)); +\PHPStan\Testing\assertType('true', mb_substitute_character('none')); +\PHPStan\Testing\assertType('true', mb_substitute_character('long')); +\PHPStan\Testing\assertType('true', mb_substitute_character('entity')); +\PHPStan\Testing\assertType('*NEVER*', mb_substitute_character('foo')); +\PHPStan\Testing\assertType('*NEVER*', mb_substitute_character('123')); +\PHPStan\Testing\assertType('*NEVER*', mb_substitute_character('123.4')); +\PHPStan\Testing\assertType('true', mb_substitute_character(0xFFFD)); +\PHPStan\Testing\assertType('true', mb_substitute_character(0x10FFFF)); +\PHPStan\Testing\assertType('*NEVER*', mb_substitute_character(-1)); +\PHPStan\Testing\assertType('*NEVER*', mb_substitute_character(0x110000)); +\PHPStan\Testing\assertType('bool', mb_substitute_character($undefined)); +\PHPStan\Testing\assertType('*NEVER*', mb_substitute_character(new stdClass())); +\PHPStan\Testing\assertType('*NEVER*', mb_substitute_character(function () {})); +\PHPStan\Testing\assertType('*NEVER*', mb_substitute_character(rand(0xD800, 0xDFFF))); +\PHPStan\Testing\assertType('bool', mb_substitute_character(rand(0, 0xDFFF))); +\PHPStan\Testing\assertType('bool', mb_substitute_character(rand(0xD800, 0x10FFFF))); diff --git a/tests/PHPStan/Analyser/data/mb_substitute_character.php b/tests/PHPStan/Analyser/data/mb_substitute_character.php new file mode 100644 index 0000000000..9dab962ec5 --- /dev/null +++ b/tests/PHPStan/Analyser/data/mb_substitute_character.php @@ -0,0 +1,21 @@ +|int<57344, 1114111>', mb_substitute_character()); +\PHPStan\Testing\assertType('true', mb_substitute_character('')); +\PHPStan\Testing\assertType('false', mb_substitute_character(null)); +\PHPStan\Testing\assertType('true', mb_substitute_character('none')); +\PHPStan\Testing\assertType('true', mb_substitute_character('long')); +\PHPStan\Testing\assertType('true', mb_substitute_character('entity')); +\PHPStan\Testing\assertType('false', mb_substitute_character('foo')); +\PHPStan\Testing\assertType('true', mb_substitute_character('123')); +\PHPStan\Testing\assertType('true', mb_substitute_character('123.4')); +\PHPStan\Testing\assertType('true', mb_substitute_character(0xFFFD)); +\PHPStan\Testing\assertType('true', mb_substitute_character(0x10FFFF)); +\PHPStan\Testing\assertType('false', mb_substitute_character(-1)); +\PHPStan\Testing\assertType('false', mb_substitute_character(0x110000)); +\PHPStan\Testing\assertType('bool', mb_substitute_character($undefined)); +\PHPStan\Testing\assertType('bool', mb_substitute_character(new stdClass())); +\PHPStan\Testing\assertType('bool', mb_substitute_character(function () {})); +\PHPStan\Testing\assertType('false', mb_substitute_character(rand(0xD800, 0xDFFF))); +\PHPStan\Testing\assertType('bool', mb_substitute_character(rand(0, 0xDFFF))); +\PHPStan\Testing\assertType('bool', mb_substitute_character(rand(0xD800, 0x10FFFF))); From f7250dbe9b23415f02961edc5f34e3f084e2b659 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 16 Aug 2021 10:11:05 +0200 Subject: [PATCH 0106/1284] Fix TemplateTypeMap::isEmpty() --- src/Type/Generic/TemplateTypeMap.php | 4 ++-- tests/PHPStan/Type/Generic/TemplateTypeMapTest.php | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Type/Generic/TemplateTypeMap.php b/src/Type/Generic/TemplateTypeMap.php index 670b6a5f16..ec864bb171 100644 --- a/src/Type/Generic/TemplateTypeMap.php +++ b/src/Type/Generic/TemplateTypeMap.php @@ -65,12 +65,12 @@ public static function createEmpty(): self public function isEmpty(): bool { - return count($this->types) === 0; + return $this->count() === 0; } public function count(): int { - return count($this->types); + return count($this->types + $this->lowerBoundTypes); } /** @return array */ diff --git a/tests/PHPStan/Type/Generic/TemplateTypeMapTest.php b/tests/PHPStan/Type/Generic/TemplateTypeMapTest.php index 77c0c11b13..41ca7505c3 100644 --- a/tests/PHPStan/Type/Generic/TemplateTypeMapTest.php +++ b/tests/PHPStan/Type/Generic/TemplateTypeMapTest.php @@ -15,6 +15,11 @@ public function dataUnionWithLowerBoundTypes(): iterable 'T' => new ObjectType(\Exception::class), ]))->convertToLowerBoundTypes(); + yield [ + $map, + \Exception::class, + ]; + yield [ $map->union(new TemplateTypeMap([ 'T' => new ObjectType(\InvalidArgumentException::class), @@ -51,6 +56,7 @@ public function dataUnionWithLowerBoundTypes(): iterable /** @dataProvider dataUnionWithLowerBoundTypes */ public function testUnionWithLowerBoundTypes(TemplateTypeMap $map, string $expectedTDescription): void { + $this->assertFalse($map->isEmpty()); $t = $map->getType('T'); $this->assertNotNull($t); $this->assertSame($expectedTDescription, $t->describe(VerbosityLevel::precise())); From b932769213234a3ee78c39c7505ded5fade3568c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 16 Aug 2021 16:10:01 +0200 Subject: [PATCH 0107/1284] Support @var above class constants --- src/Analyser/MutatingScope.php | 23 ++++-- src/Analyser/NodeScopeResolver.php | 7 ++ src/PhpDoc/PhpDocBlock.php | 39 +++++++++- src/PhpDoc/PhpDocInheritanceResolver.php | 21 +++++ src/PhpDoc/StubPhpDocProvider.php | 42 ++++++++++ .../BetterReflectionProvider.php | 9 +++ src/Reflection/ClassConstantReflection.php | 16 +++- src/Reflection/ClassReflection.php | 43 +++++++++-- .../Runtime/RuntimeReflectionProvider.php | 7 ++ src/Testing/RuleTestCase.php | 2 + src/Testing/TestCase.php | 2 + src/Testing/TypeInferenceTestCase.php | 2 + tests/PHPStan/Analyser/AnalyserTest.php | 2 + .../Analyser/ClassConstantStubFileTest.php | 37 +++++++++ .../Analyser/NodeScopeResolverTest.php | 2 + tests/PHPStan/Analyser/classConstantStub.stub | 14 ++++ .../Analyser/classConstantStubFiles.neon | 3 + .../data/class-constant-stub-files.php | 20 +++++ .../Analyser/data/class-constant-types.php | 76 +++++++++++++++++++ tests/PHPStan/Broker/BrokerTest.php | 2 + .../Reflection/ClassReflectionTest.php | 10 ++- 21 files changed, 365 insertions(+), 14 deletions(-) create mode 100644 tests/PHPStan/Analyser/ClassConstantStubFileTest.php create mode 100644 tests/PHPStan/Analyser/classConstantStub.stub create mode 100644 tests/PHPStan/Analyser/classConstantStubFiles.neon create mode 100644 tests/PHPStan/Analyser/data/class-constant-stub-files.php create mode 100644 tests/PHPStan/Analyser/data/class-constant-types.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 5e2f28465e..05a5cd0338 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -28,6 +28,7 @@ use PhpParser\NodeFinder; use PHPStan\Node\ExecutionEndNode; use PHPStan\Parser\Parser; +use PHPStan\Reflection\ClassConstantReflection; use PHPStan\Reflection\ClassMemberReflection; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ConstantReflection; @@ -1890,7 +1891,8 @@ private function resolveType(Expr $node): Type if (strtolower($constantName) === 'class') { return new GenericClassStringType(new StaticType($this->getClassReflection())); } - return new MixedType(); + + $namesToResolve[] = 'static'; } } if (in_array(strtolower($constantClass), $namesToResolve, true)) { @@ -1926,15 +1928,26 @@ private function resolveType(Expr $node): Type continue; } - $propertyClassReflection = $this->reflectionProvider->getClass($referencedClass); - if (!$propertyClassReflection->hasConstant($constantName)) { + $constantClassReflection = $this->reflectionProvider->getClass($referencedClass); + if (!$constantClassReflection->hasConstant($constantName)) { continue; } - $constantType = $propertyClassReflection->getConstant($constantName)->getValueType(); + $constantReflection = $constantClassReflection->getConstant($constantName); + if ( + $constantReflection instanceof ClassConstantReflection + && $node->class instanceof Name + && strtolower((string) $node->class) === 'static' + && !$constantClassReflection->isFinal() + && !$constantReflection->hasPhpDocType() + ) { + return new MixedType(); + } + + $constantType = $constantReflection->getValueType(); if ( $constantType instanceof ConstantType - && in_array(sprintf('%s::%s', $propertyClassReflection->getName(), $constantName), $this->dynamicConstantNames, true) + && in_array(sprintf('%s::%s', $constantClassReflection->getName(), $constantName), $this->dynamicConstantNames, true) ) { $constantType = $constantType->generalize(GeneralizePrecision::lessSpecific()); } diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 6b9b4a664b..70d9306458 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -83,6 +83,7 @@ use PHPStan\Php\PhpVersion; use PHPStan\PhpDoc\PhpDocInheritanceResolver; use PHPStan\PhpDoc\ResolvedPhpDocBlock; +use PHPStan\PhpDoc\StubPhpDocProvider; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\MethodReflection; @@ -138,6 +139,8 @@ class NodeScopeResolver private \PHPStan\Type\FileTypeMapper $fileTypeMapper; + private StubPhpDocProvider $stubPhpDocProvider; + private PhpVersion $phpVersion; private \PHPStan\PhpDoc\PhpDocInheritanceResolver $phpDocInheritanceResolver; @@ -189,6 +192,7 @@ public function __construct( ClassReflectionExtensionRegistryProvider $classReflectionExtensionRegistryProvider, Parser $parser, FileTypeMapper $fileTypeMapper, + StubPhpDocProvider $stubPhpDocProvider, PhpVersion $phpVersion, PhpDocInheritanceResolver $phpDocInheritanceResolver, FileHelper $fileHelper, @@ -208,6 +212,7 @@ public function __construct( $this->classReflectionExtensionRegistryProvider = $classReflectionExtensionRegistryProvider; $this->parser = $parser; $this->fileTypeMapper = $fileTypeMapper; + $this->stubPhpDocProvider = $stubPhpDocProvider; $this->phpVersion = $phpVersion; $this->phpDocInheritanceResolver = $phpDocInheritanceResolver; $this->fileHelper = $fileHelper; @@ -1490,6 +1495,8 @@ private function createAstClassReflection(Node\Stmt\ClassLike $stmt, Scope $scop return new ClassReflection( $this->reflectionProvider, $this->fileTypeMapper, + $this->stubPhpDocProvider, + $this->phpDocInheritanceResolver, $this->phpVersion, $this->classReflectionExtensionRegistryProvider->getRegistry()->getPropertiesClassReflectionExtensions(), $this->classReflectionExtensionRegistryProvider->getRegistry()->getMethodsClassReflectionExtensions(), diff --git a/src/PhpDoc/PhpDocBlock.php b/src/PhpDoc/PhpDocBlock.php index 880e8a92b4..92fa123c71 100644 --- a/src/PhpDoc/PhpDocBlock.php +++ b/src/PhpDoc/PhpDocBlock.php @@ -144,6 +144,43 @@ public static function resolvePhpDocBlockForProperty( ); } + /** + * @param string|null $docComment + * @param \PHPStan\Reflection\ClassReflection $classReflection + * @param string|null $trait + * @param string $constantName + * @param string $file + * @param bool|null $explicit + * @param array $originalPositionalParameterNames + * @param array $newPositionalParameterNames + * @return self + */ + public static function resolvePhpDocBlockForConstant( + ?string $docComment, + ClassReflection $classReflection, + ?string $trait, // unused + string $constantName, + string $file, + ?bool $explicit, + array $originalPositionalParameterNames, // unused + array $newPositionalParameterNames // unused + ): self + { + return self::resolvePhpDocBlockTree( + $docComment, + $classReflection, + null, + $constantName, + $file, + 'hasConstant', + 'getConstant', + __FUNCTION__, + $explicit, + [], + [] + ); + } + /** * @param string|null $docComment * @param \PHPStan\Reflection\ClassReflection $classReflection @@ -336,7 +373,7 @@ private static function resolvePhpDocBlockFromClass( ): ?self { if ($classReflection->getFileNameWithPhpDocs() !== null && $classReflection->$hasMethodName($name)) { - /** @var \PHPStan\Reflection\PropertyReflection|\PHPStan\Reflection\MethodReflection $parentReflection */ + /** @var \PHPStan\Reflection\PropertyReflection|\PHPStan\Reflection\MethodReflection|\PHPStan\Reflection\ConstantReflection $parentReflection */ $parentReflection = $classReflection->$getMethodName($name); if ($parentReflection->isPrivate()) { return null; diff --git a/src/PhpDoc/PhpDocInheritanceResolver.php b/src/PhpDoc/PhpDocInheritanceResolver.php index 67c86af318..9ad0cea135 100644 --- a/src/PhpDoc/PhpDocInheritanceResolver.php +++ b/src/PhpDoc/PhpDocInheritanceResolver.php @@ -39,6 +39,27 @@ public function resolvePhpDocForProperty( return $this->docBlockTreeToResolvedDocBlock($phpDocBlock, $declaringTraitName, null); } + public function resolvePhpDocForConstant( + ?string $docComment, + ClassReflection $classReflection, + string $classReflectionFileName, + string $constantName + ): ResolvedPhpDocBlock + { + $phpDocBlock = PhpDocBlock::resolvePhpDocBlockForConstant( + $docComment, + $classReflection, + null, + $constantName, + $classReflectionFileName, + null, + [], + [] + ); + + return $this->docBlockTreeToResolvedDocBlock($phpDocBlock, null, null); + } + /** * @param string|null $docComment * @param string $fileName diff --git a/src/PhpDoc/StubPhpDocProvider.php b/src/PhpDoc/StubPhpDocProvider.php index 75ca491d10..c4c3619e84 100644 --- a/src/PhpDoc/StubPhpDocProvider.php +++ b/src/PhpDoc/StubPhpDocProvider.php @@ -27,6 +27,9 @@ class StubPhpDocProvider /** @var array> */ private array $propertyMap = []; + /** @var array> */ + private array $constantMap = []; + /** @var array> */ private array $methodMap = []; @@ -46,6 +49,9 @@ class StubPhpDocProvider /** @var array> */ private array $knownPropertiesDocComments = []; + /** @var array> */ + private array $knownConstantsDocComments = []; + /** @var array> */ private array $knownMethodsDocComments = []; @@ -122,6 +128,32 @@ public function findPropertyPhpDoc(string $className, string $propertyName): ?Re return null; } + public function findClassConstantPhpDoc(string $className, string $constantName): ?ResolvedPhpDocBlock + { + if (!$this->isKnownClass($className)) { + return null; + } + + if (array_key_exists($constantName, $this->constantMap[$className])) { + return $this->constantMap[$className][$constantName]; + } + + if (array_key_exists($constantName, $this->knownConstantsDocComments[$className])) { + [$file, $docComment] = $this->knownConstantsDocComments[$className][$constantName]; + $this->constantMap[$className][$constantName] = $this->fileTypeMapper->getResolvedPhpDoc( + $file, + $className, + null, + null, + $docComment + ); + + return $this->constantMap[$className][$constantName]; + } + + return null; + } + /** * @param string $className * @param string $methodName @@ -306,7 +338,9 @@ private function initializeKnownElementNode(string $stubFile, Node $node): void $this->methodMap[$className] = []; $this->propertyMap[$className] = []; + $this->constantMap[$className] = []; $this->knownPropertiesDocComments[$className] = []; + $this->knownConstantsDocComments[$className] = []; $this->knownMethodsDocComments[$className] = []; foreach ($node->stmts as $stmt) { @@ -319,6 +353,14 @@ private function initializeKnownElementNode(string $stubFile, Node $node): void } $this->knownPropertiesDocComments[$className][$property->name->toString()] = [$stubFile, $docComment->getText()]; } + } elseif ($stmt instanceof Node\Stmt\ClassConst) { + foreach ($stmt->consts as $const) { + if ($docComment === null) { + $this->constantMap[$className][$const->name->toString()] = null; + continue; + } + $this->knownConstantsDocComments[$className][$const->name->toString()] = [$stubFile, $docComment->getText()]; + } } elseif ($stmt instanceof Node\Stmt\ClassMethod) { if ($docComment === null) { $this->methodMap[$className][$stmt->name->toString()] = null; diff --git a/src/Reflection/BetterReflection/BetterReflectionProvider.php b/src/Reflection/BetterReflection/BetterReflectionProvider.php index 35cf41a275..0e6d7a629d 100644 --- a/src/Reflection/BetterReflection/BetterReflectionProvider.php +++ b/src/Reflection/BetterReflection/BetterReflectionProvider.php @@ -21,6 +21,7 @@ use PHPStan\File\FileHelper; use PHPStan\File\RelativePathHelper; use PHPStan\Php\PhpVersion; +use PHPStan\PhpDoc\PhpDocInheritanceResolver; use PHPStan\PhpDoc\StubPhpDocProvider; use PHPStan\PhpDoc\Tag\ParamTag; use PHPStan\Reflection\ClassNameHelper; @@ -52,6 +53,8 @@ class BetterReflectionProvider implements ReflectionProvider private \PHPStan\Type\FileTypeMapper $fileTypeMapper; + private PhpDocInheritanceResolver $phpDocInheritanceResolver; + private PhpVersion $phpVersion; private \PHPStan\Reflection\SignatureMap\NativeFunctionReflectionProvider $nativeFunctionReflectionProvider; @@ -84,6 +87,7 @@ public function __construct( ClassReflectionExtensionRegistryProvider $classReflectionExtensionRegistryProvider, ClassReflector $classReflector, FileTypeMapper $fileTypeMapper, + PhpDocInheritanceResolver $phpDocInheritanceResolver, PhpVersion $phpVersion, NativeFunctionReflectionProvider $nativeFunctionReflectionProvider, StubPhpDocProvider $stubPhpDocProvider, @@ -101,6 +105,7 @@ public function __construct( $this->classReflectionExtensionRegistryProvider = $classReflectionExtensionRegistryProvider; $this->classReflector = $classReflector; $this->fileTypeMapper = $fileTypeMapper; + $this->phpDocInheritanceResolver = $phpDocInheritanceResolver; $this->phpVersion = $phpVersion; $this->nativeFunctionReflectionProvider = $nativeFunctionReflectionProvider; $this->stubPhpDocProvider = $stubPhpDocProvider; @@ -155,6 +160,8 @@ public function getClass(string $className): ClassReflection $classReflection = new ClassReflection( $this->reflectionProviderProvider->getReflectionProvider(), $this->fileTypeMapper, + $this->stubPhpDocProvider, + $this->phpDocInheritanceResolver, $this->phpVersion, $this->classReflectionExtensionRegistryProvider->getRegistry()->getPropertiesClassReflectionExtensions(), $this->classReflectionExtensionRegistryProvider->getRegistry()->getMethodsClassReflectionExtensions(), @@ -227,6 +234,8 @@ public function getAnonymousClassReflection(\PhpParser\Node\Stmt\Class_ $classNo self::$anonymousClasses[$className] = new ClassReflection( $this->reflectionProviderProvider->getReflectionProvider(), $this->fileTypeMapper, + $this->stubPhpDocProvider, + $this->phpDocInheritanceResolver, $this->phpVersion, $this->classReflectionExtensionRegistryProvider->getRegistry()->getPropertiesClassReflectionExtensions(), $this->classReflectionExtensionRegistryProvider->getRegistry()->getMethodsClassReflectionExtensions(), diff --git a/src/Reflection/ClassConstantReflection.php b/src/Reflection/ClassConstantReflection.php index cdebf0dc6b..c5f261ed83 100644 --- a/src/Reflection/ClassConstantReflection.php +++ b/src/Reflection/ClassConstantReflection.php @@ -13,6 +13,8 @@ class ClassConstantReflection implements ConstantReflection private \ReflectionClassConstant $reflection; + private ?Type $phpDocType; + private ?string $deprecatedDescription; private bool $isDeprecated; @@ -24,6 +26,7 @@ class ClassConstantReflection implements ConstantReflection public function __construct( ClassReflection $declaringClass, \ReflectionClassConstant $reflection, + ?Type $phpDocType, ?string $deprecatedDescription, bool $isDeprecated, bool $isInternal @@ -31,6 +34,7 @@ public function __construct( { $this->declaringClass = $declaringClass; $this->reflection = $reflection; + $this->phpDocType = $phpDocType; $this->deprecatedDescription = $deprecatedDescription; $this->isDeprecated = $isDeprecated; $this->isInternal = $isInternal; @@ -59,11 +63,21 @@ public function getValue() return $this->reflection->getValue(); } + public function hasPhpDocType(): bool + { + return $this->phpDocType !== null; + } + public function getValueType(): Type { if ($this->valueType === null) { - $this->valueType = ConstantTypeHelper::getTypeFromValue($this->getValue()); + if ($this->phpDocType === null) { + $this->valueType = ConstantTypeHelper::getTypeFromValue($this->getValue()); + } else { + $this->valueType = $this->phpDocType; + } } + return $this->valueType; } diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index 34534395c2..b17994c62a 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -5,7 +5,9 @@ use Attribute; use PHPStan\BetterReflection\Reflection\Adapter\ReflectionClass; use PHPStan\Php\PhpVersion; +use PHPStan\PhpDoc\PhpDocInheritanceResolver; use PHPStan\PhpDoc\ResolvedPhpDocBlock; +use PHPStan\PhpDoc\StubPhpDocProvider; use PHPStan\PhpDoc\Tag\ExtendsTag; use PHPStan\PhpDoc\Tag\ImplementsTag; use PHPStan\PhpDoc\Tag\MethodTag; @@ -36,6 +38,10 @@ class ClassReflection implements ReflectionWithFilename private \PHPStan\Type\FileTypeMapper $fileTypeMapper; + private StubPhpDocProvider $stubPhpDocProvider; + + private PhpDocInheritanceResolver $phpDocInheritanceResolver; + private PhpVersion $phpVersion; /** @var \PHPStan\Reflection\PropertiesClassReflectionExtension[] */ @@ -122,6 +128,8 @@ class ClassReflection implements ReflectionWithFilename public function __construct( ReflectionProvider $reflectionProvider, FileTypeMapper $fileTypeMapper, + StubPhpDocProvider $stubPhpDocProvider, + PhpDocInheritanceResolver $phpDocInheritanceResolver, PhpVersion $phpVersion, array $propertiesClassReflectionExtensions, array $methodsClassReflectionExtensions, @@ -135,6 +143,8 @@ public function __construct( { $this->reflectionProvider = $reflectionProvider; $this->fileTypeMapper = $fileTypeMapper; + $this->stubPhpDocProvider = $stubPhpDocProvider; + $this->phpDocInheritanceResolver = $phpDocInheritanceResolver; $this->phpVersion = $phpVersion; $this->propertiesClassReflectionExtensions = $propertiesClassReflectionExtensions; $this->methodsClassReflectionExtensions = $methodsClassReflectionExtensions; @@ -777,21 +787,40 @@ public function getConstant(string $name): ConstantReflection $deprecatedDescription = null; $isDeprecated = false; $isInternal = false; - $declaringClass = $reflectionConstant->getDeclaringClass(); + $declaringClass = $this->reflectionProvider->getClass($reflectionConstant->getDeclaringClass()->getName()); $fileName = $declaringClass->getFileName(); - if ($reflectionConstant->getDocComment() !== false && $fileName !== false) { - $docComment = $reflectionConstant->getDocComment(); - $className = $declaringClass->getName(); - $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc($fileName, $className, null, null, $docComment); + $phpDocType = null; + $resolvedPhpDoc = $this->stubPhpDocProvider->findClassConstantPhpDoc( + $declaringClass->getName(), + $name + ); + if ($resolvedPhpDoc === null && $fileName !== false) { + $docComment = null; + if ($reflectionConstant->getDocComment() !== false) { + $docComment = $reflectionConstant->getDocComment(); + } + $resolvedPhpDoc = $this->phpDocInheritanceResolver->resolvePhpDocForConstant( + $docComment, + $declaringClass, + $fileName, + $name + ); + } + if ($resolvedPhpDoc !== null) { $deprecatedDescription = $resolvedPhpDoc->getDeprecatedTag() !== null ? $resolvedPhpDoc->getDeprecatedTag()->getMessage() : null; $isDeprecated = $resolvedPhpDoc->isDeprecated(); $isInternal = $resolvedPhpDoc->isInternal(); + $varTags = $resolvedPhpDoc->getVarTags(); + if (isset($varTags[0]) && count($varTags) === 1) { + $phpDocType = $varTags[0]->getType(); + } } $this->constants[$name] = new ClassConstantReflection( - $this->reflectionProvider->getClass($declaringClass->getName()), + $declaringClass, $reflectionConstant, + $phpDocType, $deprecatedDescription, $isDeprecated, $isInternal @@ -1061,6 +1090,8 @@ public function withTypes(array $types): self return new self( $this->reflectionProvider, $this->fileTypeMapper, + $this->stubPhpDocProvider, + $this->phpDocInheritanceResolver, $this->phpVersion, $this->propertiesClassReflectionExtensions, $this->methodsClassReflectionExtensions, diff --git a/src/Reflection/Runtime/RuntimeReflectionProvider.php b/src/Reflection/Runtime/RuntimeReflectionProvider.php index 7696136a6f..d4f832d1ff 100644 --- a/src/Reflection/Runtime/RuntimeReflectionProvider.php +++ b/src/Reflection/Runtime/RuntimeReflectionProvider.php @@ -7,6 +7,7 @@ use PHPStan\BetterReflection\SourceLocator\SourceStubber\PhpStormStubsSourceStubber; use PHPStan\DependencyInjection\Reflection\ClassReflectionExtensionRegistryProvider; use PHPStan\Php\PhpVersion; +use PHPStan\PhpDoc\PhpDocInheritanceResolver; use PHPStan\PhpDoc\StubPhpDocProvider; use PHPStan\PhpDoc\Tag\ParamTag; use PHPStan\Reflection\ClassNameHelper; @@ -42,6 +43,8 @@ class RuntimeReflectionProvider implements ReflectionProvider private StubPhpDocProvider $stubPhpDocProvider; + private PhpDocInheritanceResolver $phpDocInheritanceResolver; + private PhpStormStubsSourceStubber $phpStormStubsSourceStubber; /** @var \PHPStan\Reflection\FunctionReflection[] */ @@ -61,6 +64,7 @@ public function __construct( ClassReflectionExtensionRegistryProvider $classReflectionExtensionRegistryProvider, FunctionReflectionFactory $functionReflectionFactory, FileTypeMapper $fileTypeMapper, + PhpDocInheritanceResolver $phpDocInheritanceResolver, PhpVersion $phpVersion, NativeFunctionReflectionProvider $nativeFunctionReflectionProvider, StubPhpDocProvider $stubPhpDocProvider, @@ -71,6 +75,7 @@ public function __construct( $this->classReflectionExtensionRegistryProvider = $classReflectionExtensionRegistryProvider; $this->functionReflectionFactory = $functionReflectionFactory; $this->fileTypeMapper = $fileTypeMapper; + $this->phpDocInheritanceResolver = $phpDocInheritanceResolver; $this->phpVersion = $phpVersion; $this->nativeFunctionReflectionProvider = $nativeFunctionReflectionProvider; $this->stubPhpDocProvider = $stubPhpDocProvider; @@ -154,6 +159,8 @@ private function getClassFromReflection(\ReflectionClass $reflectionClass, strin $classReflection = new ClassReflection( $this->reflectionProviderProvider->getReflectionProvider(), $this->fileTypeMapper, + $this->stubPhpDocProvider, + $this->phpDocInheritanceResolver, $this->phpVersion, $this->classReflectionExtensionRegistryProvider->getRegistry()->getPropertiesClassReflectionExtensions(), $this->classReflectionExtensionRegistryProvider->getRegistry()->getMethodsClassReflectionExtensions(), diff --git a/src/Testing/RuleTestCase.php b/src/Testing/RuleTestCase.php index 01f657065a..8d6d7f6611 100644 --- a/src/Testing/RuleTestCase.php +++ b/src/Testing/RuleTestCase.php @@ -18,6 +18,7 @@ use PHPStan\PhpDoc\PhpDocInheritanceResolver; use PHPStan\PhpDoc\PhpDocNodeResolver; use PHPStan\PhpDoc\PhpDocStringResolver; +use PHPStan\PhpDoc\StubPhpDocProvider; use PHPStan\Reflection\ReflectionProvider\DirectReflectionProviderProvider; use PHPStan\Rules\Registry; use PHPStan\Rules\Rule; @@ -77,6 +78,7 @@ private function getAnalyser(): Analyser $this->getClassReflectionExtensionRegistryProvider(), $this->getParser(), $fileTypeMapper, + self::getContainer()->getByType(StubPhpDocProvider::class), self::getContainer()->getByType(PhpVersion::class), $phpDocInheritanceResolver, $fileHelper, diff --git a/src/Testing/TestCase.php b/src/Testing/TestCase.php index ec3ccb90e1..bac8ae96cf 100644 --- a/src/Testing/TestCase.php +++ b/src/Testing/TestCase.php @@ -238,6 +238,7 @@ private function createRuntimeReflectionProvider( $classReflectionExtensionRegistryProvider, $functionReflectionFactory, $fileTypeMapper, + self::getContainer()->getByType(PhpDocInheritanceResolver::class), self::getContainer()->getByType(PhpVersion::class), self::getContainer()->getByType(NativeFunctionReflectionProvider::class), self::getContainer()->getByType(StubPhpDocProvider::class), @@ -393,6 +394,7 @@ private function createStaticReflectionProvider( $classReflectionExtensionRegistryProvider, $classReflector, $fileTypeMapper, + self::getContainer()->getByType(PhpDocInheritanceResolver::class), self::getContainer()->getByType(PhpVersion::class), self::getContainer()->getByType(NativeFunctionReflectionProvider::class), self::getContainer()->getByType(StubPhpDocProvider::class), diff --git a/src/Testing/TypeInferenceTestCase.php b/src/Testing/TypeInferenceTestCase.php index 6e5fdea242..06774f7fb8 100644 --- a/src/Testing/TypeInferenceTestCase.php +++ b/src/Testing/TypeInferenceTestCase.php @@ -19,6 +19,7 @@ use PHPStan\PhpDoc\PhpDocInheritanceResolver; use PHPStan\PhpDoc\PhpDocNodeResolver; use PHPStan\PhpDoc\PhpDocStringResolver; +use PHPStan\PhpDoc\StubPhpDocProvider; use PHPStan\Reflection\ReflectionProvider\DirectReflectionProviderProvider; use PHPStan\TrinaryLogic; use PHPStan\Type\DynamicMethodReturnTypeExtension; @@ -69,6 +70,7 @@ public function processFile( $this->getClassReflectionExtensionRegistryProvider(), $this->getParser(), $fileTypeMapper, + self::getContainer()->getByType(StubPhpDocProvider::class), self::getContainer()->getByType(PhpVersion::class), $phpDocInheritanceResolver, $fileHelper, diff --git a/tests/PHPStan/Analyser/AnalyserTest.php b/tests/PHPStan/Analyser/AnalyserTest.php index 2547c6985e..a98abc07f5 100644 --- a/tests/PHPStan/Analyser/AnalyserTest.php +++ b/tests/PHPStan/Analyser/AnalyserTest.php @@ -17,6 +17,7 @@ use PHPStan\PhpDoc\PhpDocNodeResolver; use PHPStan\PhpDoc\PhpDocStringResolver; +use PHPStan\PhpDoc\StubPhpDocProvider; use PHPStan\Reflection\ReflectionProvider\DirectReflectionProviderProvider; use PHPStan\Rules\AlwaysFailRule; use PHPStan\Rules\Registry; @@ -507,6 +508,7 @@ private function createAnalyser(bool $reportUnmatchedIgnoredErrors): \PHPStan\An $this->getClassReflectionExtensionRegistryProvider(), $this->getParser(), $fileTypeMapper, + self::getContainer()->getByType(StubPhpDocProvider::class), self::getContainer()->getByType(PhpVersion::class), $phpDocInheritanceResolver, $fileHelper, diff --git a/tests/PHPStan/Analyser/ClassConstantStubFileTest.php b/tests/PHPStan/Analyser/ClassConstantStubFileTest.php new file mode 100644 index 0000000000..02081f8433 --- /dev/null +++ b/tests/PHPStan/Analyser/ClassConstantStubFileTest.php @@ -0,0 +1,37 @@ +gatherAssertTypes(__DIR__ . '/data/class-constant-stub-files.php'); + } + + /** + * @dataProvider dataFileAsserts + * @param string $assertType + * @param string $file + * @param mixed ...$args + */ + public function testFileAsserts( + string $assertType, + string $file, + ...$args + ): void + { + $this->assertFileAsserts($assertType, $file, ...$args); + } + + public static function getAdditionalConfigFiles(): array + { + return [ + __DIR__ . '/classConstantStubFiles.neon', + ]; + } + +} diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 38df6cf224..00dc0ffa27 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -461,6 +461,8 @@ public function dataFileAsserts(): iterable } else { yield from $this->gatherAssertTypes(__DIR__ . '/data/mb_substitute_character.php'); } + + yield from $this->gatherAssertTypes(__DIR__ . '/data/class-constant-types.php'); } /** diff --git a/tests/PHPStan/Analyser/classConstantStub.stub b/tests/PHPStan/Analyser/classConstantStub.stub new file mode 100644 index 0000000000..b88ee8b705 --- /dev/null +++ b/tests/PHPStan/Analyser/classConstantStub.stub @@ -0,0 +1,14 @@ +createMock(FunctionReflectionFactory::class), new FileTypeMapper($setterReflectionProviderProvider, $this->getParser(), $phpDocStringResolver, $phpDocNodeResolver, $this->createMock(Cache::class), $anonymousClassNameHelper), + self::getContainer()->getByType(PhpDocInheritanceResolver::class), self::getContainer()->getByType(PhpVersion::class), self::getContainer()->getByType(NativeFunctionReflectionProvider::class), self::getContainer()->getByType(StubPhpDocProvider::class), diff --git a/tests/PHPStan/Reflection/ClassReflectionTest.php b/tests/PHPStan/Reflection/ClassReflectionTest.php index 2a74d56c8d..48cce715eb 100644 --- a/tests/PHPStan/Reflection/ClassReflectionTest.php +++ b/tests/PHPStan/Reflection/ClassReflectionTest.php @@ -8,6 +8,8 @@ use Attributes\IsNotAttribute; use PHPStan\Broker\Broker; use PHPStan\Php\PhpVersion; +use PHPStan\PhpDoc\PhpDocInheritanceResolver; +use PHPStan\PhpDoc\StubPhpDocProvider; use PHPStan\Type\FileTypeMapper; use WrongClassConstantFile\SecuredRouter; @@ -32,7 +34,9 @@ public function testHasTraitUse(string $className, bool $has): void { $broker = $this->createMock(Broker::class); $fileTypeMapper = $this->createMock(FileTypeMapper::class); - $classReflection = new ClassReflection($broker, $fileTypeMapper, new PhpVersion(PHP_VERSION_ID), [], [], $className, new \ReflectionClass($className), null, null, null); + $stubPhpDocProvider = $this->createMock(StubPhpDocProvider::class); + $phpDocInheritanceResolver = $this->createMock(PhpDocInheritanceResolver::class); + $classReflection = new ClassReflection($broker, $fileTypeMapper, $stubPhpDocProvider, $phpDocInheritanceResolver, new PhpVersion(PHP_VERSION_ID), [], [], $className, new \ReflectionClass($className), null, null, null); $this->assertSame($has, $classReflection->hasTraitUse(\HasTraitUse\FooTrait::class)); } @@ -95,10 +99,14 @@ public function testClassHierarchyDistances( { $broker = $this->createReflectionProvider(); $fileTypeMapper = $this->createMock(FileTypeMapper::class); + $stubPhpDocProvider = $this->createMock(StubPhpDocProvider::class); + $phpDocInheritanceResolver = $this->createMock(PhpDocInheritanceResolver::class); $classReflection = new ClassReflection( $broker, $fileTypeMapper, + $stubPhpDocProvider, + $phpDocInheritanceResolver, new PhpVersion(PHP_VERSION_ID), [], [], From a9f371e49d1b19e8d41b27eba3fef34d23808bd1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 17 Aug 2021 11:29:23 +0200 Subject: [PATCH 0108/1284] Test errors in @var above class constants --- src/Rules/PhpDoc/InvalidPhpDocTagValueRule.php | 1 + .../Rules/PhpDoc/InvalidPhpDocTagValueRuleTest.php | 4 ++++ .../Rules/PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php | 4 ++++ tests/PHPStan/Rules/PhpDoc/data/invalid-phpdoc.php | 8 ++++++++ tests/PHPStan/Rules/PhpDoc/data/invalid-var-tag-type.php | 8 ++++++++ 5 files changed, 25 insertions(+) diff --git a/src/Rules/PhpDoc/InvalidPhpDocTagValueRule.php b/src/Rules/PhpDoc/InvalidPhpDocTagValueRule.php index 3b0260a001..664437b289 100644 --- a/src/Rules/PhpDoc/InvalidPhpDocTagValueRule.php +++ b/src/Rules/PhpDoc/InvalidPhpDocTagValueRule.php @@ -40,6 +40,7 @@ public function processNode(Node $node, Scope $scope): array && !$node instanceof Node\Stmt\Property && !$node instanceof Node\Expr\Assign && !$node instanceof Node\Expr\AssignRef + && !$node instanceof Node\Stmt\ClassConst ) { return []; } diff --git a/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocTagValueRuleTest.php b/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocTagValueRuleTest.php index 11c4ac4650..ba8b2a014f 100644 --- a/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocTagValueRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocTagValueRuleTest.php @@ -86,6 +86,10 @@ public function testRule(): void 'PHPDoc tag @throws has invalid value ((\Exception): Unexpected token "*/", expected \')\' at offset 24', 72, ], + [ + 'PHPDoc tag @var has invalid value ((Foo|Bar): Unexpected token "*/", expected \')\' at offset 18', + 81, + ], ]); } diff --git a/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php b/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php index 1512dc6309..30af51a209 100644 --- a/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php @@ -97,6 +97,10 @@ public function testRule(): void 67, 'Learn more at https://phpstan.org/user-guide/discovering-symbols', ], + [ + 'PHPDoc tag @var contains unresolvable type.', + 90, + ], ]); } diff --git a/tests/PHPStan/Rules/PhpDoc/data/invalid-phpdoc.php b/tests/PHPStan/Rules/PhpDoc/data/invalid-phpdoc.php index 7d9b462f0d..15a61e603e 100644 --- a/tests/PHPStan/Rules/PhpDoc/data/invalid-phpdoc.php +++ b/tests/PHPStan/Rules/PhpDoc/data/invalid-phpdoc.php @@ -73,3 +73,11 @@ public function doFoo() } } + +class ClassConstant +{ + + /** @var (Foo|Bar */ + const FOO = 1; + +} diff --git a/tests/PHPStan/Rules/PhpDoc/data/invalid-var-tag-type.php b/tests/PHPStan/Rules/PhpDoc/data/invalid-var-tag-type.php index 1c1991710a..fd98bab42f 100644 --- a/tests/PHPStan/Rules/PhpDoc/data/invalid-var-tag-type.php +++ b/tests/PHPStan/Rules/PhpDoc/data/invalid-var-tag-type.php @@ -83,3 +83,11 @@ class Bar private $foo; } + +class Baz +{ + + /** @var self&\stdClass */ + const FOO = 1; + +} From 4cb02d15c326be71c8d1eeb0227a4931398928bb Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 17 Aug 2021 11:32:41 +0200 Subject: [PATCH 0109/1284] Fix fetching class constants on object instances --- src/Analyser/MutatingScope.php | 6 ++++-- tests/PHPStan/Analyser/data/class-constant-types.php | 7 +++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 05a5cd0338..68de2a8a41 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -1877,6 +1877,7 @@ private function resolveType(Expr $node): Type return new ErrorType(); } elseif ($node instanceof Node\Expr\ClassConstFetch && $node->name instanceof Node\Identifier) { $constantName = $node->name->name; + $isObject = false; if ($node->class instanceof Name) { $constantClass = (string) $node->class; $constantClassType = new ObjectType($constantClass); @@ -1893,6 +1894,7 @@ private function resolveType(Expr $node): Type } $namesToResolve[] = 'static'; + $isObject = true; } } if (in_array(strtolower($constantClass), $namesToResolve, true)) { @@ -1908,6 +1910,7 @@ private function resolveType(Expr $node): Type } } else { $constantClassType = $this->getType($node->class); + $isObject = true; } $referencedClasses = TypeUtils::getDirectClassNames($constantClassType); @@ -1936,8 +1939,7 @@ private function resolveType(Expr $node): Type $constantReflection = $constantClassReflection->getConstant($constantName); if ( $constantReflection instanceof ClassConstantReflection - && $node->class instanceof Name - && strtolower((string) $node->class) === 'static' + && $isObject && !$constantClassReflection->isFinal() && !$constantReflection->hasPhpDocType() ) { diff --git a/tests/PHPStan/Analyser/data/class-constant-types.php b/tests/PHPStan/Analyser/data/class-constant-types.php index 1109a1441e..c5fd3499b2 100644 --- a/tests/PHPStan/Analyser/data/class-constant-types.php +++ b/tests/PHPStan/Analyser/data/class-constant-types.php @@ -19,12 +19,15 @@ public function doFoo() { assertType('1', self::NO_TYPE); assertType('mixed', static::NO_TYPE); + assertType('mixed', $this::NO_TYPE); assertType('string', self::TYPE); assertType('string', static::TYPE); + assertType('string', $this::TYPE); assertType('string', self::PRIVATE_TYPE); assertType('string', static::PRIVATE_TYPE); + assertType('string', $this::PRIVATE_TYPE); } } @@ -40,9 +43,11 @@ public function doFoo() { assertType('string', self::TYPE); assertType('string', static::TYPE); + assertType('string', $this::TYPE); assertType('\'bar\'', self::PRIVATE_TYPE); assertType('mixed', static::PRIVATE_TYPE); + assertType('mixed', $this::PRIVATE_TYPE); } } @@ -57,6 +62,7 @@ public function doFoo() { assertType('int', self::TYPE); assertType('int', static::TYPE); + assertType('int', $this::TYPE); } } @@ -71,6 +77,7 @@ public function doFoo() { assertType('string', self::TYPE); assertType('string', static::TYPE); + assertType('string', $this::TYPE); } } From 780a54cd0149d8cb5a62bb1ef6fa2b0871114ee5 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 17 Aug 2021 12:38:51 +0200 Subject: [PATCH 0110/1284] Bleeding edge - IncompatibleClassConstantPhpDocTypeRule --- conf/bleedingEdge.neon | 1 + conf/config.level2.neon | 6 + conf/config.neon | 4 +- ...ncompatibleClassConstantPhpDocTypeRule.php | 130 ++++++++++++++++++ .../PhpDoc/InvalidPhpDocVarTagTypeRule.php | 2 + ...patibleClassConstantPhpDocTypeRuleTest.php | 42 ++++++ .../InvalidPhpDocVarTagTypeRuleTest.php | 4 - .../incompatible-class-constant-phpdoc.php | 28 ++++ 8 files changed, 212 insertions(+), 5 deletions(-) create mode 100644 src/Rules/PhpDoc/IncompatibleClassConstantPhpDocTypeRule.php create mode 100644 tests/PHPStan/Rules/PhpDoc/IncompatibleClassConstantPhpDocTypeRuleTest.php create mode 100644 tests/PHPStan/Rules/PhpDoc/data/incompatible-class-constant-phpdoc.php diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 2c4bc61dfc..be65e597a3 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -24,5 +24,6 @@ parameters: validateOverridingMethodsInStubs: true crossCheckInterfaces: true finalByPhpDocTag: true + classConstants: true stubFiles: - ../stubs/arrayFunctions.stub diff --git a/conf/config.level2.neon b/conf/config.level2.neon index 857d972c53..f437bce73f 100644 --- a/conf/config.level2.neon +++ b/conf/config.level2.neon @@ -6,6 +6,10 @@ parameters: checkThisOnly: false checkPhpDocMissingReturn: true +conditionalTags: + PHPStan\Rules\PhpDoc\IncompatibleClassConstantPhpDocTypeRule: + phpstan.rules.rule: %featureToggles.classConstants% + rules: - PHPStan\Rules\Cast\EchoRule - PHPStan\Rules\Cast\InvalidCastRule @@ -50,6 +54,8 @@ services: crossCheckInterfaces: %featureToggles.crossCheckInterfaces% tags: - phpstan.rules.rule + - + class: PHPStan\Rules\PhpDoc\IncompatibleClassConstantPhpDocTypeRule - class: PHPStan\Rules\Generics\InterfaceAncestorsRule arguments: diff --git a/conf/config.neon b/conf/config.neon index c06a1f887d..7125c4ca2b 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -48,6 +48,7 @@ parameters: validateOverridingMethodsInStubs: false crossCheckInterfaces: false finalByPhpDocTag: false + classConstants: false fileExtensions: - php checkAlwaysTrueCheckTypeFunctionCall: false @@ -225,7 +226,8 @@ parametersSchema: neverInGenericReturnType: bool(), validateOverridingMethodsInStubs: bool(), crossCheckInterfaces: bool(), - finalByPhpDocTag: bool() + finalByPhpDocTag: bool(), + classConstants: bool() ]) fileExtensions: listOf(string()) checkAlwaysTrueCheckTypeFunctionCall: bool() diff --git a/src/Rules/PhpDoc/IncompatibleClassConstantPhpDocTypeRule.php b/src/Rules/PhpDoc/IncompatibleClassConstantPhpDocTypeRule.php new file mode 100644 index 0000000000..ac85f668dd --- /dev/null +++ b/src/Rules/PhpDoc/IncompatibleClassConstantPhpDocTypeRule.php @@ -0,0 +1,130 @@ + + */ +class IncompatibleClassConstantPhpDocTypeRule implements Rule +{ + + private \PHPStan\Rules\Generics\GenericObjectTypeCheck $genericObjectTypeCheck; + + private UnresolvableTypeHelper $unresolvableTypeHelper; + + public function __construct( + GenericObjectTypeCheck $genericObjectTypeCheck, + UnresolvableTypeHelper $unresolvableTypeHelper + ) + { + $this->genericObjectTypeCheck = $genericObjectTypeCheck; + $this->unresolvableTypeHelper = $unresolvableTypeHelper; + } + + public function getNodeType(): string + { + return Node\Stmt\ClassConst::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$scope->isInClass()) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $errors = []; + foreach ($node->consts as $const) { + $constantName = $const->name->toString(); + $errors = array_merge($errors, $this->processSingleConstant($scope->getClassReflection(), $constantName)); + } + + return $errors; + } + + /** + * @param string $constantName + * @return RuleError[] + */ + private function processSingleConstant(ClassReflection $classReflection, string $constantName): array + { + $constantReflection = $classReflection->getConstant($constantName); + if (!$constantReflection instanceof ClassConstantReflection) { + return []; + } + + if (!$constantReflection->hasPhpDocType()) { + return []; + } + + $phpDocType = $constantReflection->getValueType(); + + $errors = []; + if ( + $this->unresolvableTypeHelper->containsUnresolvableType($phpDocType) + ) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'PHPDoc tag @var for constant %s::%s contains unresolvable type.', + $constantReflection->getDeclaringClass()->getName(), + $constantName + ))->build(); + } else { + $nativeType = ConstantTypeHelper::getTypeFromValue($constantReflection->getValue()); + $isSuperType = $phpDocType->isSuperTypeOf($nativeType); + $verbosity = VerbosityLevel::getRecommendedLevelByType($phpDocType, $nativeType); + if ($isSuperType->no()) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'PHPDoc tag @var for constant %s::%s with type %s is incompatible with value %s.', + $constantReflection->getDeclaringClass()->getDisplayName(), + $constantName, + $phpDocType->describe($verbosity), + $nativeType->describe(VerbosityLevel::value()) + ))->build(); + + } elseif ($isSuperType->maybe()) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'PHPDoc tag @var for constant %s::%s with type %s is not subtype of value %s.', + $constantReflection->getDeclaringClass()->getDisplayName(), + $constantName, + $phpDocType->describe($verbosity), + $nativeType->describe(VerbosityLevel::value()) + ))->build(); + } + } + + return array_merge($errors, $this->genericObjectTypeCheck->check( + $phpDocType, + sprintf( + 'PHPDoc tag @var for constant %s::%s contains generic type %%s but class %%s is not generic.', + $constantReflection->getDeclaringClass()->getDisplayName(), + $constantName + ), + sprintf( + 'Generic type %%s in PHPDoc tag @var for constant %s::%s does not specify all template types of class %%s: %%s', + $constantReflection->getDeclaringClass()->getDisplayName(), + $constantName + ), + sprintf( + 'Generic type %%s in PHPDoc tag @var for constant %s::%s specifies %%d template types, but class %%s supports only %%d: %%s', + $constantReflection->getDeclaringClass()->getDisplayName(), + $constantName + ), + sprintf( + 'Type %%s in generic type %%s in PHPDoc tag @var for constant %s::%s is not subtype of template type %%s of class %%s.', + $constantReflection->getDeclaringClass()->getDisplayName(), + $constantName + ) + )); + } + +} diff --git a/src/Rules/PhpDoc/InvalidPhpDocVarTagTypeRule.php b/src/Rules/PhpDoc/InvalidPhpDocVarTagTypeRule.php index eb81251a0f..9acb9457f1 100644 --- a/src/Rules/PhpDoc/InvalidPhpDocVarTagTypeRule.php +++ b/src/Rules/PhpDoc/InvalidPhpDocVarTagTypeRule.php @@ -68,6 +68,8 @@ public function processNode(Node $node, Scope $scope): array if ( $node instanceof Node\Stmt\Property || $node instanceof Node\Stmt\PropertyProperty + || $node instanceof Node\Stmt\ClassConst + || $node instanceof Node\Stmt\Const_ ) { return []; } diff --git a/tests/PHPStan/Rules/PhpDoc/IncompatibleClassConstantPhpDocTypeRuleTest.php b/tests/PHPStan/Rules/PhpDoc/IncompatibleClassConstantPhpDocTypeRuleTest.php new file mode 100644 index 0000000000..2126a1191d --- /dev/null +++ b/tests/PHPStan/Rules/PhpDoc/IncompatibleClassConstantPhpDocTypeRuleTest.php @@ -0,0 +1,42 @@ + + */ +class IncompatibleClassConstantPhpDocTypeRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new IncompatibleClassConstantPhpDocTypeRule(new GenericObjectTypeCheck(), new UnresolvableTypeHelper(true)); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/incompatible-class-constant-phpdoc.php'], [ + [ + 'PHPDoc tag @var for constant IncompatibleClassConstantPhpDoc\Foo::FOO contains unresolvable type.', + 9, + ], + [ + 'PHPDoc tag @var for constant IncompatibleClassConstantPhpDoc\Foo::BAZ with type string is incompatible with value 1.', + 17, + ], + [ + 'PHPDoc tag @var for constant IncompatibleClassConstantPhpDoc\Foo::DOLOR with type IncompatibleClassConstantPhpDoc\Foo is incompatible with value 1.', + 26, + ], + [ + 'PHPDoc tag @var for constant IncompatibleClassConstantPhpDoc\Foo::DOLOR contains generic type IncompatibleClassConstantPhpDoc\Foo but class IncompatibleClassConstantPhpDoc\Foo is not generic.', + 26, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php b/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php index 30af51a209..1512dc6309 100644 --- a/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php @@ -97,10 +97,6 @@ public function testRule(): void 67, 'Learn more at https://phpstan.org/user-guide/discovering-symbols', ], - [ - 'PHPDoc tag @var contains unresolvable type.', - 90, - ], ]); } diff --git a/tests/PHPStan/Rules/PhpDoc/data/incompatible-class-constant-phpdoc.php b/tests/PHPStan/Rules/PhpDoc/data/incompatible-class-constant-phpdoc.php new file mode 100644 index 0000000000..d9217ad973 --- /dev/null +++ b/tests/PHPStan/Rules/PhpDoc/data/incompatible-class-constant-phpdoc.php @@ -0,0 +1,28 @@ + */ + const DOLOR = 1; + +} From 5c0cccc51fea097057ecaf6e85b277b6ef6db253 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 17 Aug 2021 13:47:26 +0200 Subject: [PATCH 0111/1284] Fixes --- tests/PHPStan/Analyser/data/deducted-types.php | 2 +- tests/PHPStan/Analyser/data/union-intersection.php | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/data/deducted-types.php b/tests/PHPStan/Analyser/data/deducted-types.php index 3cd1445f44..d8b536fcec 100644 --- a/tests/PHPStan/Analyser/data/deducted-types.php +++ b/tests/PHPStan/Analyser/data/deducted-types.php @@ -4,7 +4,7 @@ use TypesNamespaceFunctions; -class Foo +final class Foo { const INTEGER_CONSTANT = 1; diff --git a/tests/PHPStan/Analyser/data/union-intersection.php b/tests/PHPStan/Analyser/data/union-intersection.php index 0e0edc880a..03266cd2c8 100644 --- a/tests/PHPStan/Analyser/data/union-intersection.php +++ b/tests/PHPStan/Analyser/data/union-intersection.php @@ -5,6 +5,7 @@ class WithFoo { + /** @var 1 */ const FOO_CONSTANT = 1; /** @var Foo */ @@ -25,7 +26,10 @@ public static function doStaticFoo(): Foo class WithFooAndBar { + /** @var 1 */ const FOO_CONSTANT = 1; + + /** @var 1 */ const BAR_CONSTANT = 1; /** @var AnotherFoo */ @@ -59,7 +63,10 @@ public static function doStaticBar(): Bar interface WithFooAndBarInterface { + /** @var 1 */ const FOO_CONSTANT = 1; + + /** @var 1 */ const BAR_CONSTANT = 1; public function doFoo(): AnotherFoo; @@ -80,6 +87,7 @@ interface SomeInterface class Dolor { + /** @var array{1, 2, 3} */ const PARENT_CONSTANT = [1, 2, 3]; } From c0e78e44213930b12c9578d4eceb09bb0415de7d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 17 Aug 2021 13:48:54 +0200 Subject: [PATCH 0112/1284] Test --- ...patibleClassConstantPhpDocTypeRuleTest.php | 4 ++++ .../incompatible-class-constant-phpdoc.php | 24 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/tests/PHPStan/Rules/PhpDoc/IncompatibleClassConstantPhpDocTypeRuleTest.php b/tests/PHPStan/Rules/PhpDoc/IncompatibleClassConstantPhpDocTypeRuleTest.php index 2126a1191d..f2d9d347f5 100644 --- a/tests/PHPStan/Rules/PhpDoc/IncompatibleClassConstantPhpDocTypeRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/IncompatibleClassConstantPhpDocTypeRuleTest.php @@ -36,6 +36,10 @@ public function testRule(): void 'PHPDoc tag @var for constant IncompatibleClassConstantPhpDoc\Foo::DOLOR contains generic type IncompatibleClassConstantPhpDoc\Foo but class IncompatibleClassConstantPhpDoc\Foo is not generic.', 26, ], + [ + 'PHPDoc tag @var for constant IncompatibleClassConstantPhpDoc\Bar::BAZ with type string is incompatible with value 2.', + 35, + ], ]); } diff --git a/tests/PHPStan/Rules/PhpDoc/data/incompatible-class-constant-phpdoc.php b/tests/PHPStan/Rules/PhpDoc/data/incompatible-class-constant-phpdoc.php index d9217ad973..c4f163e773 100644 --- a/tests/PHPStan/Rules/PhpDoc/data/incompatible-class-constant-phpdoc.php +++ b/tests/PHPStan/Rules/PhpDoc/data/incompatible-class-constant-phpdoc.php @@ -26,3 +26,27 @@ class Foo const DOLOR = 1; } + +class Bar extends Foo +{ + + const BAR = 2; + + const BAZ = 2; + +} + +class Baz +{ + + /** @var string */ + private const BAZ = 'foo'; + +} + +class Lorem extends Baz +{ + + private const BAZ = 1; + +} From 89acb0db4400c95b231ae74830e8607d72824cbc Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 17 Aug 2021 14:06:35 +0200 Subject: [PATCH 0113/1284] BleedingEdge - OverridingConstantRule --- composer.json | 2 +- composer.lock | 14 +- conf/config.level0.neon | 7 + src/Php/PhpVersion.php | 5 + src/Reflection/ClassConstantReflection.php | 9 ++ src/Reflection/ClassReflection.php | 2 +- .../Constants/OverridingConstantRule.php | 140 ++++++++++++++++++ .../Constants/OverridingConstantRuleTest.php | 75 ++++++++++ .../Constants/data/overriding-constant.php | 41 +++++ .../data/overriding-final-constant.php | 53 +++++++ 10 files changed, 339 insertions(+), 9 deletions(-) create mode 100644 src/Rules/Constants/OverridingConstantRule.php create mode 100644 tests/PHPStan/Rules/Constants/OverridingConstantRuleTest.php create mode 100644 tests/PHPStan/Rules/Constants/data/overriding-constant.php create mode 100644 tests/PHPStan/Rules/Constants/data/overriding-final-constant.php diff --git a/composer.json b/composer.json index a448265b73..56e074e5c1 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "nette/utils": "^3.1.3", "nikic/php-parser": "4.12.0", "ondram/ci-detector": "^3.4.0", - "ondrejmirtes/better-reflection": "4.3.62", + "ondrejmirtes/better-reflection": "4.3.63", "phpstan/php-8-stubs": "^0.1.22", "phpstan/phpdoc-parser": "^0.5.5", "react/child-process": "^0.6.1", diff --git a/composer.lock b/composer.lock index f337543c91..e82388ff60 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5e9962680324731ed9a8faa87bf9a9a7", + "content-hash": "789942b974a07bb461fad422a61b9276", "packages": [ { "name": "clue/block-react", @@ -2074,16 +2074,16 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "4.3.62", + "version": "4.3.63", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "348a8777d11b9dd88f13e0d99a84d1671a8cf8a7" + "reference": "4cc6dd660549561ce96b301c4cb8661877dab820" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/348a8777d11b9dd88f13e0d99a84d1671a8cf8a7", - "reference": "348a8777d11b9dd88f13e0d99a84d1671a8cf8a7", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/4cc6dd660549561ce96b301c4cb8661877dab820", + "reference": "4cc6dd660549561ce96b301c4cb8661877dab820", "shasum": "" }, "require": { @@ -2138,9 +2138,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/4.3.62" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/4.3.63" }, - "time": "2021-07-21T11:44:15+00:00" + "time": "2021-08-17T12:14:59+00:00" }, { "name": "phpstan/php-8-stubs", diff --git a/conf/config.level0.neon b/conf/config.level0.neon index e13224c8d9..cb91d6c769 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -19,6 +19,8 @@ conditionalTags: phpstan.rules.rule: %featureToggles.apiRules% PHPStan\Rules\Api\PhpStanNamespaceIn3rdPartyPackageRule: phpstan.rules.rule: %featureToggles.apiRules% + PHPStan\Rules\Constants\OverridingConstantRule: + phpstan.rules.rule: %featureToggles.classConstants% PHPStan\Rules\Functions\ClosureUsesThisRule: phpstan.rules.rule: %featureToggles.closureUsesThis% PHPStan\Rules\Missing\MissingClosureNativeReturnTypehintRule: @@ -144,6 +146,11 @@ services: checkFunctionNameCase: %checkFunctionNameCase% reportMagicMethods: %reportMagicMethods% + - + class: PHPStan\Rules\Constants\OverridingConstantRule + arguments: + checkPhpDocMethodSignatures: %checkPhpDocMethodSignatures% + - class: PHPStan\Rules\Methods\OverridingMethodRule arguments: diff --git a/src/Php/PhpVersion.php b/src/Php/PhpVersion.php index a8090db932..688fae28ae 100644 --- a/src/Php/PhpVersion.php +++ b/src/Php/PhpVersion.php @@ -127,4 +127,9 @@ public function isNullValidArgInMbSubstituteCharacter(): bool return $this->versionId >= 80000; } + public function isInterfaceConstantImplicitlyFinal(): bool + { + return $this->versionId < 80100; + } + } diff --git a/src/Reflection/ClassConstantReflection.php b/src/Reflection/ClassConstantReflection.php index c5f261ed83..9a53dc87b3 100644 --- a/src/Reflection/ClassConstantReflection.php +++ b/src/Reflection/ClassConstantReflection.php @@ -101,6 +101,15 @@ public function isPublic(): bool return $this->reflection->isPublic(); } + public function isFinal(): bool + { + if (method_exists($this->reflection, 'isFinal')) { + return $this->reflection->isFinal(); + } + + return false; + } + public function isDeprecated(): TrinaryLogic { return TrinaryLogic::createFromBoolean($this->isDeprecated); diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index b17994c62a..b2108ff31a 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -650,7 +650,7 @@ private function collectInterfaces(ClassReflection $interface): array /** * @return \PHPStan\Reflection\ClassReflection[] */ - private function getImmediateInterfaces(): array + public function getImmediateInterfaces(): array { $indirectInterfaceNames = []; $parent = $this->getParentClass(); diff --git a/src/Rules/Constants/OverridingConstantRule.php b/src/Rules/Constants/OverridingConstantRule.php new file mode 100644 index 0000000000..cf19e0d68a --- /dev/null +++ b/src/Rules/Constants/OverridingConstantRule.php @@ -0,0 +1,140 @@ + + */ +class OverridingConstantRule implements Rule +{ + + private PhpVersion $phpVersion; + + private bool $checkPhpDocMethodSignatures; + + public function __construct( + PhpVersion $phpVersion, + bool $checkPhpDocMethodSignatures + ) + { + $this->phpVersion = $phpVersion; + $this->checkPhpDocMethodSignatures = $checkPhpDocMethodSignatures; + } + + public function getNodeType(): string + { + return Node\Stmt\ClassConst::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$scope->isInClass()) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $errors = []; + foreach ($node->consts as $const) { + $constantName = $const->name->toString(); + $errors = array_merge($errors, $this->processSingleConstant($scope->getClassReflection(), $constantName)); + } + + return $errors; + } + + /** + * @param string $constantName + * @return RuleError[] + */ + private function processSingleConstant(ClassReflection $classReflection, string $constantName): array + { + $prototype = $this->findPrototype($classReflection, $constantName); + if (!$prototype instanceof ClassConstantReflection) { + return []; + } + + $constantReflection = $classReflection->getConstant($constantName); + if (!$constantReflection instanceof ClassConstantReflection) { + return []; + } + + $errors = []; + if ( + $prototype->isFinal() + || ( + $this->phpVersion->isInterfaceConstantImplicitlyFinal() + && $prototype->getDeclaringClass()->isInterface() + ) + ) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Constant %s::%s overrides final constant %s::%s.', + $classReflection->getDisplayName(), + $constantReflection->getName(), + $prototype->getDeclaringClass()->getDisplayName(), + $prototype->getName() + ))->nonIgnorable()->build(); + } + + if (!$this->checkPhpDocMethodSignatures) { + return $errors; + } + + if (!$prototype->hasPhpDocType()) { + return $errors; + } + + if (!$constantReflection->hasPhpDocType()) { + return $errors; + } + + if (!$prototype->getValueType()->isSuperTypeOf($constantReflection->getValueType())->yes()) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Type %s of constant %s::%s is not covariant with type %s of constant %s::%s.', + $constantReflection->getValueType()->describe(VerbosityLevel::value()), + $constantReflection->getDeclaringClass()->getDisplayName(), + $constantReflection->getName(), + $prototype->getValueType()->describe(VerbosityLevel::value()), + $prototype->getDeclaringClass()->getDisplayName(), + $prototype->getName() + ))->build(); + } + + return $errors; + } + + private function findPrototype(ClassReflection $classReflection, string $constantName): ?ConstantReflection + { + foreach ($classReflection->getImmediateInterfaces() as $immediateInterface) { + if ($immediateInterface->hasConstant($constantName)) { + return $immediateInterface->getConstant($constantName); + } + } + + $parentClass = $classReflection->getParentClass(); + if ($parentClass === false) { + return null; + } + + if (!$parentClass->hasConstant($constantName)) { + return null; + } + + $constant = $parentClass->getConstant($constantName); + if ($constant->isPrivate()) { + return null; + } + + return $constant; + } + +} diff --git a/tests/PHPStan/Rules/Constants/OverridingConstantRuleTest.php b/tests/PHPStan/Rules/Constants/OverridingConstantRuleTest.php new file mode 100644 index 0000000000..5b650e5454 --- /dev/null +++ b/tests/PHPStan/Rules/Constants/OverridingConstantRuleTest.php @@ -0,0 +1,75 @@ + */ +class OverridingConstantRuleTest extends RuleTestCase +{ + + protected function getRule(): \PHPStan\Rules\Rule + { + return new OverridingConstantRule(new PhpVersion(PHP_VERSION_ID), true); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/overriding-constant.php'], [ + [ + 'Type string of constant OverridingConstant\Bar::BAR is not covariant with type int of constant OverridingConstant\Foo::BAR.', + 30, + ], + [ + 'Type int|string of constant OverridingConstant\Bar::IPSUM is not covariant with type int of constant OverridingConstant\Foo::IPSUM.', + 39, + ], + ]); + } + + public function testFinal(): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires static reflection.'); + } + + $errors = [ + [ + 'Constant OverridingFinalConstant\Bar::FOO overrides final constant OverridingFinalConstant\Foo::FOO.', + 18, + ], + [ + 'Constant OverridingFinalConstant\Bar::BAR overrides final constant OverridingFinalConstant\Foo::BAR.', + 19, + ], + ]; + + if (PHP_VERSION_ID < 80100) { + $errors[] = [ + 'Constant OverridingFinalConstant\Baz::FOO overrides final constant OverridingFinalConstant\FooInterface::FOO.', + 34, + ]; + } + + $errors[] = [ + 'Constant OverridingFinalConstant\Baz::BAR overrides final constant OverridingFinalConstant\FooInterface::BAR.', + 35, + ]; + + if (PHP_VERSION_ID < 80100) { + $errors[] = [ + 'Constant OverridingFinalConstant\Lorem::FOO overrides final constant OverridingFinalConstant\BarInterface::FOO.', + 51, + ]; + } + + $errors[] = [ + 'Type string of constant OverridingFinalConstant\Lorem::FOO is not covariant with type int of constant OverridingFinalConstant\BarInterface::FOO.', + 51, + ]; + + $this->analyse([__DIR__ . '/data/overriding-final-constant.php'], $errors); + } + +} diff --git a/tests/PHPStan/Rules/Constants/data/overriding-constant.php b/tests/PHPStan/Rules/Constants/data/overriding-constant.php new file mode 100644 index 0000000000..de893d67e6 --- /dev/null +++ b/tests/PHPStan/Rules/Constants/data/overriding-constant.php @@ -0,0 +1,41 @@ += 8.1 + +namespace OverridingFinalConstant; + +class Foo +{ + + const NON_FINAL = 1; + final const FOO = 1; + final const BAR = 1; + +} + +class Bar extends Foo +{ + + const NON_FINAL = 2; + const FOO = 2; + final const BAR = 2; + +} + +interface FooInterface +{ + + const FOO = 1; + final const BAR = 2; + +} + +class Baz implements FooInterface +{ + + const FOO = 3; + const BAR = 4; + +} + +interface BarInterface +{ + + /** @var int */ + const FOO = 1; + +} + +class Lorem implements BarInterface +{ + + /** @var string */ + const FOO = 1; + +} From 716fe524ed13db644e5f86c7892f4d31f4b0c928 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 17 Aug 2021 17:27:05 +0200 Subject: [PATCH 0114/1284] Fix --- composer.json | 2 +- composer.lock | 14 +++++++------- src/Reflection/ClassConstantReflection.php | 11 ++++++++++- src/Reflection/ClassReflection.php | 1 + src/Rules/Constants/OverridingConstantRule.php | 13 +------------ .../Rules/Constants/OverridingConstantRuleTest.php | 3 +-- 6 files changed, 21 insertions(+), 23 deletions(-) diff --git a/composer.json b/composer.json index 56e074e5c1..ceeff9e26e 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "nette/utils": "^3.1.3", "nikic/php-parser": "4.12.0", "ondram/ci-detector": "^3.4.0", - "ondrejmirtes/better-reflection": "4.3.63", + "ondrejmirtes/better-reflection": "4.3.64", "phpstan/php-8-stubs": "^0.1.22", "phpstan/phpdoc-parser": "^0.5.5", "react/child-process": "^0.6.1", diff --git a/composer.lock b/composer.lock index e82388ff60..4913070e89 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "789942b974a07bb461fad422a61b9276", + "content-hash": "8bb4c4f5674ba1245e1db1a1cad7f8e8", "packages": [ { "name": "clue/block-react", @@ -2074,16 +2074,16 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "4.3.63", + "version": "4.3.64", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "4cc6dd660549561ce96b301c4cb8661877dab820" + "reference": "737857c9777a5f6c793935ee1bb2f72017d3a976" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/4cc6dd660549561ce96b301c4cb8661877dab820", - "reference": "4cc6dd660549561ce96b301c4cb8661877dab820", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/737857c9777a5f6c793935ee1bb2f72017d3a976", + "reference": "737857c9777a5f6c793935ee1bb2f72017d3a976", "shasum": "" }, "require": { @@ -2138,9 +2138,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/4.3.63" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/4.3.64" }, - "time": "2021-08-17T12:14:59+00:00" + "time": "2021-08-17T15:26:08+00:00" }, { "name": "phpstan/php-8-stubs", diff --git a/src/Reflection/ClassConstantReflection.php b/src/Reflection/ClassConstantReflection.php index 9a53dc87b3..1c92d6119d 100644 --- a/src/Reflection/ClassConstantReflection.php +++ b/src/Reflection/ClassConstantReflection.php @@ -2,6 +2,7 @@ namespace PHPStan\Reflection; +use PHPStan\Php\PhpVersion; use PHPStan\TrinaryLogic; use PHPStan\Type\ConstantTypeHelper; use PHPStan\Type\Type; @@ -15,6 +16,8 @@ class ClassConstantReflection implements ConstantReflection private ?Type $phpDocType; + private PhpVersion $phpVersion; + private ?string $deprecatedDescription; private bool $isDeprecated; @@ -27,6 +30,7 @@ public function __construct( ClassReflection $declaringClass, \ReflectionClassConstant $reflection, ?Type $phpDocType, + PhpVersion $phpVersion, ?string $deprecatedDescription, bool $isDeprecated, bool $isInternal @@ -35,6 +39,7 @@ public function __construct( $this->declaringClass = $declaringClass; $this->reflection = $reflection; $this->phpDocType = $phpDocType; + $this->phpVersion = $phpVersion; $this->deprecatedDescription = $deprecatedDescription; $this->isDeprecated = $isDeprecated; $this->isInternal = $isInternal; @@ -107,7 +112,11 @@ public function isFinal(): bool return $this->reflection->isFinal(); } - return false; + if (!$this->phpVersion->isInterfaceConstantImplicitlyFinal()) { + return false; + } + + return $this->declaringClass->isInterface(); } public function isDeprecated(): TrinaryLogic diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index b2108ff31a..2801fc3a13 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -821,6 +821,7 @@ public function getConstant(string $name): ConstantReflection $declaringClass, $reflectionConstant, $phpDocType, + $this->phpVersion, $deprecatedDescription, $isDeprecated, $isInternal diff --git a/src/Rules/Constants/OverridingConstantRule.php b/src/Rules/Constants/OverridingConstantRule.php index cf19e0d68a..7818efa68a 100644 --- a/src/Rules/Constants/OverridingConstantRule.php +++ b/src/Rules/Constants/OverridingConstantRule.php @@ -4,7 +4,6 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; -use PHPStan\Php\PhpVersion; use PHPStan\Reflection\ClassConstantReflection; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ConstantReflection; @@ -19,16 +18,12 @@ class OverridingConstantRule implements Rule { - private PhpVersion $phpVersion; - private bool $checkPhpDocMethodSignatures; public function __construct( - PhpVersion $phpVersion, bool $checkPhpDocMethodSignatures ) { - $this->phpVersion = $phpVersion; $this->checkPhpDocMethodSignatures = $checkPhpDocMethodSignatures; } @@ -69,13 +64,7 @@ private function processSingleConstant(ClassReflection $classReflection, string } $errors = []; - if ( - $prototype->isFinal() - || ( - $this->phpVersion->isInterfaceConstantImplicitlyFinal() - && $prototype->getDeclaringClass()->isInterface() - ) - ) { + if ($prototype->isFinal()) { $errors[] = RuleErrorBuilder::message(sprintf( 'Constant %s::%s overrides final constant %s::%s.', $classReflection->getDisplayName(), diff --git a/tests/PHPStan/Rules/Constants/OverridingConstantRuleTest.php b/tests/PHPStan/Rules/Constants/OverridingConstantRuleTest.php index 5b650e5454..7fd789a6b5 100644 --- a/tests/PHPStan/Rules/Constants/OverridingConstantRuleTest.php +++ b/tests/PHPStan/Rules/Constants/OverridingConstantRuleTest.php @@ -2,7 +2,6 @@ namespace PHPStan\Rules\Constants; -use PHPStan\Php\PhpVersion; use PHPStan\Testing\RuleTestCase; /** @extends RuleTestCase */ @@ -11,7 +10,7 @@ class OverridingConstantRuleTest extends RuleTestCase protected function getRule(): \PHPStan\Rules\Rule { - return new OverridingConstantRule(new PhpVersion(PHP_VERSION_ID), true); + return new OverridingConstantRule(true); } public function testRule(): void From bad2607a032787d822a1a8f91d5176455eac8693 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 17 Aug 2021 17:07:03 +0200 Subject: [PATCH 0115/1284] Bleeding edge - private method called through static:: --- conf/bleedingEdge.neon | 1 + conf/config.level2.neon | 4 ++ conf/config.neon | 4 +- .../CallPrivateMethodThroughStaticRule.php | 61 +++++++++++++++++++ ...CallPrivateMethodThroughStaticRuleTest.php | 28 +++++++++ .../Methods/CallStaticMethodsRuleTest.php | 4 ++ .../data/call-private-method-static.php | 37 +++++++++++ .../Methods/data/call-static-methods.php | 16 +++++ 8 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 src/Rules/Methods/CallPrivateMethodThroughStaticRule.php create mode 100644 tests/PHPStan/Rules/Methods/CallPrivateMethodThroughStaticRuleTest.php create mode 100644 tests/PHPStan/Rules/Methods/data/call-private-method-static.php diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index be65e597a3..6120cbd670 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -25,5 +25,6 @@ parameters: crossCheckInterfaces: true finalByPhpDocTag: true classConstants: true + privateStaticCall: true stubFiles: - ../stubs/arrayFunctions.stub diff --git a/conf/config.level2.neon b/conf/config.level2.neon index f437bce73f..1ef7a9755b 100644 --- a/conf/config.level2.neon +++ b/conf/config.level2.neon @@ -9,6 +9,8 @@ parameters: conditionalTags: PHPStan\Rules\PhpDoc\IncompatibleClassConstantPhpDocTypeRule: phpstan.rules.rule: %featureToggles.classConstants% + PHPStan\Rules\Methods\CallPrivateMethodThroughStaticRule: + phpstan.rules.rule: %featureToggles.privateStaticCall% rules: - PHPStan\Rules\Cast\EchoRule @@ -54,6 +56,8 @@ services: crossCheckInterfaces: %featureToggles.crossCheckInterfaces% tags: - phpstan.rules.rule + - + class: PHPStan\Rules\Methods\CallPrivateMethodThroughStaticRule - class: PHPStan\Rules\PhpDoc\IncompatibleClassConstantPhpDocTypeRule - diff --git a/conf/config.neon b/conf/config.neon index 7125c4ca2b..932dd54217 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -49,6 +49,7 @@ parameters: crossCheckInterfaces: false finalByPhpDocTag: false classConstants: false + privateStaticCall: false fileExtensions: - php checkAlwaysTrueCheckTypeFunctionCall: false @@ -227,7 +228,8 @@ parametersSchema: validateOverridingMethodsInStubs: bool(), crossCheckInterfaces: bool(), finalByPhpDocTag: bool(), - classConstants: bool() + classConstants: bool(), + privateStaticCall: bool() ]) fileExtensions: listOf(string()) checkAlwaysTrueCheckTypeFunctionCall: bool() diff --git a/src/Rules/Methods/CallPrivateMethodThroughStaticRule.php b/src/Rules/Methods/CallPrivateMethodThroughStaticRule.php new file mode 100644 index 0000000000..089cd81613 --- /dev/null +++ b/src/Rules/Methods/CallPrivateMethodThroughStaticRule.php @@ -0,0 +1,61 @@ + + */ +class CallPrivateMethodThroughStaticRule implements Rule +{ + + public function getNodeType(): string + { + return StaticCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->name instanceof Node\Identifier) { + return []; + } + if (!$node->class instanceof Name) { + return []; + } + + $methodName = $node->name->name; + $className = $node->class; + if ($className->toLowerString() !== 'static') { + return []; + } + + $classType = $scope->resolveTypeByName($className); + if (!$classType->hasMethod($methodName)->yes()) { + return []; + } + + $method = $classType->getMethod($methodName, $scope); + if (!$method->isPrivate()) { + return []; + } + + if ($scope->isInClass() && $scope->getClassReflection()->isFinal()) { + return []; + } + + return [ + RuleErrorBuilder::message(sprintf( + 'Unsafe call to private method %s::%s() through static::.', + $method->getDeclaringClass()->getDisplayName(), + $method->getName() + ))->build(), + ]; + } + +} diff --git a/tests/PHPStan/Rules/Methods/CallPrivateMethodThroughStaticRuleTest.php b/tests/PHPStan/Rules/Methods/CallPrivateMethodThroughStaticRuleTest.php new file mode 100644 index 0000000000..d96da60225 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/CallPrivateMethodThroughStaticRuleTest.php @@ -0,0 +1,28 @@ + + */ +class CallPrivateMethodThroughStaticRuleTest extends RuleTestCase +{ + + protected function getRule(): \PHPStan\Rules\Rule + { + return new CallPrivateMethodThroughStaticRule(); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/call-private-method-static.php'], [ + [ + 'Unsafe call to private method CallPrivateMethodThroughStatic\Foo::doBar() through static::.', + 12, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php index d1ab7c2347..e3fafa83c9 100644 --- a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php @@ -225,6 +225,10 @@ public function testCallStaticMethods(): void 328, 'Learn more at https://phpstan.org/user-guide/discovering-symbols', ], + [ + 'Call to an undefined static method static(CallStaticMethods\CallWithStatic)::nonexistent().', + 344, + ], ]); } diff --git a/tests/PHPStan/Rules/Methods/data/call-private-method-static.php b/tests/PHPStan/Rules/Methods/data/call-private-method-static.php new file mode 100644 index 0000000000..f273d48d73 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/call-private-method-static.php @@ -0,0 +1,37 @@ + Date: Tue, 17 Aug 2021 17:47:04 +0200 Subject: [PATCH 0116/1284] Bleeding edge - private property accessed through static:: --- conf/config.level2.neon | 4 ++ ...AccessPrivatePropertyThroughStaticRule.php | 63 +++++++++++++++++++ ...ssPrivatePropertyThroughStaticRuleTest.php | 27 ++++++++ .../AccessStaticPropertiesRuleTest.php | 8 +++ .../data/access-private-property-static.php | 33 ++++++++++ .../data/access-static-properties.php | 15 +++++ 6 files changed, 150 insertions(+) create mode 100644 src/Rules/Properties/AccessPrivatePropertyThroughStaticRule.php create mode 100644 tests/PHPStan/Rules/Properties/AccessPrivatePropertyThroughStaticRuleTest.php create mode 100644 tests/PHPStan/Rules/Properties/data/access-private-property-static.php diff --git a/conf/config.level2.neon b/conf/config.level2.neon index 1ef7a9755b..8e41cc7b06 100644 --- a/conf/config.level2.neon +++ b/conf/config.level2.neon @@ -11,6 +11,8 @@ conditionalTags: phpstan.rules.rule: %featureToggles.classConstants% PHPStan\Rules\Methods\CallPrivateMethodThroughStaticRule: phpstan.rules.rule: %featureToggles.privateStaticCall% + PHPStan\Rules\Properties\AccessPrivatePropertyThroughStaticRule: + phpstan.rules.rule: %featureToggles.privateStaticCall% rules: - PHPStan\Rules\Cast\EchoRule @@ -60,6 +62,8 @@ services: class: PHPStan\Rules\Methods\CallPrivateMethodThroughStaticRule - class: PHPStan\Rules\PhpDoc\IncompatibleClassConstantPhpDocTypeRule + - + class: PHPStan\Rules\Properties\AccessPrivatePropertyThroughStaticRule - class: PHPStan\Rules\Generics\InterfaceAncestorsRule arguments: diff --git a/src/Rules/Properties/AccessPrivatePropertyThroughStaticRule.php b/src/Rules/Properties/AccessPrivatePropertyThroughStaticRule.php new file mode 100644 index 0000000000..8accf408b2 --- /dev/null +++ b/src/Rules/Properties/AccessPrivatePropertyThroughStaticRule.php @@ -0,0 +1,63 @@ + + */ +class AccessPrivatePropertyThroughStaticRule implements Rule +{ + + public function getNodeType(): string + { + return Node\Expr\StaticPropertyFetch::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->name instanceof Node\VarLikeIdentifier) { + return []; + } + if (!$node->class instanceof Name) { + return []; + } + + $propertyName = $node->name->name; + $className = $node->class; + if ($className->toLowerString() !== 'static') { + return []; + } + + $classType = $scope->resolveTypeByName($className); + if (!$classType->hasProperty($propertyName)->yes()) { + return []; + } + + $property = $classType->getProperty($propertyName, $scope); + if (!$property->isPrivate()) { + return []; + } + if (!$property->isStatic()) { + return []; + } + + if ($scope->isInClass() && $scope->getClassReflection()->isFinal()) { + return []; + } + + return [ + RuleErrorBuilder::message(sprintf( + 'Unsafe access to private property %s::$%s through static::.', + $property->getDeclaringClass()->getDisplayName(), + $propertyName + ))->build(), + ]; + } + +} diff --git a/tests/PHPStan/Rules/Properties/AccessPrivatePropertyThroughStaticRuleTest.php b/tests/PHPStan/Rules/Properties/AccessPrivatePropertyThroughStaticRuleTest.php new file mode 100644 index 0000000000..09ce325b1a --- /dev/null +++ b/tests/PHPStan/Rules/Properties/AccessPrivatePropertyThroughStaticRuleTest.php @@ -0,0 +1,27 @@ + */ +class AccessPrivatePropertyThroughStaticRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new AccessPrivatePropertyThroughStaticRule(); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/access-private-property-static.php'], [ + [ + 'Unsafe access to private property AccessPrivatePropertyThroughStatic\Foo::$foo through static::.', + 13, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php index 740455d680..ac320be48d 100644 --- a/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php @@ -174,6 +174,14 @@ public function testAccessStaticProperties(): void 209, 'Learn more at https://phpstan.org/user-guide/discovering-symbols', ], + [ + 'Static access to instance property AccessWithStatic::$bar.', + 223, + ], + [ + 'Access to an undefined static property static(AccessWithStatic)::$nonexistent.', + 224, + ], ]); } diff --git a/tests/PHPStan/Rules/Properties/data/access-private-property-static.php b/tests/PHPStan/Rules/Properties/data/access-private-property-static.php new file mode 100644 index 0000000000..51e6fc3917 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/access-private-property-static.php @@ -0,0 +1,33 @@ + Date: Tue, 17 Aug 2021 18:10:57 +0200 Subject: [PATCH 0117/1284] Bleeding edge - private constant accessed through static:: --- conf/config.level2.neon | 4 ++ ...AccessPrivateConstantThroughStaticRule.php | 60 +++++++++++++++++++ .../ArrayFillFunctionReturnTypeExtension.php | 2 +- ...ssPrivateConstantThroughStaticRuleTest.php | 29 +++++++++ .../Rules/Classes/ClassConstantRuleTest.php | 8 ++- .../data/access-private-constant-static.php | 29 +++++++++ .../data/class-constant-visibility.php | 13 ++++ 7 files changed, 142 insertions(+), 3 deletions(-) create mode 100644 src/Rules/Classes/AccessPrivateConstantThroughStaticRule.php create mode 100644 tests/PHPStan/Rules/Classes/AccessPrivateConstantThroughStaticRuleTest.php create mode 100644 tests/PHPStan/Rules/Classes/data/access-private-constant-static.php diff --git a/conf/config.level2.neon b/conf/config.level2.neon index 8e41cc7b06..df6c9850f8 100644 --- a/conf/config.level2.neon +++ b/conf/config.level2.neon @@ -7,6 +7,8 @@ parameters: checkPhpDocMissingReturn: true conditionalTags: + PHPStan\Rules\Classes\AccessPrivateConstantThroughStaticRule: + phpstan.rules.rule: %featureToggles.privateStaticCall% PHPStan\Rules\PhpDoc\IncompatibleClassConstantPhpDocTypeRule: phpstan.rules.rule: %featureToggles.classConstants% PHPStan\Rules\Methods\CallPrivateMethodThroughStaticRule: @@ -40,6 +42,8 @@ rules: - PHPStan\Rules\PhpDoc\InvalidThrowsPhpDocValueRule services: + - + class: PHPStan\Rules\Classes\AccessPrivateConstantThroughStaticRule - class: PHPStan\Rules\Classes\MixinRule arguments: diff --git a/src/Rules/Classes/AccessPrivateConstantThroughStaticRule.php b/src/Rules/Classes/AccessPrivateConstantThroughStaticRule.php new file mode 100644 index 0000000000..fa3bf53a6d --- /dev/null +++ b/src/Rules/Classes/AccessPrivateConstantThroughStaticRule.php @@ -0,0 +1,60 @@ + + */ +class AccessPrivateConstantThroughStaticRule implements Rule +{ + + public function getNodeType(): string + { + return Node\Expr\ClassConstFetch::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->name instanceof Node\Identifier) { + return []; + } + if (!$node->class instanceof Name) { + return []; + } + + $constantName = $node->name->name; + $className = $node->class; + if ($className->toLowerString() !== 'static') { + return []; + } + + $classType = $scope->resolveTypeByName($className); + if (!$classType->hasConstant($constantName)->yes()) { + return []; + } + + $constant = $classType->getConstant($constantName); + if (!$constant->isPrivate()) { + return []; + } + + if ($scope->isInClass() && $scope->getClassReflection()->isFinal()) { + return []; + } + + return [ + RuleErrorBuilder::message(sprintf( + 'Unsafe access to private constant %s::%s through static::.', + $constant->getDeclaringClass()->getDisplayName(), + $constantName + ))->build(), + ]; + } + +} diff --git a/src/Type/Php/ArrayFillFunctionReturnTypeExtension.php b/src/Type/Php/ArrayFillFunctionReturnTypeExtension.php index 8b1819d57f..c1a8c168dd 100644 --- a/src/Type/Php/ArrayFillFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayFillFunctionReturnTypeExtension.php @@ -37,7 +37,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, if ( $startIndexType instanceof ConstantIntegerType && $numberType instanceof ConstantIntegerType - && $numberType->getValue() <= static::MAX_SIZE_USE_CONSTANT_ARRAY + && $numberType->getValue() <= self::MAX_SIZE_USE_CONSTANT_ARRAY ) { $arrayBuilder = ConstantArrayTypeBuilder::createEmpty(); $nextIndex = $startIndexType->getValue(); diff --git a/tests/PHPStan/Rules/Classes/AccessPrivateConstantThroughStaticRuleTest.php b/tests/PHPStan/Rules/Classes/AccessPrivateConstantThroughStaticRuleTest.php new file mode 100644 index 0000000000..8c6ff39361 --- /dev/null +++ b/tests/PHPStan/Rules/Classes/AccessPrivateConstantThroughStaticRuleTest.php @@ -0,0 +1,29 @@ + + */ +class AccessPrivateConstantThroughStaticRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new AccessPrivateConstantThroughStaticRule(); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/access-private-constant-static.php'], [ + [ + 'Unsafe access to private constant AccessPrivateConstantThroughStatic\Foo::FOO through static::.', + 12, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Classes/ClassConstantRuleTest.php b/tests/PHPStan/Rules/Classes/ClassConstantRuleTest.php index 838397f882..53a1da938e 100644 --- a/tests/PHPStan/Rules/Classes/ClassConstantRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ClassConstantRuleTest.php @@ -143,13 +143,17 @@ public function testClassConstantVisibility(): void 'Cannot access constant FOO on int|string.', 116, ], + [ + 'Access to undefined constant static(ClassConstantVisibility\AccessWithStatic)::BAR.', + 129, + ], [ 'Class ClassConstantVisibility\Foo referenced with incorrect case: ClassConstantVisibility\FOO.', - 122, + 135, ], [ 'Access to private constant PRIVATE_FOO of class ClassConstantVisibility\Foo.', - 122, + 135, ], ]); } diff --git a/tests/PHPStan/Rules/Classes/data/access-private-constant-static.php b/tests/PHPStan/Rules/Classes/data/access-private-constant-static.php new file mode 100644 index 0000000000..84fbffba7c --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/access-private-constant-static.php @@ -0,0 +1,29 @@ + Date: Tue, 17 Aug 2021 18:37:50 +0200 Subject: [PATCH 0118/1284] Final constant rule --- conf/config.level0.neon | 1 + src/Php/PhpVersion.php | 5 ++ src/Rules/Constants/FinalConstantRule.php | 43 ++++++++++++++ .../Rules/Constants/FinalConstantRuleTest.php | 57 +++++++++++++++++++ .../Rules/Constants/data/final-constant.php | 11 ++++ 5 files changed, 117 insertions(+) create mode 100644 src/Rules/Constants/FinalConstantRule.php create mode 100644 tests/PHPStan/Rules/Constants/FinalConstantRuleTest.php create mode 100644 tests/PHPStan/Rules/Constants/data/final-constant.php diff --git a/conf/config.level0.neon b/conf/config.level0.neon index cb91d6c769..c9361622fa 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -50,6 +50,7 @@ rules: - PHPStan\Rules\Classes\NewStaticRule - PHPStan\Rules\Classes\NonClassAttributeClassRule - PHPStan\Rules\Classes\TraitAttributeClassRule + - PHPStan\Rules\Constants\FinalConstantRule - PHPStan\Rules\Exceptions\ThrowExpressionRule - PHPStan\Rules\Functions\ArrowFunctionAttributesRule - PHPStan\Rules\Functions\ArrowFunctionReturnNullsafeByRefRule diff --git a/src/Php/PhpVersion.php b/src/Php/PhpVersion.php index 688fae28ae..6bf3aa7589 100644 --- a/src/Php/PhpVersion.php +++ b/src/Php/PhpVersion.php @@ -132,4 +132,9 @@ public function isInterfaceConstantImplicitlyFinal(): bool return $this->versionId < 80100; } + public function supportsFinalConstants(): bool + { + return $this->versionId >= 80100; + } + } diff --git a/src/Rules/Constants/FinalConstantRule.php b/src/Rules/Constants/FinalConstantRule.php new file mode 100644 index 0000000000..ffbcc7d491 --- /dev/null +++ b/src/Rules/Constants/FinalConstantRule.php @@ -0,0 +1,43 @@ + */ +class FinalConstantRule implements Rule +{ + + private PhpVersion $phpVersion; + + public function __construct(PhpVersion $phpVersion) + { + $this->phpVersion = $phpVersion; + } + + public function getNodeType(): string + { + return ClassConst::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->isFinal()) { + return []; + } + + if ($this->phpVersion->supportsFinalConstants()) { + return []; + } + + return [ + RuleErrorBuilder::message('Final class constants are supported only on PHP 8.1 and later.')->nonIgnorable()->build(), + ]; + } + +} diff --git a/tests/PHPStan/Rules/Constants/FinalConstantRuleTest.php b/tests/PHPStan/Rules/Constants/FinalConstantRuleTest.php new file mode 100644 index 0000000000..df7f57f92a --- /dev/null +++ b/tests/PHPStan/Rules/Constants/FinalConstantRuleTest.php @@ -0,0 +1,57 @@ + + */ +class FinalConstantRuleTest extends RuleTestCase +{ + + /** @var int */ + private $phpVersionId; + + protected function getRule(): Rule + { + return new FinalConstantRule(new PhpVersion($this->phpVersionId)); + } + + public function dataRule(): array + { + return [ + [ + 80000, + [ + [ + 'Final class constants are supported only on PHP 8.1 and later.', + 9, + ], + ], + ], + [ + 80100, + [], + ], + ]; + } + + /** + * @dataProvider dataRule + * @param int $phpVersionId + * @param mixed[] $errors + */ + public function testRule(int $phpVersionId, array $errors): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires static reflection.'); + } + + $this->phpVersionId = $phpVersionId; + $this->analyse([__DIR__ . '/data/final-constant.php'], $errors); + } + +} diff --git a/tests/PHPStan/Rules/Constants/data/final-constant.php b/tests/PHPStan/Rules/Constants/data/final-constant.php new file mode 100644 index 0000000000..9cb37a0cb8 --- /dev/null +++ b/tests/PHPStan/Rules/Constants/data/final-constant.php @@ -0,0 +1,11 @@ + Date: Tue, 17 Aug 2021 18:54:21 +0200 Subject: [PATCH 0119/1284] Fix --- tests/PHPStan/Rules/Constants/data/final-constant.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Rules/Constants/data/final-constant.php b/tests/PHPStan/Rules/Constants/data/final-constant.php index 9cb37a0cb8..1c6bc5eab7 100644 --- a/tests/PHPStan/Rules/Constants/data/final-constant.php +++ b/tests/PHPStan/Rules/Constants/data/final-constant.php @@ -1,4 +1,4 @@ -= 8.1 namespace FinalConstant; From d78d60a286fc094593586f596c05f5ff997fe2d7 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 17 Aug 2021 23:54:17 +0200 Subject: [PATCH 0120/1284] Deprecated wrongly-named PhpPropertyReflection::hasPhpDoc() --- src/Reflection/Php/PhpPropertyReflection.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Reflection/Php/PhpPropertyReflection.php b/src/Reflection/Php/PhpPropertyReflection.php index d173ba72fd..935da1955b 100644 --- a/src/Reflection/Php/PhpPropertyReflection.php +++ b/src/Reflection/Php/PhpPropertyReflection.php @@ -129,7 +129,13 @@ public function isPromoted(): bool return $this->reflection->isPromoted(); } + /** @deprecated Use hasPhpDocType() */ public function hasPhpDoc(): bool + { + return $this->hasPhpDocType(); + } + + public function hasPhpDocType(): bool { return $this->phpDocType !== null; } From 9395ecfdc11814b5096c5d5328c0afa9bf184c0a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 18 Aug 2021 10:47:18 +0200 Subject: [PATCH 0121/1284] OverridingConstantRule - check visibility --- .../Constants/OverridingConstantRule.php | 21 ++++++++ .../Constants/OverridingConstantRuleTest.php | 21 ++++++++ .../data/overriding-final-constant.php | 50 +++++++++++++++++++ 3 files changed, 92 insertions(+) diff --git a/src/Rules/Constants/OverridingConstantRule.php b/src/Rules/Constants/OverridingConstantRule.php index 7818efa68a..336d42d69c 100644 --- a/src/Rules/Constants/OverridingConstantRule.php +++ b/src/Rules/Constants/OverridingConstantRule.php @@ -74,6 +74,27 @@ private function processSingleConstant(ClassReflection $classReflection, string ))->nonIgnorable()->build(); } + if ($prototype->isPublic()) { + if (!$constantReflection->isPublic()) { + $errors[] = RuleErrorBuilder::message(sprintf( + '%s constant %s::%s overriding public constant %s::%s should also be public.', + $constantReflection->isPrivate() ? 'Private' : 'Protected', + $constantReflection->getDeclaringClass()->getDisplayName(), + $constantReflection->getName(), + $prototype->getDeclaringClass()->getDisplayName(), + $prototype->getName() + ))->nonIgnorable()->build(); + } + } elseif ($constantReflection->isPrivate()) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Private constant %s::%s overriding protected constant %s::%s should be protected or public.', + $constantReflection->getDeclaringClass()->getDisplayName(), + $constantReflection->getName(), + $prototype->getDeclaringClass()->getDisplayName(), + $prototype->getName() + ))->nonIgnorable()->build(); + } + if (!$this->checkPhpDocMethodSignatures) { return $errors; } diff --git a/tests/PHPStan/Rules/Constants/OverridingConstantRuleTest.php b/tests/PHPStan/Rules/Constants/OverridingConstantRuleTest.php index 7fd789a6b5..24eaee4c4a 100644 --- a/tests/PHPStan/Rules/Constants/OverridingConstantRuleTest.php +++ b/tests/PHPStan/Rules/Constants/OverridingConstantRuleTest.php @@ -68,6 +68,27 @@ public function testFinal(): void 51, ]; + $errors[] = [ + 'Private constant OverridingFinalConstant\PrivateDolor::PROTECTED_CONST overriding protected constant OverridingFinalConstant\Dolor::PROTECTED_CONST should be protected or public.', + 69, + ]; + $errors[] = [ + 'Private constant OverridingFinalConstant\PrivateDolor::PUBLIC_CONST overriding public constant OverridingFinalConstant\Dolor::PUBLIC_CONST should also be public.', + 70, + ]; + $errors[] = [ + 'Private constant OverridingFinalConstant\PrivateDolor::ANOTHER_PUBLIC_CONST overriding public constant OverridingFinalConstant\Dolor::ANOTHER_PUBLIC_CONST should also be public.', + 71, + ]; + $errors[] = [ + 'Protected constant OverridingFinalConstant\ProtectedDolor::PUBLIC_CONST overriding public constant OverridingFinalConstant\Dolor::PUBLIC_CONST should also be public.', + 80, + ]; + $errors[] = [ + 'Protected constant OverridingFinalConstant\ProtectedDolor::ANOTHER_PUBLIC_CONST overriding public constant OverridingFinalConstant\Dolor::ANOTHER_PUBLIC_CONST should also be public.', + 81, + ]; + $this->analyse([__DIR__ . '/data/overriding-final-constant.php'], $errors); } diff --git a/tests/PHPStan/Rules/Constants/data/overriding-final-constant.php b/tests/PHPStan/Rules/Constants/data/overriding-final-constant.php index 2778bd0f5f..50deb4b5a6 100644 --- a/tests/PHPStan/Rules/Constants/data/overriding-final-constant.php +++ b/tests/PHPStan/Rules/Constants/data/overriding-final-constant.php @@ -51,3 +51,53 @@ class Lorem implements BarInterface const FOO = 1; } + +class Dolor +{ + + private const PRIVATE_CONST = 1; + protected const PROTECTED_CONST = 2; + public const PUBLIC_CONST = 3; + const ANOTHER_PUBLIC_CONST = 4; + +} + +class PrivateDolor extends Dolor +{ + + private const PRIVATE_CONST = 1; + private const PROTECTED_CONST = 2; // error + private const PUBLIC_CONST = 3; // error + private const ANOTHER_PUBLIC_CONST = 4; // error + +} + +class ProtectedDolor extends Dolor +{ + + protected const PRIVATE_CONST = 1; + protected const PROTECTED_CONST = 2; + protected const PUBLIC_CONST = 3; // error + protected const ANOTHER_PUBLIC_CONST = 4; // error + +} + +class PublicDolor extends Dolor +{ + + public const PRIVATE_CONST = 1; + public const PROTECTED_CONST = 2; + public const PUBLIC_CONST = 3; + public const ANOTHER_PUBLIC_CONST = 4; + +} + +class Public2Dolor extends Dolor +{ + + const PRIVATE_CONST = 1; + const PROTECTED_CONST = 2; + const PUBLIC_CONST = 3; + const ANOTHER_PUBLIC_CONST = 4; + +} From 0f4885aa101e0cc49f906e72990e356bd281ec63 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 17 Aug 2021 23:32:56 +0200 Subject: [PATCH 0122/1284] Bleeding edge - OverridingPropertyRule --- composer.json | 4 +- composer.lock | 39 ++-- conf/bleedingEdge.neon | 1 + conf/config.level0.neon | 7 + conf/config.neon | 4 +- src/Node/ClassPropertyNode.php | 5 + src/Reflection/Php/PhpPropertyReflection.php | 14 ++ .../IncompatiblePropertyPhpDocTypeRule.php | 2 +- .../Properties/OverridingPropertyRule.php | 193 ++++++++++++++++++ .../Properties/OverridingPropertyRuleTest.php | 97 +++++++++ .../Properties/data/overriding-property.php | 160 +++++++++++++++ 11 files changed, 507 insertions(+), 19 deletions(-) create mode 100644 src/Rules/Properties/OverridingPropertyRule.php create mode 100644 tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php create mode 100644 tests/PHPStan/Rules/Properties/data/overriding-property.php diff --git a/composer.json b/composer.json index ceeff9e26e..581ac9845f 100644 --- a/composer.json +++ b/composer.json @@ -21,9 +21,9 @@ "nette/neon": "^3.0", "nette/schema": "^1.0", "nette/utils": "^3.1.3", - "nikic/php-parser": "4.12.0", + "nikic/php-parser": "dev-master as 4.12.0", "ondram/ci-detector": "^3.4.0", - "ondrejmirtes/better-reflection": "4.3.64", + "ondrejmirtes/better-reflection": "4.3.65", "phpstan/php-8-stubs": "^0.1.22", "phpstan/phpdoc-parser": "^0.5.5", "react/child-process": "^0.6.1", diff --git a/composer.lock b/composer.lock index 4913070e89..3ff4e6fe3d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "8bb4c4f5674ba1245e1db1a1cad7f8e8", + "content-hash": "e7c69231bdd43f8e377c2fc08d85c590", "packages": [ { "name": "clue/block-react", @@ -1946,16 +1946,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.12.0", + "version": "dev-master", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "6608f01670c3cc5079e18c1dab1104e002579143" + "reference": "9aebf377fcdf205b2156cb78c0bd6e7b2003f106" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6608f01670c3cc5079e18c1dab1104e002579143", - "reference": "6608f01670c3cc5079e18c1dab1104e002579143", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/9aebf377fcdf205b2156cb78c0bd6e7b2003f106", + "reference": "9aebf377fcdf205b2156cb78c0bd6e7b2003f106", "shasum": "" }, "require": { @@ -1966,6 +1966,7 @@ "ircmaxell/php-yacc": "^0.0.7", "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" }, + "default-branch": true, "bin": [ "bin/php-parse" ], @@ -1996,9 +1997,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.12.0" + "source": "https://github.com/nikic/PHP-Parser/tree/master" }, - "time": "2021-07-21T10:44:31+00:00" + "time": "2021-08-08T17:12:44+00:00" }, { "name": "ondram/ci-detector", @@ -2074,16 +2075,16 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "4.3.64", + "version": "4.3.65", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "737857c9777a5f6c793935ee1bb2f72017d3a976" + "reference": "5e74a9b7ccd861481c618306d6e995b738419f35" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/737857c9777a5f6c793935ee1bb2f72017d3a976", - "reference": "737857c9777a5f6c793935ee1bb2f72017d3a976", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/5e74a9b7ccd861481c618306d6e995b738419f35", + "reference": "5e74a9b7ccd861481c618306d6e995b738419f35", "shasum": "" }, "require": { @@ -2138,9 +2139,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/4.3.64" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/4.3.65" }, - "time": "2021-08-17T15:26:08+00:00" + "time": "2021-08-18T09:23:47+00:00" }, { "name": "phpstan/php-8-stubs", @@ -6385,10 +6386,18 @@ "time": "2021-03-09T10:59:23+00:00" } ], - "aliases": [], + "aliases": [ + { + "package": "nikic/php-parser", + "version": "9999999-dev", + "alias": "4.12.0", + "alias_normalized": "4.12.0.0" + } + ], "minimum-stability": "dev", "stability-flags": { - "jetbrains/phpstorm-stubs": 20 + "jetbrains/phpstorm-stubs": 20, + "nikic/php-parser": 20 }, "prefer-stable": true, "prefer-lowest": false, diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 6120cbd670..bfdd57db17 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -26,5 +26,6 @@ parameters: finalByPhpDocTag: true classConstants: true privateStaticCall: true + overridingProperty: true stubFiles: - ../stubs/arrayFunctions.stub diff --git a/conf/config.level0.neon b/conf/config.level0.neon index c9361622fa..1ba50bab15 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -29,6 +29,8 @@ conditionalTags: phpstan.rules.rule: %featureToggles.fileWhitespace% PHPStan\Rules\Properties\UninitializedPropertyRule: phpstan.rules.rule: %checkUninitializedProperties% + PHPStan\Rules\Properties\OverridingPropertyRule: + phpstan.rules.rule: %featureToggles.overridingProperty% parametersSchema: missingClosureNativeReturnCheckObjectTypehint: bool() @@ -213,6 +215,11 @@ services: checkClassCaseSensitivity: %checkClassCaseSensitivity% checkThisOnly: %checkThisOnly% + - + class: PHPStan\Rules\Properties\OverridingPropertyRule + arguments: + checkPhpDocMethodSignatures: %checkPhpDocMethodSignatures% + - class: PHPStan\Rules\Properties\UninitializedPropertyRule arguments: diff --git a/conf/config.neon b/conf/config.neon index 932dd54217..11b72ad1b2 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -50,6 +50,7 @@ parameters: finalByPhpDocTag: false classConstants: false privateStaticCall: false + overridingProperty: false fileExtensions: - php checkAlwaysTrueCheckTypeFunctionCall: false @@ -229,7 +230,8 @@ parametersSchema: crossCheckInterfaces: bool(), finalByPhpDocTag: bool(), classConstants: bool(), - privateStaticCall: bool() + privateStaticCall: bool(), + overridingProperty: bool() ]) fileExtensions: listOf(string()) checkAlwaysTrueCheckTypeFunctionCall: bool() diff --git a/src/Node/ClassPropertyNode.php b/src/Node/ClassPropertyNode.php index 36bde885b1..d04f196489 100644 --- a/src/Node/ClassPropertyNode.php +++ b/src/Node/ClassPropertyNode.php @@ -99,6 +99,11 @@ public function isStatic(): bool return (bool) ($this->flags & Class_::MODIFIER_STATIC); } + public function isReadOnly(): bool + { + return (bool) ($this->flags & Class_::MODIFIER_READONLY); + } + /** * @return Identifier|Name|NullableType|UnionType|null */ diff --git a/src/Reflection/Php/PhpPropertyReflection.php b/src/Reflection/Php/PhpPropertyReflection.php index 935da1955b..077c1e58bd 100644 --- a/src/Reflection/Php/PhpPropertyReflection.php +++ b/src/Reflection/Php/PhpPropertyReflection.php @@ -97,6 +97,15 @@ public function isPublic(): bool return $this->reflection->isPublic(); } + public function isReadOnly(): bool + { + if (method_exists($this->reflection, 'isReadOnly')) { + return $this->reflection->isReadOnly(); + } + + return false; + } + public function getReadableType(): Type { if ($this->type === null) { @@ -149,6 +158,11 @@ public function getPhpDocType(): Type return new MixedType(); } + public function hasNativeType(): bool + { + return $this->nativeType !== null; + } + public function getNativeType(): Type { if ($this->finalNativeType === null) { diff --git a/src/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRule.php b/src/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRule.php index 74b813e37e..e2494d919f 100644 --- a/src/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRule.php +++ b/src/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRule.php @@ -43,7 +43,7 @@ public function processNode(Node $node, Scope $scope): array $propertyName = $node->getName(); $propertyReflection = $scope->getClassReflection()->getNativeProperty($propertyName); - if (!$propertyReflection->hasPhpDoc()) { + if (!$propertyReflection->hasPhpDocType()) { return []; } diff --git a/src/Rules/Properties/OverridingPropertyRule.php b/src/Rules/Properties/OverridingPropertyRule.php new file mode 100644 index 0000000000..c66b81b0b6 --- /dev/null +++ b/src/Rules/Properties/OverridingPropertyRule.php @@ -0,0 +1,193 @@ + + */ +class OverridingPropertyRule implements Rule +{ + + private bool $checkPhpDocMethodSignatures; + + public function __construct(bool $checkPhpDocMethodSignatures) + { + $this->checkPhpDocMethodSignatures = $checkPhpDocMethodSignatures; + } + + public function getNodeType(): string + { + return ClassPropertyNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$scope->isInClass()) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $classReflection = $scope->getClassReflection(); + $prototype = $this->findPrototype($classReflection, $node->getName()); + if ($prototype === null) { + return []; + } + + $errors = []; + if ($prototype->isStatic()) { + if (!$node->isStatic()) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Non-static property %s::$%s overrides static property %s::$%s.', + $classReflection->getDisplayName(), + $node->getName(), + $prototype->getDeclaringClass()->getDisplayName(), + $node->getName() + ))->nonIgnorable()->build(); + } + } elseif ($node->isStatic()) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Static property %s::$%s overrides non-static property %s::$%s.', + $classReflection->getDisplayName(), + $node->getName(), + $prototype->getDeclaringClass()->getDisplayName(), + $node->getName() + ))->nonIgnorable()->build(); + } + + if ($prototype->isReadOnly()) { + if (!$node->isReadOnly()) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Readwrite property %s::$%s overrides readonly property %s::$%s.', + $classReflection->getDisplayName(), + $node->getName(), + $prototype->getDeclaringClass()->getDisplayName(), + $node->getName() + ))->nonIgnorable()->build(); + } + } elseif ($node->isReadOnly()) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Readonly property %s::$%s overrides readwrite property %s::$%s.', + $classReflection->getDisplayName(), + $node->getName(), + $prototype->getDeclaringClass()->getDisplayName(), + $node->getName() + ))->nonIgnorable()->build(); + } + + if ($prototype->isPublic()) { + if (!$node->isPublic()) { + $errors[] = RuleErrorBuilder::message(sprintf( + '%s property %s::$%s overriding public property %s::$%s should also be public.', + $node->isPrivate() ? 'Private' : 'Protected', + $classReflection->getDisplayName(), + $node->getName(), + $prototype->getDeclaringClass()->getDisplayName(), + $node->getName() + ))->nonIgnorable()->build(); + } + } elseif ($node->isPrivate()) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Private property %s::$%s overriding protected property %s::$%s should be protected or public.', + $classReflection->getDisplayName(), + $node->getName(), + $prototype->getDeclaringClass()->getDisplayName(), + $node->getName() + ))->nonIgnorable()->build(); + } + + $typeErrors = []; + if ($prototype->hasNativeType()) { + if ($node->getNativeType() === null) { + $typeErrors[] = RuleErrorBuilder::message(sprintf( + 'Property %s::$%s overriding property %s::$%s (%s) should also have native type %s.', + $classReflection->getDisplayName(), + $node->getName(), + $prototype->getDeclaringClass()->getDisplayName(), + $node->getName(), + $prototype->getNativeType()->describe(VerbosityLevel::typeOnly()), + $prototype->getNativeType()->describe(VerbosityLevel::typeOnly()) + ))->nonIgnorable()->build(); + } else { + $nativeType = ParserNodeTypeToPHPStanType::resolve($node->getNativeType(), $scope->getClassReflection()->getName()); + if (!$prototype->getNativeType()->equals($nativeType)) { + $typeErrors[] = RuleErrorBuilder::message(sprintf( + 'Type %s of property %s::$%s is not the same as type %s of overridden property %s::$%s.', + $nativeType->describe(VerbosityLevel::typeOnly()), + $classReflection->getDisplayName(), + $node->getName(), + $prototype->getNativeType()->describe(VerbosityLevel::typeOnly()), + $prototype->getDeclaringClass()->getDisplayName(), + $node->getName() + ))->nonIgnorable()->build(); + } + } + } elseif ($node->getNativeType() !== null) { + $typeErrors[] = RuleErrorBuilder::message(sprintf( + 'Property %s::$%s (%s) overriding property %s::$%s should not have a native type.', + $classReflection->getDisplayName(), + $node->getName(), + ParserNodeTypeToPHPStanType::resolve($node->getNativeType(), $scope->getClassReflection()->getName())->describe(VerbosityLevel::typeOnly()), + $prototype->getDeclaringClass()->getDisplayName(), + $node->getName() + ))->nonIgnorable()->build(); + } + + $errors = array_merge($errors, $typeErrors); + + if (!$this->checkPhpDocMethodSignatures) { + return $errors; + } + + if (count($typeErrors) > 0) { + return $errors; + } + + $propertyReflection = $classReflection->getNativeProperty($node->getName()); + if ($propertyReflection->getReadableType()->equals($prototype->getReadableType())) { + return $errors; + } + + $verbosity = VerbosityLevel::getRecommendedLevelByType($prototype->getReadableType(), $propertyReflection->getReadableType()); + + $errors[] = RuleErrorBuilder::message(sprintf( + 'Type %s of property %s::$%s is not the same as type %s of overridden property %s::$%s.', + $propertyReflection->getReadableType()->describe($verbosity), + $classReflection->getDisplayName(), + $node->getName(), + $prototype->getReadableType()->describe($verbosity), + $prototype->getDeclaringClass()->getDisplayName(), + $node->getName() + ))->build(); + + return $errors; + } + + private function findPrototype(ClassReflection $classReflection, string $propertyName): ?PhpPropertyReflection + { + $parentClass = $classReflection->getParentClass(); + if ($parentClass === false) { + return null; + } + + if (!$parentClass->hasNativeProperty($propertyName)) { + return null; + } + + $property = $parentClass->getNativeProperty($propertyName); + if ($property->isPrivate()) { + return null; + } + + return $property; + } + +} diff --git a/tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php b/tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php new file mode 100644 index 0000000000..a05fe49500 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php @@ -0,0 +1,97 @@ + + */ +class OverridingPropertyRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new OverridingPropertyRule(true); + } + + public function testRule(): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires static reflection.'); + } + + $this->analyse([__DIR__ . '/data/overriding-property.php'], [ + [ + 'Static property OverridingProperty\Bar::$protectedFoo overrides non-static property OverridingProperty\Foo::$protectedFoo.', + 25, + ], + [ + 'Non-static property OverridingProperty\Bar::$protectedStaticFoo overrides static property OverridingProperty\Foo::$protectedStaticFoo.', + 26, + ], + [ + 'Static property OverridingProperty\Bar::$publicFoo overrides non-static property OverridingProperty\Foo::$publicFoo.', + 28, + ], + [ + 'Non-static property OverridingProperty\Bar::$publicStaticFoo overrides static property OverridingProperty\Foo::$publicStaticFoo.', + 29, + ], + [ + 'Readonly property OverridingProperty\ReadonlyChild::$readWrite overrides readwrite property OverridingProperty\ReadonlyParent::$readWrite.', + 45, + ], + [ + 'Readwrite property OverridingProperty\ReadonlyChild::$readOnly overrides readonly property OverridingProperty\ReadonlyParent::$readOnly.', + 46, + ], + [ + 'Readonly property OverridingProperty\ReadonlyChild2::$readWrite overrides readwrite property OverridingProperty\ReadonlyParent::$readWrite.', + 55, + ], + [ + 'Readwrite property OverridingProperty\ReadonlyChild2::$readOnly overrides readonly property OverridingProperty\ReadonlyParent::$readOnly.', + 56, + ], + [ + 'Private property OverridingProperty\PrivateDolor::$protectedFoo overriding protected property OverridingProperty\Dolor::$protectedFoo should be protected or public.', + 76, + ], + [ + 'Private property OverridingProperty\PrivateDolor::$publicFoo overriding public property OverridingProperty\Dolor::$publicFoo should also be public.', + 77, + ], + [ + 'Private property OverridingProperty\PrivateDolor::$anotherPublicFoo overriding public property OverridingProperty\Dolor::$anotherPublicFoo should also be public.', + 78, + ], + [ + 'Protected property OverridingProperty\ProtectedDolor::$publicFoo overriding public property OverridingProperty\Dolor::$publicFoo should also be public.', + 87, + ], + [ + 'Protected property OverridingProperty\ProtectedDolor::$anotherPublicFoo overriding public property OverridingProperty\Dolor::$anotherPublicFoo should also be public.', + 88, + ], + [ + 'Property OverridingProperty\TypeChild::$withType overriding property OverridingProperty\Typed::$withType (int) should also have native type int.', + 125, + ], + [ + 'Property OverridingProperty\TypeChild::$withoutType (int) overriding property OverridingProperty\Typed::$withoutType should not have a native type.', + 126, + ], + [ + 'Type string of property OverridingProperty\Typed2Child::$foo is not the same as type int of overridden property OverridingProperty\Typed2::$foo.', + 142, + ], + [ + 'Type 4 of property OverridingProperty\Typed2WithPhpDoc::$foo is not the same as type 1|2|3 of overridden property OverridingProperty\TypedWithPhpDoc::$foo.', + 158, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Properties/data/overriding-property.php b/tests/PHPStan/Rules/Properties/data/overriding-property.php new file mode 100644 index 0000000000..51c56ff6a1 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/overriding-property.php @@ -0,0 +1,160 @@ += 8.1 + +namespace OverridingProperty; + +class Foo +{ + + private $privateFoo; + private static $privateStaticFoo; + + protected $protectedFoo; + protected static $protectedStaticFoo; + + public $publicFoo; + public static $publicStaticFoo; + +} + +class Bar extends Foo +{ + + private static $privateFoo; + private $privateStaticFoo; + + protected static $protectedFoo; + protected $protectedStaticFoo; + + public static $publicFoo; + public $publicStaticFoo; + +} + +class ReadonlyParent +{ + + public mixed $readWrite; + public readonly mixed $readOnly; + public readonly mixed $readonly2; + +} + +class ReadonlyChild extends ReadonlyParent +{ + + public readonly mixed $readWrite; + public mixed $readOnly; + public readonly mixed $readonly2; + +} + +class ReadonlyChild2 extends ReadonlyParent +{ + + public function __construct( + public readonly mixed $readWrite, + public mixed $readOnly, + public readonly mixed $readonly2 + ) {} + +} + +class Dolor +{ + + private $privateFoo; + protected $protectedFoo; + public $publicFoo; + var $anotherPublicFoo; + +} + +class PrivateDolor extends Dolor +{ + + private $privateFoo; + private $protectedFoo; // error + private $publicFoo; // error + private $anotherPublicFoo; // error + +} + +class ProtectedDolor extends Dolor +{ + + protected $privateFoo; + protected $protectedFoo; + protected $publicFoo; // error + protected $anotherPublicFoo; // error + +} + +class PublicDolor extends Dolor +{ + + public $privateFoo; + public $protectedFoo; + public $publicFoo; + public $anotherPublicFoo; + +} + +class Public2Dolor extends Dolor +{ + + var $privateFoo; + var $protectedFoo; + var $publicFoo; + var $anotherPublicFoo; + +} + +class Typed +{ + + public int $withType; + public $withoutType; + public int $withType2; + public $withoutType2; + +} + +class TypeChild extends Typed +{ + + public $withType; // error + public int $withoutType; // error + public int $withType2; + public $withoutType2; + +} + +class Typed2 +{ + + protected int $foo; + +} + +class Typed2Child extends Typed2 +{ + + protected string $foo; + +} + +class TypedWithPhpDoc +{ + + /** @var 1|2|3 */ + protected int $foo; + +} + +class Typed2WithPhpDoc extends TypedWithPhpDoc +{ + + /** @var 4 */ + protected int $foo; + +} From 502596f1a1ee3ec319bab13462bb74294ff4f770 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 18 Aug 2021 14:50:16 +0200 Subject: [PATCH 0123/1284] ReadOnlyPropertyRule --- conf/config.level0.neon | 1 + src/Php/PhpVersion.php | 5 ++ src/Rules/Properties/ReadOnlyPropertyRule.php | 52 ++++++++++++ .../Properties/ReadOnlyPropertyRuleTest.php | 82 +++++++++++++++++++ .../Properties/data/read-only-property.php | 12 +++ 5 files changed, 152 insertions(+) create mode 100644 src/Rules/Properties/ReadOnlyPropertyRule.php create mode 100644 tests/PHPStan/Rules/Properties/ReadOnlyPropertyRuleTest.php create mode 100644 tests/PHPStan/Rules/Properties/data/read-only-property.php diff --git a/conf/config.level0.neon b/conf/config.level0.neon index 1ba50bab15..7530b8b7f8 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -75,6 +75,7 @@ rules: - PHPStan\Rules\Properties\AccessPropertiesInAssignRule - PHPStan\Rules\Properties\AccessStaticPropertiesInAssignRule - PHPStan\Rules\Properties\PropertyAttributesRule + - PHPStan\Rules\Properties\ReadOnlyPropertyRule - PHPStan\Rules\Variables\UnsetRule services: diff --git a/src/Php/PhpVersion.php b/src/Php/PhpVersion.php index 6bf3aa7589..cbb75add69 100644 --- a/src/Php/PhpVersion.php +++ b/src/Php/PhpVersion.php @@ -137,4 +137,9 @@ public function supportsFinalConstants(): bool return $this->versionId >= 80100; } + public function supportsReadOnlyProperties(): bool + { + return $this->versionId >= 80100; + } + } diff --git a/src/Rules/Properties/ReadOnlyPropertyRule.php b/src/Rules/Properties/ReadOnlyPropertyRule.php new file mode 100644 index 0000000000..16162f53bb --- /dev/null +++ b/src/Rules/Properties/ReadOnlyPropertyRule.php @@ -0,0 +1,52 @@ + + */ +class ReadOnlyPropertyRule implements Rule +{ + + private PhpVersion $phpVersion; + + public function __construct(PhpVersion $phpVersion) + { + $this->phpVersion = $phpVersion; + } + + public function getNodeType(): string + { + return ClassPropertyNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->isReadOnly()) { + return []; + } + + $errors = []; + if (!$this->phpVersion->supportsReadOnlyProperties()) { + $errors[] = RuleErrorBuilder::message('Readonly properties are supported only on PHP 8.1 and later.')->nonIgnorable()->build(); + } + + if ($node->getNativeType() === null) { + $errors[] = RuleErrorBuilder::message('Readonly property must have a native type.')->nonIgnorable()->build(); + } + + if ($node->getDefault() !== null) { + $errors[] = RuleErrorBuilder::message('Readonly property cannot have a default value.')->nonIgnorable()->build(); + } + + return $errors; + } + +} diff --git a/tests/PHPStan/Rules/Properties/ReadOnlyPropertyRuleTest.php b/tests/PHPStan/Rules/Properties/ReadOnlyPropertyRuleTest.php new file mode 100644 index 0000000000..7bcd73b7c9 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/ReadOnlyPropertyRuleTest.php @@ -0,0 +1,82 @@ + + */ +class ReadOnlyPropertyRuleTest extends RuleTestCase +{ + + /** @var int */ + private $phpVersionId; + + protected function getRule(): Rule + { + return new ReadOnlyPropertyRule(new PhpVersion($this->phpVersionId)); + } + + public function dataRule(): array + { + return [ + [ + 80000, + [ + [ + 'Readonly properties are supported only on PHP 8.1 and later.', + 8, + ], + [ + 'Readonly properties are supported only on PHP 8.1 and later.', + 9, + ], + [ + 'Readonly property must have a native type.', + 9, + ], + [ + 'Readonly properties are supported only on PHP 8.1 and later.', + 10, + ], + [ + 'Readonly property cannot have a default value.', + 10, + ], + ], + ], + [ + 80100, + [ + [ + 'Readonly property must have a native type.', + 9, + ], + [ + 'Readonly property cannot have a default value.', + 10, + ], + ], + ], + ]; + } + + /** + * @dataProvider dataRule + * @param int $phpVersionId + * @param mixed[] $errors + */ + public function testRule(int $phpVersionId, array $errors): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires static reflection.'); + } + + $this->phpVersionId = $phpVersionId; + $this->analyse([__DIR__ . '/data/read-only-property.php'], $errors); + } + +} diff --git a/tests/PHPStan/Rules/Properties/data/read-only-property.php b/tests/PHPStan/Rules/Properties/data/read-only-property.php new file mode 100644 index 0000000000..4182f194c4 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/read-only-property.php @@ -0,0 +1,12 @@ += 8.1 + +namespace ReadOnlyProperty; + +class Foo +{ + + private readonly int $foo; + private readonly $bar; + private readonly int $baz = 0; + +} From 66fb725ce66e7166d5b9faf73992b9725155015f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 18 Aug 2021 17:41:25 +0200 Subject: [PATCH 0124/1284] Microoptimization --- src/Type/ConstantTypeHelper.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Type/ConstantTypeHelper.php b/src/Type/ConstantTypeHelper.php index f8fdd0699a..4c515e4a5a 100644 --- a/src/Type/ConstantTypeHelper.php +++ b/src/Type/ConstantTypeHelper.php @@ -29,6 +29,9 @@ public static function getTypeFromValue($value): Type return new ConstantStringType($value); } elseif (is_array($value)) { $arrayBuilder = ConstantArrayTypeBuilder::createEmpty(); + if (count($value) > 256) { + $arrayBuilder->degradeToGeneralArray(); + } foreach ($value as $k => $v) { $arrayBuilder->setOffsetValueType(self::getTypeFromValue($k), self::getTypeFromValue($v)); } From c56d866e63d6cd9eb80eecc9c7f1c989b2110cd3 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 18 Aug 2021 17:42:18 +0200 Subject: [PATCH 0125/1284] Remove TypeCombinator::CONSTANT_SCALAR_UNION_THRESHOLD --- src/Type/TypeCombinator.php | 11 ----------- tests/PHPStan/Analyser/data/bug-2677.php | 2 +- tests/PHPStan/Type/TypeCombinatorTest.php | 4 ++-- 3 files changed, 3 insertions(+), 14 deletions(-) diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index 1958c21029..3e56b8c24a 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -23,8 +23,6 @@ class TypeCombinator { - private const CONSTANT_SCALAR_UNION_THRESHOLD = 8; - public static function addNull(Type $type): Type { return self::union($type, new NullType()); @@ -289,15 +287,6 @@ public static function union(Type ...$types): Type continue; } foreach ($scalarTypeItems as $type) { - if (count($scalarTypeItems) > self::CONSTANT_SCALAR_UNION_THRESHOLD) { - $types[] = $type->generalize(GeneralizePrecision::moreSpecific()); - - if ($type instanceof ConstantStringType) { - continue; - } - - break; - } $types[] = $type; } } diff --git a/tests/PHPStan/Analyser/data/bug-2677.php b/tests/PHPStan/Analyser/data/bug-2677.php index aaf1201b2f..c191dd1f3d 100644 --- a/tests/PHPStan/Analyser/data/bug-2677.php +++ b/tests/PHPStan/Analyser/data/bug-2677.php @@ -44,6 +44,6 @@ function () assertType('array(\'Bug2677\\\\A\', \'Bug2677\\\\B\', \'Bug2677\\\\C\', \'Bug2677\\\\D\', \'Bug2677\\\\E\', \'Bug2677\\\\F\', \'Bug2677\\\\G\', \'Bug2677\\\\H\', \'Bug2677\\\\I\', \'Bug2677\\\\J\', \'Bug2677\\\\K\', \'Bug2677\\\\L\', \'Bug2677\\\\M\', \'Bug2677\\\\N\', \'Bug2677\\\\O\', \'Bug2677\\\\P\')', $classes); foreach ($classes as $class) { - assertType('class-string', $class); + assertType('\'Bug2677\\\\A\'|\'Bug2677\\\\B\'|\'Bug2677\\\\C\'|\'Bug2677\\\\D\'|\'Bug2677\\\\E\'|\'Bug2677\\\\F\'|\'Bug2677\\\\G\'|\'Bug2677\\\\H\'|\'Bug2677\\\\I\'|\'Bug2677\\\\J\'|\'Bug2677\\\\K\'|\'Bug2677\\\\L\'|\'Bug2677\\\\M\'|\'Bug2677\\\\N\'|\'Bug2677\\\\O\'|\'Bug2677\\\\P\'', $class); } }; diff --git a/tests/PHPStan/Type/TypeCombinatorTest.php b/tests/PHPStan/Type/TypeCombinatorTest.php index aa3c4ac0cb..86ceaae854 100644 --- a/tests/PHPStan/Type/TypeCombinatorTest.php +++ b/tests/PHPStan/Type/TypeCombinatorTest.php @@ -841,8 +841,8 @@ public function dataUnion(): array new ConstantStringType('loremm'), new ConstantStringType('loremmm'), ], - IntersectionType::class, - 'non-empty-string', + UnionType::class, + "'bar'|'barr'|'baz'|'bazz'|'foo'|'fooo'|'lorem'|'loremm'|'loremmm'", ], [ [ From da9e0613b8c21f0263680715da6986c921a75840 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 18 Aug 2021 17:44:41 +0200 Subject: [PATCH 0126/1284] TypeCombinator::union() - optimization of constant scalar types --- src/Type/TypeCombinator.php | 198 ++++++++++++++-------- tests/PHPStan/Type/TypeCombinatorTest.php | 24 +++ 2 files changed, 151 insertions(+), 71 deletions(-) diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index 3e56b8c24a..afe9f4cb18 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -247,6 +247,10 @@ public static function union(Type ...$types): Type unset($types[$i]); } + foreach ($scalarTypes as $classType => $scalarTypeItems) { + $scalarTypes[$classType] = array_values($scalarTypeItems); + } + /** @var ArrayType[] $arrayTypes */ $arrayTypes = $arrayTypes; @@ -280,105 +284,68 @@ public static function union(Type ...$types): Type foreach ($scalarTypes as $classType => $scalarTypeItems) { if (isset($hasGenericScalarTypes[$classType])) { + unset($scalarTypes[$classType]); continue; } if ($classType === ConstantBooleanType::class && count($scalarTypeItems) === 2) { $types[] = new BooleanType(); + unset($scalarTypes[$classType]); continue; } - foreach ($scalarTypeItems as $type) { - $types[] = $type; - } - } - // transform A | A to A - // transform A | never to A - for ($i = 0; $i < count($types); $i++) { - for ($j = $i + 1; $j < count($types); $j++) { - if ($types[$i] instanceof IntegerRangeType) { - $type = $types[$i]->tryUnion($types[$j]); - if ($type !== null) { - $types[$i] = $type; - $i--; - array_splice($types, $j, 1); - continue 2; + for ($i = 0; $i < count($scalarTypeItems); $i++) { + for ($j = 0; $j < count($types); $j++) { + $compareResult = self::compareTypesInUnion($scalarTypeItems[$i], $types[$j]); + if ($compareResult === null) { + continue; } - } - if ($types[$i] instanceof SubtractableType) { - $typeWithoutSubtractedTypeA = $types[$i]->getTypeWithoutSubtractedType(); - if ($typeWithoutSubtractedTypeA instanceof MixedType && $types[$j] instanceof MixedType) { - $isSuperType = $typeWithoutSubtractedTypeA->isSuperTypeOfMixed($types[$j]); - } else { - $isSuperType = $typeWithoutSubtractedTypeA->isSuperTypeOf($types[$j]); - } - if ($isSuperType->yes()) { - $subtractedType = null; - if ($types[$j] instanceof SubtractableType) { - $subtractedType = $types[$j]->getSubtractedType(); - } - $types[$i] = self::intersectWithSubtractedType($types[$i], $subtractedType); + [$a, $b] = $compareResult; + if ($a !== null) { + $scalarTypeItems[$i] = $a; array_splice($types, $j--, 1); continue 1; } - } - - if ($types[$j] instanceof SubtractableType) { - $typeWithoutSubtractedTypeB = $types[$j]->getTypeWithoutSubtractedType(); - if ($typeWithoutSubtractedTypeB instanceof MixedType && $types[$i] instanceof MixedType) { - $isSuperType = $typeWithoutSubtractedTypeB->isSuperTypeOfMixed($types[$i]); - } else { - $isSuperType = $typeWithoutSubtractedTypeB->isSuperTypeOf($types[$i]); - } - if ($isSuperType->yes()) { - $subtractedType = null; - if ($types[$i] instanceof SubtractableType) { - $subtractedType = $types[$i]->getSubtractedType(); - } - $types[$j] = self::intersectWithSubtractedType($types[$j], $subtractedType); - array_splice($types, $i--, 1); + if ($b !== null) { + $types[$j] = $b; + array_splice($scalarTypeItems, $i--, 1); continue 2; } } + } - if ( - !$types[$j] instanceof ConstantArrayType - && $types[$j]->isSuperTypeOf($types[$i])->yes() - ) { - array_splice($types, $i--, 1); - continue 2; - } + $scalarTypes[$classType] = $scalarTypeItems; + } - if ( - !$types[$i] instanceof ConstantArrayType - && $types[$i]->isSuperTypeOf($types[$j])->yes() - ) { - array_splice($types, $j--, 1); - continue 1; + // transform A | A to A + // transform A | never to A + for ($i = 0; $i < count($types); $i++) { + for ($j = $i + 1; $j < count($types); $j++) { + $compareResult = self::compareTypesInUnion($types[$i], $types[$j]); + if ($compareResult === null) { + continue; } - if ( - $types[$i] instanceof ConstantStringType - && $types[$i]->getValue() === '' - && $types[$j]->describe(VerbosityLevel::value()) === 'non-empty-string' - ) { - $types[$i] = new StringType(); + [$a, $b] = $compareResult; + if ($a !== null) { + $types[$i] = $a; array_splice($types, $j--, 1); continue 1; } - - if ( - $types[$j] instanceof ConstantStringType - && $types[$j]->getValue() === '' - && $types[$i]->describe(VerbosityLevel::value()) === 'non-empty-string' - ) { - $types[$j] = new StringType(); + if ($b !== null) { + $types[$j] = $b; array_splice($types, $i--, 1); continue 2; } } } + foreach ($scalarTypes as $scalarTypeItems) { + foreach ($scalarTypeItems as $scalarType) { + $types[] = $scalarType; + } + } + if (count($types) === 0) { return new NeverType(); @@ -408,6 +375,95 @@ public static function union(Type ...$types): Type return new UnionType($types); } + /** + * @param Type $a + * @param Type $b + * @return array{Type, null}|array{null, Type}|null + */ + private static function compareTypesInUnion(Type $a, Type $b): ?array + { + if ($a instanceof IntegerRangeType) { + $type = $a->tryUnion($b); + if ($type !== null) { + $a = $type; + return [$a, null]; + } + } + if ($b instanceof IntegerRangeType) { + $type = $b->tryUnion($a); + if ($type !== null) { + $b = $type; + return [null, $b]; + } + } + + if ($a instanceof SubtractableType) { + $typeWithoutSubtractedTypeA = $a->getTypeWithoutSubtractedType(); + if ($typeWithoutSubtractedTypeA instanceof MixedType && $b instanceof MixedType) { + $isSuperType = $typeWithoutSubtractedTypeA->isSuperTypeOfMixed($b); + } else { + $isSuperType = $typeWithoutSubtractedTypeA->isSuperTypeOf($b); + } + if ($isSuperType->yes()) { + $subtractedType = null; + if ($b instanceof SubtractableType) { + $subtractedType = $b->getSubtractedType(); + } + $a = self::intersectWithSubtractedType($a, $subtractedType); + return [$a, null]; + } + } + + if ($b instanceof SubtractableType) { + $typeWithoutSubtractedTypeB = $b->getTypeWithoutSubtractedType(); + if ($typeWithoutSubtractedTypeB instanceof MixedType && $a instanceof MixedType) { + $isSuperType = $typeWithoutSubtractedTypeB->isSuperTypeOfMixed($a); + } else { + $isSuperType = $typeWithoutSubtractedTypeB->isSuperTypeOf($a); + } + if ($isSuperType->yes()) { + $subtractedType = null; + if ($a instanceof SubtractableType) { + $subtractedType = $a->getSubtractedType(); + } + $b = self::intersectWithSubtractedType($b, $subtractedType); + return [null, $b]; + } + } + + if ( + !$b instanceof ConstantArrayType + && $b->isSuperTypeOf($a)->yes() + ) { + return [null, $b]; + } + + if ( + !$a instanceof ConstantArrayType + && $a->isSuperTypeOf($b)->yes() + ) { + return [$a, null]; + } + + if ( + $a instanceof ConstantStringType + && $a->getValue() === '' + && $b->describe(VerbosityLevel::value()) === 'non-empty-string' + ) { + return [null, new StringType()]; + } + + if ( + $b instanceof ConstantStringType + && $b->getValue() === '' + && $a->describe(VerbosityLevel::value()) === 'non-empty-string' + ) { + return [new StringType(), null]; + } + + return null; + } + private static function unionWithSubtractedType( Type $type, ?Type $subtractedType diff --git a/tests/PHPStan/Type/TypeCombinatorTest.php b/tests/PHPStan/Type/TypeCombinatorTest.php index 86ceaae854..12bf6ee581 100644 --- a/tests/PHPStan/Type/TypeCombinatorTest.php +++ b/tests/PHPStan/Type/TypeCombinatorTest.php @@ -1780,6 +1780,30 @@ public function dataUnion(): array StringType::class, 'string', ], + [ + [ + new StringType(), + new UnionType([ + new ConstantStringType(''), + new ConstantStringType('0'), + new ConstantBooleanType(false), + ]), + ], + UnionType::class, + 'string|false', + ], + [ + [ + new IntersectionType([new StringType(), new AccessoryNonEmptyStringType()]), + new UnionType([ + new ConstantStringType(''), + new ConstantStringType('0'), + new ConstantBooleanType(false), + ]), + ], + UnionType::class, + 'string|false', + ], ]; } From d18314dfec399fd41e746a826674336a08abb5fe Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 19 Aug 2021 12:59:43 +0200 Subject: [PATCH 0127/1284] Microoptimization --- src/Type/TypeCombinator.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index afe9f4cb18..986635b1e1 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -293,22 +293,22 @@ public static function union(Type ...$types): Type continue; } - for ($i = 0; $i < count($scalarTypeItems); $i++) { - for ($j = 0; $j < count($types); $j++) { - $compareResult = self::compareTypesInUnion($scalarTypeItems[$i], $types[$j]); + for ($i = 0; $i < count($types); $i++) { + for ($j = 0; $j < count($scalarTypeItems); $j++) { + $compareResult = self::compareTypesInUnion($types[$i], $scalarTypeItems[$j]); if ($compareResult === null) { continue; } [$a, $b] = $compareResult; if ($a !== null) { - $scalarTypeItems[$i] = $a; - array_splice($types, $j--, 1); + $types[$i] = $a; + array_splice($scalarTypeItems, $j--, 1); continue 1; } if ($b !== null) { - $types[$j] = $b; - array_splice($scalarTypeItems, $i--, 1); + $scalarTypeItems[$j] = $b; + array_splice($types, $i--, 1); continue 2; } } From 96b10ea07fdfd6d7912f4ac6ef5a8bc968b67cb8 Mon Sep 17 00:00:00 2001 From: Thierry Fournier Date: Thu, 19 Aug 2021 13:54:53 +0200 Subject: [PATCH 0128/1284] fix pg_field_name prototype This patch fix pg_field_name prototype according with the PHP documentation https://www.php.net/manual/en/function.pg-field-name.php pg_field_name could return field name (string) or false on error --- resources/functionMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 67ddd3f695..9f7282a0a0 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -8534,7 +8534,7 @@ 'pg_fetch_row' => ['array|false', 'result'=>'resource', 'row='=>'?int', 'result_type='=>'int'], 'pg_field_is_null' => ['int|false', 'result'=>'', 'field_name_or_number'=>'string|int'], 'pg_field_is_null\'1' => ['int', 'result'=>'', 'row'=>'int', 'field_name_or_number'=>'string|int'], -'pg_field_name' => ['string', 'result'=>'resource', 'field_number'=>'int'], +'pg_field_name' => ['string|false', 'result'=>'resource', 'field_number'=>'int'], 'pg_field_num' => ['int', 'result'=>'resource', 'field_name'=>'string'], 'pg_field_prtlen' => ['int|false', 'result'=>'', 'field_name_or_number'=>''], 'pg_field_prtlen\'1' => ['int', 'result'=>'', 'row'=>'int', 'field_name_or_number'=>'string|int'], From 5be8e54354566abd3c90b499d1ddd97f257566aa Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 19 Aug 2021 15:25:30 +0200 Subject: [PATCH 0129/1284] Use IntegerRangeType in modulo-operator --- src/Analyser/MutatingScope.php | 42 +++++++++++++++++++ .../Analyser/NodeScopeResolverTest.php | 1 + .../PHPStan/Analyser/data/modulo-operator.php | 42 +++++++++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 tests/PHPStan/Analyser/data/modulo-operator.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 68de2a8a41..af97c27102 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -1111,6 +1111,48 @@ private function resolveType(Expr $node): Type } if ($node instanceof Node\Expr\BinaryOp\Mod || $node instanceof Expr\AssignOp\Mod) { + if ($node instanceof Node\Expr\AssignOp) { + $left = $node->var; + $right = $node->expr; + } else { + $left = $node->left; + $right = $node->right; + } + + $leftType = $this->getType($left); + $rightType = $this->getType($right); + + $integer = new IntegerType(); + $positiveInt = IntegerRangeType::fromInterval(0, null); + if ($integer->isSuperTypeOf($rightType)->yes()) { + $rangeMin = null; + $rangeMax = null; + + if ($rightType instanceof IntegerRangeType) { + $rangeMax = $rightType->getMax() !== null ? $rightType->getMax() - 1 : null; + } elseif ($rightType instanceof ConstantIntegerType) { + $rangeMax = $rightType->getValue() - 1; + } elseif ($rightType instanceof UnionType) { + foreach ($rightType->getTypes() as $type) { + if ($type instanceof IntegerRangeType) { + $rangeMax = max($rangeMax, $type->getMax() !== null ? $type->getMax() - 1 : null); + } elseif ($type instanceof ConstantIntegerType) { + $rangeMax = max($rangeMax, $type->getValue() - 1); + } + } + } + + if ($positiveInt->isSuperTypeOf($leftType)->yes()) { + $rangeMin = 0; + } elseif ($rangeMax !== null) { + $rangeMin = $rangeMax * -1; + } + + return IntegerRangeType::fromInterval($rangeMin, $rangeMax); + } elseif ($positiveInt->isSuperTypeOf($leftType)->yes()) { + return IntegerRangeType::fromInterval(0, null); + } + return new IntegerType(); } diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 00dc0ffa27..b3dabbf74e 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -463,6 +463,7 @@ public function dataFileAsserts(): iterable } yield from $this->gatherAssertTypes(__DIR__ . '/data/class-constant-types.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/modulo-operator.php'); } /** diff --git a/tests/PHPStan/Analyser/data/modulo-operator.php b/tests/PHPStan/Analyser/data/modulo-operator.php new file mode 100644 index 0000000000..1bdeac1872 --- /dev/null +++ b/tests/PHPStan/Analyser/data/modulo-operator.php @@ -0,0 +1,42 @@ + $range + * @param int<0, max> $zeroOrMore + * @param 1|2|3 $intConst + * @param int|int<4, max> $unionRange + * @param int|7 $hybridUnionRange + */ + function doBar(int $i, $p, $range, $zeroOrMore, $intConst, $unionRange, $hybridUnionRange, $mixed) + { + assertType('int<-1, 1>', $i % 2); + assertType('int<0, 1>', $p % 2); + + assertType('int<-2, 2>', $i % 3); + assertType('int<0, 2>', $p % 3); + + assertType('0|1|2', $intConst % 3); + assertType('int<-2, 2>', $i % $intConst); + assertType('int<0, 2>', $p % $intConst); + + assertType('int<0, 2>', $range % 3); + + assertType('int<-9, 9>', $i % $range); + assertType('int<0, 9>', $p % $range); + + assertType('int', $i % $unionRange); + assertType('int<0, max>', $p % $unionRange); + + assertType('int<-6, 6>', $i % $hybridUnionRange); + assertType('int<0, 6>', $p % $hybridUnionRange); + + assertType('int<0, max>', $zeroOrMore % $mixed); + } +} From 92aaf3f614f7b786cb5c5b83e5b17255d67e8d51 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 19 Aug 2021 18:29:57 +0200 Subject: [PATCH 0130/1284] MissingClassConstantTypehintRule --- conf/config.level6.neon | 1 + .../MissingClassConstantTypehintRule.php | 88 +++++++++++++++++++ .../MissingClassConstantTypehintRuleTest.php | 40 +++++++++ .../data/missing-class-constant-typehint.php | 28 ++++++ 4 files changed, 157 insertions(+) create mode 100644 src/Rules/Constants/MissingClassConstantTypehintRule.php create mode 100644 tests/PHPStan/Rules/Constants/MissingClassConstantTypehintRuleTest.php create mode 100644 tests/PHPStan/Rules/Constants/data/missing-class-constant-typehint.php diff --git a/conf/config.level6.neon b/conf/config.level6.neon index 4fdeb19345..05f3616832 100644 --- a/conf/config.level6.neon +++ b/conf/config.level6.neon @@ -8,6 +8,7 @@ parameters: checkMissingTypehints: true rules: + - PHPStan\Rules\Constants\MissingClassConstantTypehintRule - PHPStan\Rules\Functions\MissingFunctionParameterTypehintRule - PHPStan\Rules\Functions\MissingFunctionReturnTypehintRule - PHPStan\Rules\Methods\MissingMethodParameterTypehintRule diff --git a/src/Rules/Constants/MissingClassConstantTypehintRule.php b/src/Rules/Constants/MissingClassConstantTypehintRule.php new file mode 100644 index 0000000000..f28cd71a76 --- /dev/null +++ b/src/Rules/Constants/MissingClassConstantTypehintRule.php @@ -0,0 +1,88 @@ + + */ +final class MissingClassConstantTypehintRule implements \PHPStan\Rules\Rule +{ + + private \PHPStan\Rules\MissingTypehintCheck $missingTypehintCheck; + + public function __construct(MissingTypehintCheck $missingTypehintCheck) + { + $this->missingTypehintCheck = $missingTypehintCheck; + } + + public function getNodeType(): string + { + return Node\Stmt\ClassConst::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$scope->isInClass()) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $errors = []; + foreach ($node->consts as $const) { + $constantName = $const->name->toString(); + $errors = array_merge($errors, $this->processSingleConstant($scope->getClassReflection(), $constantName)); + } + + return $errors; + } + + /** + * @param string $constantName + * @return RuleError[] + */ + private function processSingleConstant(ClassReflection $classReflection, string $constantName): array + { + $constantReflection = $classReflection->getConstant($constantName); + $constantType = $constantReflection->getValueType(); + + $errors = []; + foreach ($this->missingTypehintCheck->getIterableTypesWithMissingValueTypehint($constantType) as $iterableType) { + $iterableTypeDescription = $iterableType->describe(VerbosityLevel::typeOnly()); + $errors[] = RuleErrorBuilder::message(sprintf( + 'Constant %s::%s type has no value type specified in iterable type %s.', + $constantReflection->getDeclaringClass()->getDisplayName(), + $constantName, + $iterableTypeDescription + ))->tip(MissingTypehintCheck::TURN_OFF_MISSING_ITERABLE_VALUE_TYPE_TIP)->build(); + } + + foreach ($this->missingTypehintCheck->getNonGenericObjectTypesWithGenericClass($constantType) as [$name, $genericTypeNames]) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Constant %s::%s with generic %s does not specify its types: %s', + $constantReflection->getDeclaringClass()->getDisplayName(), + $constantName, + $name, + implode(', ', $genericTypeNames) + ))->tip(MissingTypehintCheck::TURN_OFF_NON_GENERIC_CHECK_TIP)->build(); + } + + foreach ($this->missingTypehintCheck->getCallablesWithMissingSignature($constantType) as $callableType) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Constant %s::%s type has no signature specified for %s.', + $constantReflection->getDeclaringClass()->getDisplayName(), + $constantName, + $callableType->describe(VerbosityLevel::typeOnly()) + ))->build(); + } + + return $errors; + } + +} diff --git a/tests/PHPStan/Rules/Constants/MissingClassConstantTypehintRuleTest.php b/tests/PHPStan/Rules/Constants/MissingClassConstantTypehintRuleTest.php new file mode 100644 index 0000000000..f084566139 --- /dev/null +++ b/tests/PHPStan/Rules/Constants/MissingClassConstantTypehintRuleTest.php @@ -0,0 +1,40 @@ + + */ +class MissingClassConstantTypehintRuleTest extends RuleTestCase +{ + + protected function getRule(): \PHPStan\Rules\Rule + { + $reflectionProvider = $this->createReflectionProvider(); + return new MissingClassConstantTypehintRule(new MissingTypehintCheck($reflectionProvider, true, true, true)); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/missing-class-constant-typehint.php'], [ + [ + 'Constant MissingClassConstantTypehint\Foo::BAR type has no value type specified in iterable type array.', + 11, + 'See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type', + ], + [ + 'Constant MissingClassConstantTypehint\Foo::BAZ with generic class MissingClassConstantTypehint\Bar does not specify its types: T', + 17, + 'You can turn this off by setting checkGenericClassInNonGenericObjectType: false in your %configurationFile%.', + ], + [ + 'Constant MissingClassConstantTypehint\Foo::LOREM type has no signature specified for callable.', + 20, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Constants/data/missing-class-constant-typehint.php b/tests/PHPStan/Rules/Constants/data/missing-class-constant-typehint.php new file mode 100644 index 0000000000..6d639da39f --- /dev/null +++ b/tests/PHPStan/Rules/Constants/data/missing-class-constant-typehint.php @@ -0,0 +1,28 @@ + Date: Thu, 19 Aug 2021 22:53:37 +0200 Subject: [PATCH 0131/1284] Fix Phar/PharData::extractTo type information --- resources/functionMap.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 9f7282a0a0..a939b2eb60 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -8628,7 +8628,7 @@ 'Phar::decompressFiles' => ['bool'], 'Phar::delete' => ['bool', 'entry'=>'string'], 'Phar::delMetadata' => ['bool'], -'Phar::extractTo' => ['bool', 'pathto'=>'string', 'files='=>'string|array', 'overwrite='=>'bool'], +'Phar::extractTo' => ['bool', 'pathto'=>'string', 'files='=>'string|array|null', 'overwrite='=>'bool'], 'Phar::getAlias' => ['string'], 'Phar::getMetadata' => ['mixed'], 'Phar::getModified' => ['bool'], @@ -8679,7 +8679,7 @@ 'PharData::decompressFiles' => ['bool'], 'PharData::delete' => ['bool', 'entry'=>'string'], 'PharData::delMetadata' => ['bool'], -'PharData::extractTo' => ['bool', 'pathto'=>'string', 'files='=>'string|array', 'overwrite='=>'bool'], +'PharData::extractTo' => ['bool', 'pathto'=>'string', 'files='=>'string|array|null', 'overwrite='=>'bool'], 'PharData::isWritable' => ['bool'], 'PharData::offsetGet' => ['PharFileInfo', 'offset'=>'string'], 'PharData::offsetSet' => ['', 'offset'=>'string', 'value'=>'string'], From d5b2c886d4fd2596bb2cb8cc34eb956a21eff546 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 20 Aug 2021 10:00:15 +0200 Subject: [PATCH 0132/1284] More precise abs() return-type --- resources/functionMap.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index a939b2eb60..704dc36f96 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -58,9 +58,9 @@ return [ '_' => ['string', 'message'=>'string'], '__halt_compiler' => ['void'], -'abs' => ['int', 'number'=>'int'], +'abs' => ['0|positive-int', 'number'=>'int'], 'abs\'1' => ['float', 'number'=>'float'], -'abs\'2' => ['float|int', 'number'=>'string'], +'abs\'2' => ['float|0|positive-int', 'number'=>'string'], 'accelerator_get_configuration' => ['array'], 'accelerator_get_scripts' => ['array'], 'accelerator_get_status' => ['array', 'fetch_scripts'=>'bool'], From 2f991d19edfb2a483a6dfe1efa32c3c576d4edfe Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 20 Aug 2021 11:39:01 +0200 Subject: [PATCH 0133/1284] StrlenFunctionReturnTypeExtension: cover more scalar types --- .../Php/StrlenFunctionReturnTypeExtension.php | 38 ++++++++++++++++++- tests/PHPStan/Analyser/data/bug-5129.php | 2 +- .../Analyser/data/non-empty-string.php | 7 +++- 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/src/Type/Php/StrlenFunctionReturnTypeExtension.php b/src/Type/Php/StrlenFunctionReturnTypeExtension.php index da048a9556..0f432c8c62 100644 --- a/src/Type/Php/StrlenFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrlenFunctionReturnTypeExtension.php @@ -6,9 +6,12 @@ use PHPStan\Analyser\Scope; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\ParametersAcceptorSelector; +use PHPStan\Type\BooleanType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\IntegerRangeType; +use PHPStan\Type\IntegerType; +use PHPStan\Type\TypeUtils; class StrlenFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { @@ -30,8 +33,41 @@ public function getTypeFromFunctionCall( } $argType = $scope->getType($args[0]->value); + + $constantStrings = TypeUtils::getConstantStrings($argType); + $min = null; + $max = null; + foreach ($constantStrings as $constantString) { + $len = strlen($constantString->getValue()); + + if ($min === null) { + $min = $len; + $max = $len; + } + + if ($len < $min) { + $min = $len; + } + if ($len <= $max) { + continue; + } + + $max = $len; + } + + // $max is always != null, when $min is != null + if ($min !== null) { + return IntegerRangeType::fromInterval($min, $max); + } + + $bool = new BooleanType(); + if ($bool->isSuperTypeOf($argType)->yes()) { + return IntegerRangeType::fromInterval(0, 1); + } + $isNonEmpty = $argType->isNonEmptyString(); - if ($isNonEmpty->yes()) { + $integer = new IntegerType(); + if ($isNonEmpty->yes() || $integer->isSuperTypeOf($argType)->yes()) { return IntegerRangeType::fromInterval(1, null); } diff --git a/tests/PHPStan/Analyser/data/bug-5129.php b/tests/PHPStan/Analyser/data/bug-5129.php index 205faaaeab..925594b386 100644 --- a/tests/PHPStan/Analyser/data/bug-5129.php +++ b/tests/PHPStan/Analyser/data/bug-5129.php @@ -20,7 +20,7 @@ public function sayHello(string $s): void assertType('0', strlen($this->foo)); $this->foo = 'x'; - assertType('int<1, max>', strlen($this->foo)); + assertType('1', strlen($this->foo)); if (strlen($this->foo) > 0) { return; } diff --git a/tests/PHPStan/Analyser/data/non-empty-string.php b/tests/PHPStan/Analyser/data/non-empty-string.php index a951ca8af0..c9b1b8cfa0 100644 --- a/tests/PHPStan/Analyser/data/non-empty-string.php +++ b/tests/PHPStan/Analyser/data/non-empty-string.php @@ -302,8 +302,9 @@ class MoreNonEmptyStringFunctions /** * @param non-empty-string $nonEmpty + * @param '1'|'2'|'5'|'10' $constUnion */ - public function doFoo(string $s, string $nonEmpty, int $i) + public function doFoo(string $s, string $nonEmpty, int $i, bool $bool, $constUnion) { assertType('string', addslashes($s)); assertType('non-empty-string', addslashes($nonEmpty)); @@ -349,8 +350,12 @@ public function doFoo(string $s, string $nonEmpty, int $i) assertType('non-empty-string', vsprintf($nonEmpty, [])); assertType('0', strlen('')); + assertType('5', strlen('hallo')); + assertType('int<0, 1>', strlen($bool)); + assertType('int<1, max>', strlen($i)); assertType('int<0, max>', strlen($s)); assertType('int<1, max>', strlen($nonEmpty)); + assertType('int<1, 2>', strlen($constUnion)); assertType('non-empty-string', str_pad($nonEmpty, 0)); assertType('non-empty-string', str_pad($nonEmpty, 1)); From b410b1332318236a990ba42a42970f31b754ee86 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 20 Aug 2021 12:02:59 +0200 Subject: [PATCH 0134/1284] fix modulo operator when handling integer-ranges without 0 --- src/Analyser/MutatingScope.php | 6 +++++- tests/PHPStan/Analyser/data/modulo-operator.php | 8 +++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index af97c27102..a84eacfa9d 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -1135,7 +1135,11 @@ private function resolveType(Expr $node): Type } elseif ($rightType instanceof UnionType) { foreach ($rightType->getTypes() as $type) { if ($type instanceof IntegerRangeType) { - $rangeMax = max($rangeMax, $type->getMax() !== null ? $type->getMax() - 1 : null); + if ($type->getMax() === null) { + $rangeMax = null; + } else { + $rangeMax = max($rangeMax, $type->getMax()); + } } elseif ($type instanceof ConstantIntegerType) { $rangeMax = max($rangeMax, $type->getValue() - 1); } diff --git a/tests/PHPStan/Analyser/data/modulo-operator.php b/tests/PHPStan/Analyser/data/modulo-operator.php index 1bdeac1872..6e432eb259 100644 --- a/tests/PHPStan/Analyser/data/modulo-operator.php +++ b/tests/PHPStan/Analyser/data/modulo-operator.php @@ -14,7 +14,7 @@ class Foo * @param int|int<4, max> $unionRange * @param int|7 $hybridUnionRange */ - function doBar(int $i, $p, $range, $zeroOrMore, $intConst, $unionRange, $hybridUnionRange, $mixed) + function doBar(int $i, int $j, $p, $range, $zeroOrMore, $intConst, $unionRange, $hybridUnionRange, $mixed) { assertType('int<-1, 1>', $i % 2); assertType('int<0, 1>', $p % 2); @@ -38,5 +38,11 @@ function doBar(int $i, $p, $range, $zeroOrMore, $intConst, $unionRange, $hybridU assertType('int<0, 6>', $p % $hybridUnionRange); assertType('int<0, max>', $zeroOrMore % $mixed); + + if ($i === 0) { + return; + } + + assertType('int', $j % $i); } } From 15a0ff0e02add7fda577cd9505c4fda2bbe9738d Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 20 Aug 2021 12:28:13 +0200 Subject: [PATCH 0135/1284] str_split() length parameter is always positive --- resources/functionMap.php | 2 +- resources/functionMap_php74delta.php | 2 +- resources/functionMap_php80delta.php | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 704dc36f96..8e058d7419 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -11696,7 +11696,7 @@ 'str_replace' => ['string|array', 'search'=>'string|array', 'replace'=>'string|array', 'subject'=>'string|array', '&w_replace_count='=>'int'], 'str_rot13' => ['string', 'str'=>'string'], 'str_shuffle' => ['string', 'str'=>'string'], -'str_split' => ['array|false', 'str'=>'string', 'split_length='=>'int'], +'str_split' => ['array|false', 'str'=>'string', 'split_length='=>'positive-int'], 'str_word_count' => ['array|int|false', 'string'=>'string', 'format='=>'int', 'charlist='=>'string'], 'strcasecmp' => ['int', 'str1'=>'string', 'str2'=>'string'], 'strchr' => ['string|false', 'haystack'=>'string', 'needle'=>'string', 'before_needle='=>'bool'], diff --git a/resources/functionMap_php74delta.php b/resources/functionMap_php74delta.php index 73349639fe..ec728c0afa 100644 --- a/resources/functionMap_php74delta.php +++ b/resources/functionMap_php74delta.php @@ -39,7 +39,7 @@ 'FFI::typeof' => ['FFI\CType', '&ptr'=>'FFI\CData'], 'FFI::type' => ['FFI\CType', 'type'=>'string'], 'get_mangled_object_vars' => ['array', 'obj'=>'object'], - 'mb_str_split' => ['array|false', 'str'=>'string', 'split_length='=>'int', 'encoding='=>'string'], + 'mb_str_split' => ['array|false', 'str'=>'string', 'split_length='=>'positive-int', 'encoding='=>'string'], 'password_algos' => ['array'], 'password_hash' => ['string|false', 'password'=>'string', 'algo'=>'string|null', 'options='=>'array'], 'password_needs_rehash' => ['bool', 'hash'=>'string', 'algo'=>'string|null', 'options='=>'array'], diff --git a/resources/functionMap_php80delta.php b/resources/functionMap_php80delta.php index f1735f2a4e..7529a42ded 100644 --- a/resources/functionMap_php80delta.php +++ b/resources/functionMap_php80delta.php @@ -69,7 +69,7 @@ 'imagerotate' => ['false|object', 'src_im'=>'resource', 'angle'=>'float', 'bgdcolor'=>'int', 'ignoretransparent='=>'int'], 'imagescale' => ['false|object', 'im'=>'resource', 'new_width'=>'int', 'new_height='=>'int', 'method='=>'int'], 'mb_decode_numericentity' => ['string|false', 'string'=>'string', 'convmap'=>'array', 'encoding='=>'string'], - 'mb_str_split' => ['array', 'str'=>'string', 'split_length='=>'int', 'encoding='=>'string'], + 'mb_str_split' => ['array', 'str'=>'string', 'split_length='=>'positive-int', 'encoding='=>'string'], 'mktime' => ['int|false', 'hour'=>'int', 'minute='=>'int', 'second='=>'int', 'month='=>'int', 'day='=>'int', 'year='=>'int'], 'odbc_exec' => ['resource|false', 'connection_id'=>'resource', 'query'=>'string'], 'parse_str' => ['void', 'encoded_string'=>'string', '&w_result'=>'array'], @@ -83,7 +83,7 @@ 'socket_select' => ['int|false', '&rw_read'=>'Socket[]|null', '&rw_write'=>'Socket[]|null', '&rw_except'=>'Socket[]|null', 'seconds'=>'int|null', 'microseconds='=>'int'], 'sodium_crypto_aead_chacha20poly1305_ietf_decrypt' => ['string|false', 'confidential_message'=>'string', 'public_message'=>'string', 'nonce'=>'string', 'key'=>'string'], 'str_contains' => ['bool', 'haystack'=>'string', 'needle'=>'string'], - 'str_split' => ['array', 'str'=>'string', 'split_length='=>'int'], + 'str_split' => ['array', 'str'=>'string', 'split_length='=>'positive-int'], 'str_ends_with' => ['bool', 'haystack'=>'string', 'needle'=>'string'], 'str_starts_with' => ['bool', 'haystack'=>'string', 'needle'=>'string'], 'strchr' => ['string|false', 'haystack'=>'string', 'needle'=>'string', 'before_needle='=>'bool'], From 458f5d26880b10260cc6cfbeb1b78511f9336a2b Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 20 Aug 2021 13:06:01 +0200 Subject: [PATCH 0136/1284] support integer-range type in min/max for two arguments --- .../Php/MinMaxFunctionReturnTypeExtension.php | 29 ++++++++++++++++- tests/PHPStan/Analyser/data/minmax-arrays.php | 32 +++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/src/Type/Php/MinMaxFunctionReturnTypeExtension.php b/src/Type/Php/MinMaxFunctionReturnTypeExtension.php index e710b9a8e3..355acdbc89 100644 --- a/src/Type/Php/MinMaxFunctionReturnTypeExtension.php +++ b/src/Type/Php/MinMaxFunctionReturnTypeExtension.php @@ -2,7 +2,9 @@ namespace PHPStan\Type\Php; +use PhpParser\Node\Expr\BinaryOp\Smaller; use PhpParser\Node\Expr\FuncCall; +use PhpParser\Node\Expr\Ternary; use PHPStan\Analyser\Scope; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\ParametersAcceptorSelector; @@ -64,6 +66,31 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, return new ErrorType(); } + // rewrite min($x, $y) as $x < $y ? $x : $y + // we don't handle arrays, which have different semantics + $functionName = $functionReflection->getName(); + $args = $functionCall->args; + if (count($functionCall->args) === 2) { + $argType0 = $scope->getType($args[0]->value); + $argType1 = $scope->getType($args[1]->value); + + if ($argType0->isArray()->no() && $argType1->isArray()->no()) { + if ($functionName === 'min') { + return $scope->getType(new Ternary( + new Smaller($args[0]->value, $args[1]->value), + $args[0]->value, + $args[1]->value + )); + } elseif ($functionName === 'max') { + return $scope->getType(new Ternary( + new Smaller($args[0]->value, $args[1]->value), + $args[1]->value, + $args[0]->value + )); + } + } + } + $argumentTypes = []; foreach ($functionCall->args as $arg) { $argType = $scope->getType($arg->value); @@ -83,7 +110,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, } return $this->processType( - $functionReflection->getName(), + $functionName, $argumentTypes ); } diff --git a/tests/PHPStan/Analyser/data/minmax-arrays.php b/tests/PHPStan/Analyser/data/minmax-arrays.php index 37c65e0c5a..0f9ca3aede 100644 --- a/tests/PHPStan/Analyser/data/minmax-arrays.php +++ b/tests/PHPStan/Analyser/data/minmax-arrays.php @@ -118,3 +118,35 @@ function dummy5(int $i, int $j): void function dummy6(string $s, string $t): void { assertType('array(?0 => non-empty-string, ?1 => non-empty-string)', array_filter([$s, $t])); } + +class HelloWorld +{ + public function setRange(int $range): void + { + if ($range < 0) { + return; + } + assertType('int<0, 100>', min($range, 100)); + assertType('int<0, 100>', min(100, $range)); + } + + public function setRange2(int $range): void + { + if ($range > 100) { + return; + } + assertType('int<0, 100>', max($range, 0)); + assertType('int<0, 100>', max(0, $range)); + } + + public function boundRange(): void + { + /** + * @var int<1, 6> $range + */ + $range = getFoo(); + + assertType('int<1, 4>', min($range, 4)); + assertType('int<4, 6>', max(4, $range)); + } +} From fdf732c64c9a494d5fb4995050afd03cae6400b7 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 20 Aug 2021 13:07:47 +0200 Subject: [PATCH 0137/1284] `array_fill()`: handle negative cases, support integer ranges and `non-empty-array` --- resources/functionMap_php80delta.php | 2 ++ .../ParentDirectoryRelativePathHelper.php | 3 ++ .../ArrayFillFunctionReturnTypeExtension.php | 34 ++++++++++++++++--- .../Analyser/LegacyNodeScopeResolverTest.php | 24 +++++++++++++ .../PHPStan/Analyser/data/array-functions.php | 9 +++++ tests/PHPStan/Parallel/SchedulerTest.php | 2 +- 6 files changed, 69 insertions(+), 5 deletions(-) diff --git a/resources/functionMap_php80delta.php b/resources/functionMap_php80delta.php index 7529a42ded..eaf5385734 100644 --- a/resources/functionMap_php80delta.php +++ b/resources/functionMap_php80delta.php @@ -22,6 +22,7 @@ return [ 'new' => [ 'array_combine' => ['associative-array', 'keys'=>'string[]|int[]', 'values'=>'array'], + 'array_fill' => ['array', 'start_key'=>'int', 'num'=>'0|positive-int', 'val'=>'mixed'], 'bcdiv' => ['string', 'dividend'=>'string', 'divisor'=>'string', 'scale='=>'int'], 'bcmod' => ['string', 'dividend'=>'string', 'divisor'=>'string', 'scale='=>'int'], 'bcpowmod' => ['string', 'base'=>'string', 'exponent'=>'string', 'modulus'=>'string', 'scale='=>'int'], @@ -146,6 +147,7 @@ 'old' => [ 'array_combine' => ['associative-array|false', 'keys'=>'string[]|int[]', 'values'=>'array'], + 'array_fill' => ['array', 'start_key'=>'int', 'num'=>'int', 'val'=>'mixed'], 'bcdiv' => ['?string', 'dividend'=>'string', 'divisor'=>'string', 'scale='=>'int'], 'bcmod' => ['?string', 'dividend'=>'string', 'divisor'=>'string', 'scale='=>'int'], 'bcpowmod' => ['?string', 'base'=>'string', 'exponent'=>'string', 'modulus'=>'string', 'scale='=>'int'], diff --git a/src/File/ParentDirectoryRelativePathHelper.php b/src/File/ParentDirectoryRelativePathHelper.php index 306f8f0dbf..b88c930758 100644 --- a/src/File/ParentDirectoryRelativePathHelper.php +++ b/src/File/ParentDirectoryRelativePathHelper.php @@ -54,6 +54,9 @@ public function getFilenameParts(string $filename): array } $dotsCount = $parentPartsCount - $i; + if ($dotsCount < 0) { + throw new \PHPStan\ShouldNotHappenException(); + } return array_merge(array_fill(0, $dotsCount, '..'), array_slice($filenameParts, $i)); } diff --git a/src/Type/Php/ArrayFillFunctionReturnTypeExtension.php b/src/Type/Php/ArrayFillFunctionReturnTypeExtension.php index c1a8c168dd..ab42e9a5d9 100644 --- a/src/Type/Php/ArrayFillFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayFillFunctionReturnTypeExtension.php @@ -4,21 +4,33 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\Php\PhpVersion; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\ArrayType; use PHPStan\Type\Constant\ConstantArrayTypeBuilder; +use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantIntegerType; +use PHPStan\Type\IntegerRangeType; use PHPStan\Type\IntegerType; use PHPStan\Type\IntersectionType; +use PHPStan\Type\NeverType; use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; class ArrayFillFunctionReturnTypeExtension implements \PHPStan\Type\DynamicFunctionReturnTypeExtension { private const MAX_SIZE_USE_CONSTANT_ARRAY = 100; + private PhpVersion $phpVersion; + + public function __construct(PhpVersion $phpVersion) + { + $this->phpVersion = $phpVersion; + } + public function isFunctionSupported(FunctionReflection $functionReflection): bool { return $functionReflection->getName() === 'array_fill'; @@ -34,6 +46,23 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $numberType = $scope->getType($functionCall->args[1]->value); $valueType = $scope->getType($functionCall->args[2]->value); + if ($numberType instanceof IntegerRangeType) { + if ($numberType->getMin() < 0) { + return TypeCombinator::union( + new ArrayType(new IntegerType(), $valueType), + new ConstantBooleanType(false) + ); + } + } + + // check against negative-int, which is not allowed + if (IntegerRangeType::fromInterval(null, -1)->isSuperTypeOf($numberType)->yes()) { + if ($this->phpVersion->throwsValueErrorForInternalFunctions()) { + return new NeverType(); + } + return new ConstantBooleanType(false); + } + if ( $startIndexType instanceof ConstantIntegerType && $numberType instanceof ConstantIntegerType @@ -56,10 +85,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, return $arrayBuilder->getArray(); } - if ( - $numberType instanceof ConstantIntegerType - && $numberType->getValue() > 0 - ) { + if (IntegerRangeType::fromInterval(1, null)->isSuperTypeOf($numberType)->yes()) { return new IntersectionType([ new ArrayType(new IntegerType(), $valueType), new NonEmptyArrayType(), diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 27983a2c6a..6971ea40f0 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -5100,10 +5100,34 @@ public function dataArrayFunctions(): array 'array(1, 1, 1, 1, 1)', '$filledIntegers', ], + [ + 'array()', + '$emptyFilled', + ], [ 'array(1)', '$filledIntegersWithKeys', ], + [ + 'array&nonEmpty', + '$filledNonEmptyArray', + ], + [ + PHP_VERSION_ID < 80000 ? 'false' : '*NEVER*', + '$filledAlwaysFalse', + ], + [ + PHP_VERSION_ID < 80000 ? 'false' : '*NEVER*', + '$filledNegativeConstAlwaysFalse', + ], + [ + 'array|false', + '$filledByMaybeNegativeRange', + ], + [ + 'array&nonEmpty', + '$filledByPositiveRange', + ], [ 'array(1, 2)', 'array_keys($integerKeys)', diff --git a/tests/PHPStan/Analyser/data/array-functions.php b/tests/PHPStan/Analyser/data/array-functions.php index 752fe4fd07..15d7252487 100644 --- a/tests/PHPStan/Analyser/data/array-functions.php +++ b/tests/PHPStan/Analyser/data/array-functions.php @@ -37,7 +37,16 @@ }, 1); $filledIntegers = array_fill(0, 5, 1); +$emptyFilled = array_fill(3, 0, 'banana'); $filledIntegersWithKeys = array_fill_keys([0], 1); +/** @var negative-int $negInt */ +$filledAlwaysFalse = array_fill(0, $negInt, 1); +/** @var positive-int $posInt */ +$filledNonEmptyArray = array_fill(0, $posInt, 'foo'); +$filledNegativeConstAlwaysFalse = array_fill(0, -5, 1); +/** @var int<-3, 5> $maybeNegRange */ +$filledByMaybeNegativeRange = array_fill(0, $maybeNegRange, 1); +$filledByPositiveRange = array_fill(0, rand(3, 5), 1); $integerKeys = [ 1 => 'foo', diff --git a/tests/PHPStan/Parallel/SchedulerTest.php b/tests/PHPStan/Parallel/SchedulerTest.php index e029e06d9b..538dd69016 100644 --- a/tests/PHPStan/Parallel/SchedulerTest.php +++ b/tests/PHPStan/Parallel/SchedulerTest.php @@ -73,7 +73,7 @@ public function dataSchedule(): array * @param int $maximumNumberOfProcesses * @param int $minimumNumberOfJobsPerProcess * @param int $jobSize - * @param int $numberOfFiles + * @param 0|positive-int $numberOfFiles * @param int $expectedNumberOfProcesses * @param array $expectedJobSizes */ From 5f7c3d6c315a554e2bf33834b8b3f9031051a0c8 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 20 Aug 2021 13:08:38 +0200 Subject: [PATCH 0138/1284] return non-empty-array from (mb_)?str_split() --- src/Type/Php/StrSplitFunctionReturnTypeExtension.php | 7 ++++++- tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php | 10 +++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/Type/Php/StrSplitFunctionReturnTypeExtension.php b/src/Type/Php/StrSplitFunctionReturnTypeExtension.php index 0157f68db2..192fd5733c 100644 --- a/src/Type/Php/StrSplitFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrSplitFunctionReturnTypeExtension.php @@ -6,6 +6,7 @@ use PHPStan\Analyser\Scope; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\ParametersAcceptorSelector; +use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\ArrayType; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantBooleanType; @@ -15,6 +16,7 @@ use PHPStan\Type\IntegerType; use PHPStan\Type\StringType; use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; use PHPStan\Type\TypeUtils; final class StrSplitFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension @@ -89,7 +91,10 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $stringType = $scope->getType($functionCall->args[0]->value); if (!$stringType instanceof ConstantStringType) { - return new ArrayType(new IntegerType(), new StringType()); + return TypeCombinator::intersect( + new ArrayType(new IntegerType(), new StringType()), + new NonEmptyArrayType() + ); } $stringValue = $stringType->getValue(); diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 6971ea40f0..af8dae7a3c 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -5888,7 +5888,7 @@ public function dataFunctions(): array '$strSplitConstantStringWithoutDefinedSplitLength', ], [ - 'array', + 'array&nonEmpty', '$strSplitStringWithoutDefinedSplitLength', ], [ @@ -5908,7 +5908,7 @@ public function dataFunctions(): array '$strSplitConstantStringWithInvalidSplitLengthType', ], [ - 'array', + 'array&nonEmpty', '$strSplitConstantStringWithVariableStringAndConstantSplitLength', ], [ @@ -9682,7 +9682,7 @@ public function dataPhp74Functions(): array '$mbStrSplitConstantStringWithoutDefinedSplitLength', ], [ - 'array', + 'array&nonEmpty', '$mbStrSplitStringWithoutDefinedSplitLength', ], [ @@ -9702,7 +9702,7 @@ public function dataPhp74Functions(): array '$mbStrSplitConstantStringWithInvalidSplitLengthType', ], [ - 'array', + 'array&nonEmpty', '$mbStrSplitConstantStringWithVariableStringAndConstantSplitLength', ], [ @@ -9758,7 +9758,7 @@ public function dataPhp74Functions(): array '$mbStrSplitConstantStringWithInvalidSplitLengthTypeAndVariableEncoding', ], [ - 'array', + 'array&nonEmpty', '$mbStrSplitConstantStringWithVariableStringAndConstantSplitLengthAndValidEncoding', ], [ From 7af0c4e1d341f632561c59c4eb0bfa639313bd9e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 18 Aug 2021 18:14:11 +0200 Subject: [PATCH 0139/1284] Cache constant reflections --- .../BetterReflection/BetterReflectionProvider.php | 9 ++++++++- src/Reflection/Runtime/RuntimeReflectionProvider.php | 9 ++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/Reflection/BetterReflection/BetterReflectionProvider.php b/src/Reflection/BetterReflection/BetterReflectionProvider.php index 0e6d7a629d..850eb36364 100644 --- a/src/Reflection/BetterReflection/BetterReflectionProvider.php +++ b/src/Reflection/BetterReflection/BetterReflectionProvider.php @@ -82,6 +82,9 @@ class BetterReflectionProvider implements ReflectionProvider /** @var \PHPStan\Reflection\ClassReflection[] */ private static array $anonymousClasses = []; + /** @var array */ + private array $cachedConstants = []; + public function __construct( ReflectionProvider\ReflectionProviderProvider $reflectionProviderProvider, ClassReflectionExtensionRegistryProvider $classReflectionExtensionRegistryProvider, @@ -359,6 +362,10 @@ public function getConstant(\PhpParser\Node\Name $nameNode, ?Scope $scope): Glob throw new \PHPStan\Broker\ConstantNotFoundException((string) $nameNode); } + if (array_key_exists($constantName, $this->cachedConstants)) { + return $this->cachedConstants[$constantName]; + } + $constantReflection = $this->constantReflector->reflect($constantName); try { $constantValue = $constantReflection->getValue(); @@ -369,7 +376,7 @@ public function getConstant(\PhpParser\Node\Name $nameNode, ?Scope $scope): Glob $fileName = null; } - return new RuntimeConstantReflection( + return $this->cachedConstants[$constantName] = new RuntimeConstantReflection( $constantName, $constantValueType, $fileName diff --git a/src/Reflection/Runtime/RuntimeReflectionProvider.php b/src/Reflection/Runtime/RuntimeReflectionProvider.php index d4f832d1ff..9e5a5bc98a 100644 --- a/src/Reflection/Runtime/RuntimeReflectionProvider.php +++ b/src/Reflection/Runtime/RuntimeReflectionProvider.php @@ -59,6 +59,9 @@ class RuntimeReflectionProvider implements ReflectionProvider /** @var \PHPStan\Reflection\ClassReflection[] */ private static array $anonymousClasses = []; + /** @var array */ + private array $cachedConstants = []; + public function __construct( ReflectionProvider\ReflectionProviderProvider $reflectionProviderProvider, ClassReflectionExtensionRegistryProvider $classReflectionExtensionRegistryProvider, @@ -341,7 +344,11 @@ public function getConstant(\PhpParser\Node\Name $nameNode, ?Scope $scope): Glob throw new \PHPStan\Broker\ConstantNotFoundException((string) $nameNode); } - return new RuntimeConstantReflection( + if (array_key_exists($constantName, $this->cachedConstants)) { + return $this->cachedConstants[$constantName]; + } + + return $this->cachedConstants[$constantName] = new RuntimeConstantReflection( $constantName, ConstantTypeHelper::getTypeFromValue(constant($constantName)), null From 29fcf80e1676f1a13ee04e88db6dbeb8685b58e1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 20 Aug 2021 13:21:31 +0200 Subject: [PATCH 0140/1284] Do not produce reflection error for unknown constants in class constants --- src/Reflection/ClassConstantReflection.php | 7 ++++++- src/Type/ConstantTypeHelper.php | 3 +++ tests/PHPStan/Analyser/AnalyserIntegrationTest.php | 3 +-- tests/PHPStan/Analyser/NodeScopeResolverTest.php | 5 +++++ tests/PHPStan/Analyser/data/bug-3379.php | 3 +++ 5 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/Reflection/ClassConstantReflection.php b/src/Reflection/ClassConstantReflection.php index 1c92d6119d..88f87bf8f7 100644 --- a/src/Reflection/ClassConstantReflection.php +++ b/src/Reflection/ClassConstantReflection.php @@ -2,6 +2,7 @@ namespace PHPStan\Reflection; +use PHPStan\BetterReflection\NodeCompiler\Exception\UnableToCompileNode; use PHPStan\Php\PhpVersion; use PHPStan\TrinaryLogic; use PHPStan\Type\ConstantTypeHelper; @@ -65,7 +66,11 @@ public function getFileName(): ?string */ public function getValue() { - return $this->reflection->getValue(); + try { + return $this->reflection->getValue(); + } catch (UnableToCompileNode $e) { + return NAN; + } } public function hasPhpDocType(): bool diff --git a/src/Type/ConstantTypeHelper.php b/src/Type/ConstantTypeHelper.php index 4c515e4a5a..287b95ea58 100644 --- a/src/Type/ConstantTypeHelper.php +++ b/src/Type/ConstantTypeHelper.php @@ -20,6 +20,9 @@ public static function getTypeFromValue($value): Type if (is_int($value)) { return new ConstantIntegerType($value); } elseif (is_float($value)) { + if (is_nan($value)) { + return new MixedType(); + } return new ConstantFloatType($value); } elseif (is_bool($value)) { return new ConstantBooleanType($value); diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index dffd663c06..4cff1f74e5 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -278,9 +278,8 @@ public function testBug3379(): void $this->markTestSkipped('Test requires static reflection'); } $errors = $this->runAnalyse(__DIR__ . '/data/bug-3379.php'); - $this->assertCount(2, $errors); + $this->assertCount(1, $errors); $this->assertSame('Constant SOME_UNKNOWN_CONST not found.', $errors[0]->getMessage()); - $this->assertSame('Reflection error: Could not locate constant "SOME_UNKNOWN_CONST" while evaluating expression in Bug3379\Foo at line 8', $errors[1]->getMessage()); } public function testBug3798(): void diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index b3dabbf74e..1599e5e069 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -463,6 +463,11 @@ public function dataFileAsserts(): iterable } yield from $this->gatherAssertTypes(__DIR__ . '/data/class-constant-types.php'); + + if (self::$useStaticReflectionProvider) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3379.php'); + } + yield from $this->gatherAssertTypes(__DIR__ . '/data/modulo-operator.php'); } diff --git a/tests/PHPStan/Analyser/data/bug-3379.php b/tests/PHPStan/Analyser/data/bug-3379.php index e6b0cb5df4..53d82890e3 100644 --- a/tests/PHPStan/Analyser/data/bug-3379.php +++ b/tests/PHPStan/Analyser/data/bug-3379.php @@ -2,6 +2,8 @@ namespace Bug3379; +use function PHPStan\Testing\assertType; + class Foo { @@ -11,4 +13,5 @@ class Foo function () { echo Foo::URL; + assertType('mixed', Foo::URL); }; From 2f1615bdfdd439bda946b52df46804dc56c2c289 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 20 Aug 2021 13:30:50 +0200 Subject: [PATCH 0141/1284] ParallelAnalyser - do not allow to go below the default timeout --- src/Parallel/ParallelAnalyser.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Parallel/ParallelAnalyser.php b/src/Parallel/ParallelAnalyser.php index c0d4edcad7..1099bb341a 100644 --- a/src/Parallel/ParallelAnalyser.php +++ b/src/Parallel/ParallelAnalyser.php @@ -17,6 +17,8 @@ class ParallelAnalyser { + private const DEFAULT_TIMEOUT = 600.0; + private int $internalErrorsCountLimit; private float $processTimeout; @@ -32,7 +34,7 @@ public function __construct( ) { $this->internalErrorsCountLimit = $internalErrorsCountLimit; - $this->processTimeout = $processTimeout; + $this->processTimeout = max($processTimeout, self::DEFAULT_TIMEOUT); $this->decoderBufferSize = $decoderBufferSize; } From 119facc64db3acd77b8024fafa8bb3228e90cb25 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 20 Aug 2021 13:36:56 +0200 Subject: [PATCH 0142/1284] Use positive-int in parameters only on PHP 8 --- resources/functionMap.php | 2 +- resources/functionMap_php74delta.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 8e058d7419..704dc36f96 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -11696,7 +11696,7 @@ 'str_replace' => ['string|array', 'search'=>'string|array', 'replace'=>'string|array', 'subject'=>'string|array', '&w_replace_count='=>'int'], 'str_rot13' => ['string', 'str'=>'string'], 'str_shuffle' => ['string', 'str'=>'string'], -'str_split' => ['array|false', 'str'=>'string', 'split_length='=>'positive-int'], +'str_split' => ['array|false', 'str'=>'string', 'split_length='=>'int'], 'str_word_count' => ['array|int|false', 'string'=>'string', 'format='=>'int', 'charlist='=>'string'], 'strcasecmp' => ['int', 'str1'=>'string', 'str2'=>'string'], 'strchr' => ['string|false', 'haystack'=>'string', 'needle'=>'string', 'before_needle='=>'bool'], diff --git a/resources/functionMap_php74delta.php b/resources/functionMap_php74delta.php index ec728c0afa..73349639fe 100644 --- a/resources/functionMap_php74delta.php +++ b/resources/functionMap_php74delta.php @@ -39,7 +39,7 @@ 'FFI::typeof' => ['FFI\CType', '&ptr'=>'FFI\CData'], 'FFI::type' => ['FFI\CType', 'type'=>'string'], 'get_mangled_object_vars' => ['array', 'obj'=>'object'], - 'mb_str_split' => ['array|false', 'str'=>'string', 'split_length='=>'positive-int', 'encoding='=>'string'], + 'mb_str_split' => ['array|false', 'str'=>'string', 'split_length='=>'int', 'encoding='=>'string'], 'password_algos' => ['array'], 'password_hash' => ['string|false', 'password'=>'string', 'algo'=>'string|null', 'options='=>'array'], 'password_needs_rehash' => ['bool', 'hash'=>'string', 'algo'=>'string|null', 'options='=>'array'], From 9762d371ebb2663ada1e6947d2edc685330011ee Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 20 Aug 2021 15:06:34 +0200 Subject: [PATCH 0143/1284] =?UTF-8?q?Add=20PHP=5FWINDOWS=5F*=C2=A0constant?= =?UTF-8?q?s=20to=20dynamicConstantNames?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- conf/config.neon | 3 +++ 1 file changed, 3 insertions(+) diff --git a/conf/config.neon b/conf/config.neon index 11b72ad1b2..c18f2db590 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -133,6 +133,9 @@ parameters: - PHP_RELEASE_VERSION - PHP_VERSION_ID - PHP_EXTRA_VERSION + - PHP_WINDOWS_VERSION_MAJOR + - PHP_WINDOWS_VERSION_MINOR + - PHP_WINDOWS_VERSION_BUILD - PHP_ZTS - PHP_DEBUG - PHP_MAXPATHLEN From 24f6264bac7af157bb4efdd34a8536f6445c6ac3 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 21 Aug 2021 11:53:05 +0200 Subject: [PATCH 0144/1284] OverridingPropertyRule - relax checking PHPDoc types --- conf/config.level0.neon | 1 + conf/config.neon | 2 + .../Properties/OverridingPropertyRule.php | 44 +++++++++-- .../Properties/OverridingPropertyRuleTest.php | 74 ++++++++++++++++++- .../data/overriding-property-phpdoc.php | 31 ++++++++ 5 files changed, 145 insertions(+), 7 deletions(-) create mode 100644 tests/PHPStan/Rules/Properties/data/overriding-property-phpdoc.php diff --git a/conf/config.level0.neon b/conf/config.level0.neon index 7530b8b7f8..6f1bbf757e 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -220,6 +220,7 @@ services: class: PHPStan\Rules\Properties\OverridingPropertyRule arguments: checkPhpDocMethodSignatures: %checkPhpDocMethodSignatures% + reportMaybes: %reportMaybesInPropertyPhpDocTypes% - class: PHPStan\Rules\Properties\UninitializedPropertyRule diff --git a/conf/config.neon b/conf/config.neon index c18f2db590..24e08980f3 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -82,6 +82,7 @@ parameters: implicitThrows: true reportMaybes: false reportMaybesInMethodSignatures: false + reportMaybesInPropertyPhpDocTypes: false reportStaticMethodSignatures: false mixinExcludeClasses: [] scanFiles: [] @@ -267,6 +268,7 @@ parametersSchema: tipsOfTheDay: bool() reportMaybes: bool() reportMaybesInMethodSignatures: bool() + reportMaybesInPropertyPhpDocTypes: bool() reportStaticMethodSignatures: bool() parallel: structure([ jobSize: int(), diff --git a/src/Rules/Properties/OverridingPropertyRule.php b/src/Rules/Properties/OverridingPropertyRule.php index c66b81b0b6..07e55ec642 100644 --- a/src/Rules/Properties/OverridingPropertyRule.php +++ b/src/Rules/Properties/OverridingPropertyRule.php @@ -20,9 +20,15 @@ class OverridingPropertyRule implements Rule private bool $checkPhpDocMethodSignatures; - public function __construct(bool $checkPhpDocMethodSignatures) + private bool $reportMaybes; + + public function __construct( + bool $checkPhpDocMethodSignatures, + bool $reportMaybes + ) { $this->checkPhpDocMethodSignatures = $checkPhpDocMethodSignatures; + $this->reportMaybes = $reportMaybes; } public function getNodeType(): string @@ -152,21 +158,49 @@ public function processNode(Node $node, Scope $scope): array } $propertyReflection = $classReflection->getNativeProperty($node->getName()); - if ($propertyReflection->getReadableType()->equals($prototype->getReadableType())) { + if ($prototype->getReadableType()->equals($propertyReflection->getReadableType())) { return $errors; } $verbosity = VerbosityLevel::getRecommendedLevelByType($prototype->getReadableType(), $propertyReflection->getReadableType()); - - $errors[] = RuleErrorBuilder::message(sprintf( - 'Type %s of property %s::$%s is not the same as type %s of overridden property %s::$%s.', + $isSuperType = $prototype->getReadableType()->isSuperTypeOf($propertyReflection->getReadableType()); + $canBeTurnedOffError = RuleErrorBuilder::message(sprintf( + 'PHPDoc type %s of property %s::$%s is not the same as PHPDoc type %s of overridden property %s::$%s.', $propertyReflection->getReadableType()->describe($verbosity), $classReflection->getDisplayName(), $node->getName(), $prototype->getReadableType()->describe($verbosity), $prototype->getDeclaringClass()->getDisplayName(), $node->getName() + ))->tip(sprintf( + "You can fix 3rd party PHPDoc types with stub files:\n %s\n This error can be turned off by setting\n %s", + 'https://phpstan.org/user-guide/stub-files', + 'reportMaybesInPropertyPhpDocTypes: false in your %configurationFile%.' ))->build(); + $cannotBeTurnedOffError = RuleErrorBuilder::message(sprintf( + 'PHPDoc type %s of property %s::$%s is %s PHPDoc type %s of overridden property %s::$%s.', + $propertyReflection->getReadableType()->describe($verbosity), + $classReflection->getDisplayName(), + $node->getName(), + $this->reportMaybes ? 'not the same as' : 'not covariant with', + $prototype->getReadableType()->describe($verbosity), + $prototype->getDeclaringClass()->getDisplayName(), + $node->getName() + ))->tip(sprintf( + "You can fix 3rd party PHPDoc types with stub files:\n %s", + 'https://phpstan.org/user-guide/stub-files' + ))->build(); + if ($this->reportMaybes) { + if (!$isSuperType->yes()) { + $errors[] = $cannotBeTurnedOffError; + } else { + $errors[] = $canBeTurnedOffError; + } + } else { + if (!$isSuperType->yes()) { + $errors[] = $cannotBeTurnedOffError; + } + } return $errors; } diff --git a/tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php b/tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php index a05fe49500..b29dfb9d9f 100644 --- a/tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php +++ b/tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php @@ -11,9 +11,12 @@ class OverridingPropertyRuleTest extends RuleTestCase { + /** @var bool */ + private $reportMaybes; + protected function getRule(): Rule { - return new OverridingPropertyRule(true); + return new OverridingPropertyRule(true, $this->reportMaybes); } public function testRule(): void @@ -22,6 +25,7 @@ public function testRule(): void $this->markTestSkipped('Test requires static reflection.'); } + $this->reportMaybes = true; $this->analyse([__DIR__ . '/data/overriding-property.php'], [ [ 'Static property OverridingProperty\Bar::$protectedFoo overrides non-static property OverridingProperty\Foo::$protectedFoo.', @@ -88,10 +92,76 @@ public function testRule(): void 142, ], [ - 'Type 4 of property OverridingProperty\Typed2WithPhpDoc::$foo is not the same as type 1|2|3 of overridden property OverridingProperty\TypedWithPhpDoc::$foo.', + 'PHPDoc type 4 of property OverridingProperty\Typed2WithPhpDoc::$foo is not the same as PHPDoc type 1|2|3 of overridden property OverridingProperty\TypedWithPhpDoc::$foo.', 158, + sprintf( + "You can fix 3rd party PHPDoc types with stub files:\n %s", + 'https://phpstan.org/user-guide/stub-files' + ), ], ]); } + public function dataRulePHPDocTypes(): array + { + $tip = sprintf( + "You can fix 3rd party PHPDoc types with stub files:\n %s", + 'https://phpstan.org/user-guide/stub-files', + ); + $tipWithOption = sprintf( + "You can fix 3rd party PHPDoc types with stub files:\n %s\n This error can be turned off by setting\n %s", + 'https://phpstan.org/user-guide/stub-files', + 'reportMaybesInPropertyPhpDocTypes: false in your %configurationFile%.' + ); + + return [ + [ + false, + [ + [ + 'PHPDoc type array of property OverridingPropertyPhpDoc\Bar::$arrayClassStrings is not covariant with PHPDoc type array of overridden property OverridingPropertyPhpDoc\Foo::$arrayClassStrings.', + 26, + $tip, + ], + [ + 'PHPDoc type int of property OverridingPropertyPhpDoc\Bar::$string is not covariant with PHPDoc type string of overridden property OverridingPropertyPhpDoc\Foo::$string.', + 29, + $tip, + ], + ], + ], + [ + true, + [ + [ + 'PHPDoc type array of property OverridingPropertyPhpDoc\Bar::$array is not the same as PHPDoc type array of overridden property OverridingPropertyPhpDoc\Foo::$array.', + 23, + $tipWithOption, + ], + [ + 'PHPDoc type array of property OverridingPropertyPhpDoc\Bar::$arrayClassStrings is not the same as PHPDoc type array of overridden property OverridingPropertyPhpDoc\Foo::$arrayClassStrings.', + 26, + $tip, + ], + [ + 'PHPDoc type int of property OverridingPropertyPhpDoc\Bar::$string is not the same as PHPDoc type string of overridden property OverridingPropertyPhpDoc\Foo::$string.', + 29, + $tip, + ], + ], + ], + ]; + } + + /** + * @dataProvider dataRulePHPDocTypes + * @param bool $reportMaybes + * @param mixed[] $errors + */ + public function testRulePHPDocTypes(bool $reportMaybes, array $errors): void + { + $this->reportMaybes = $reportMaybes; + $this->analyse([__DIR__ . '/data/overriding-property-phpdoc.php'], $errors); + } + } diff --git a/tests/PHPStan/Rules/Properties/data/overriding-property-phpdoc.php b/tests/PHPStan/Rules/Properties/data/overriding-property-phpdoc.php new file mode 100644 index 0000000000..6e6a7d0f2e --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/overriding-property-phpdoc.php @@ -0,0 +1,31 @@ + */ + protected $arrayClassStrings; + + /** @var string */ + protected $string; + +} + +class Bar extends Foo +{ + + /** @var array */ + protected $array; + + /** @var array */ + protected $arrayClassStrings; + + /** @var int */ + protected $string; + +} From 68495e8a965118464f42927ae7941721bda03ec3 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 21 Aug 2021 13:43:17 +0200 Subject: [PATCH 0145/1284] Fix --- tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php b/tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php index b29dfb9d9f..a3e5962157 100644 --- a/tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php +++ b/tests/PHPStan/Rules/Properties/OverridingPropertyRuleTest.php @@ -106,7 +106,7 @@ public function dataRulePHPDocTypes(): array { $tip = sprintf( "You can fix 3rd party PHPDoc types with stub files:\n %s", - 'https://phpstan.org/user-guide/stub-files', + 'https://phpstan.org/user-guide/stub-files' ); $tipWithOption = sprintf( "You can fix 3rd party PHPDoc types with stub files:\n %s\n This error can be turned off by setting\n %s", From 89ceb6a9790d534b1d101b76317e487f0ee5d0c8 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 22 Aug 2021 09:52:26 +0200 Subject: [PATCH 0146/1284] Updated BetterReflection --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 581ac9845f..c8b408ecd5 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "nette/utils": "^3.1.3", "nikic/php-parser": "dev-master as 4.12.0", "ondram/ci-detector": "^3.4.0", - "ondrejmirtes/better-reflection": "4.3.65", + "ondrejmirtes/better-reflection": "4.3.66", "phpstan/php-8-stubs": "^0.1.22", "phpstan/phpdoc-parser": "^0.5.5", "react/child-process": "^0.6.1", diff --git a/composer.lock b/composer.lock index 3ff4e6fe3d..0c3ce1f282 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e7c69231bdd43f8e377c2fc08d85c590", + "content-hash": "e7b848558117f89979eb92177fd41ed4", "packages": [ { "name": "clue/block-react", @@ -2075,16 +2075,16 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "4.3.65", + "version": "4.3.66", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "5e74a9b7ccd861481c618306d6e995b738419f35" + "reference": "fef40df2f74a610409159f27f84175162b919707" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/5e74a9b7ccd861481c618306d6e995b738419f35", - "reference": "5e74a9b7ccd861481c618306d6e995b738419f35", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/fef40df2f74a610409159f27f84175162b919707", + "reference": "fef40df2f74a610409159f27f84175162b919707", "shasum": "" }, "require": { @@ -2139,9 +2139,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/4.3.65" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/4.3.66" }, - "time": "2021-08-18T09:23:47+00:00" + "time": "2021-08-22T07:45:42+00:00" }, { "name": "phpstan/php-8-stubs", From 5a44c297adb6ded659c3206e9e9525f60a37d55b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 22 Aug 2021 10:44:30 +0200 Subject: [PATCH 0147/1284] Optimization - do not sort huge unions --- src/Type/UnionTypeHelper.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Type/UnionTypeHelper.php b/src/Type/UnionTypeHelper.php index c0c99bbd18..0e33878645 100644 --- a/src/Type/UnionTypeHelper.php +++ b/src/Type/UnionTypeHelper.php @@ -32,6 +32,10 @@ public static function getReferencedClasses(array $types): array */ public static function sortTypes(array $types): array { + if (count($types) > 1024) { + return $types; + } + usort($types, static function (Type $a, Type $b): int { if ($a instanceof NullType) { return 1; From 9d191f9abb00b3c293a26934ba1b92bb413569b4 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 22 Aug 2021 13:06:57 +0200 Subject: [PATCH 0148/1284] Support bool as template type bound --- src/Rules/Generics/TemplateTypeCheck.php | 2 + src/Type/Generic/TemplateBooleanType.php | 54 +++++++++++++++++++ src/Type/Generic/TemplateTypeFactory.php | 5 ++ tests/PHPStan/Analyser/data/generics.php | 17 ++++++ .../Rules/Generics/data/function-template.php | 6 +++ 5 files changed, 84 insertions(+) create mode 100644 src/Type/Generic/TemplateBooleanType.php diff --git a/src/Rules/Generics/TemplateTypeCheck.php b/src/Rules/Generics/TemplateTypeCheck.php index 057623f0da..b9b8276686 100644 --- a/src/Rules/Generics/TemplateTypeCheck.php +++ b/src/Rules/Generics/TemplateTypeCheck.php @@ -7,6 +7,7 @@ use PHPStan\Rules\ClassCaseSensitivityCheck; use PHPStan\Rules\ClassNameNodePair; use PHPStan\Rules\RuleErrorBuilder; +use PHPStan\Type\BooleanType; use PHPStan\Type\Generic\GenericObjectType; use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\Generic\TemplateTypeScope; @@ -110,6 +111,7 @@ public function check( $boundClass === MixedType::class || $boundClass === StringType::class || $boundClass === IntegerType::class + || $boundClass === BooleanType::class || $boundClass === ObjectWithoutClassType::class || $boundClass === ObjectType::class || $boundClass === GenericObjectType::class diff --git a/src/Type/Generic/TemplateBooleanType.php b/src/Type/Generic/TemplateBooleanType.php new file mode 100644 index 0000000000..76cd73ec70 --- /dev/null +++ b/src/Type/Generic/TemplateBooleanType.php @@ -0,0 +1,54 @@ + */ + use TemplateTypeTrait; + use UndecidedComparisonCompoundTypeTrait; + + public function __construct( + TemplateTypeScope $scope, + TemplateTypeStrategy $templateTypeStrategy, + TemplateTypeVariance $templateTypeVariance, + string $name, + BooleanType $bound + ) + { + parent::__construct(); + $this->scope = $scope; + $this->strategy = $templateTypeStrategy; + $this->variance = $templateTypeVariance; + $this->name = $name; + $this->bound = $bound; + } + + public function traverse(callable $cb): Type + { + $newBound = $cb($this->getBound()); + if ($this->getBound() !== $newBound && $newBound instanceof BooleanType) { + return new self( + $this->scope, + $this->strategy, + $this->variance, + $this->name, + $newBound + ); + } + + return $this; + } + + protected function shouldGeneralizeInferredType(): bool + { + return false; + } + +} diff --git a/src/Type/Generic/TemplateTypeFactory.php b/src/Type/Generic/TemplateTypeFactory.php index e6172790f8..f352e02cde 100644 --- a/src/Type/Generic/TemplateTypeFactory.php +++ b/src/Type/Generic/TemplateTypeFactory.php @@ -4,6 +4,7 @@ use PHPStan\PhpDoc\Tag\TemplateTag; use PHPStan\Type\BenevolentUnionType; +use PHPStan\Type\BooleanType; use PHPStan\Type\IntegerType; use PHPStan\Type\MixedType; use PHPStan\Type\ObjectType; @@ -44,6 +45,10 @@ public static function create(TemplateTypeScope $scope, string $name, ?Type $bou return new TemplateIntegerType($scope, $strategy, $variance, $name, $bound); } + if ($bound instanceof BooleanType && ($boundClass === BooleanType::class || $bound instanceof TemplateType)) { + return new TemplateBooleanType($scope, $strategy, $variance, $name, $bound); + } + if ($bound instanceof MixedType && ($boundClass === MixedType::class || $bound instanceof TemplateType)) { return new TemplateMixedType($scope, $strategy, $variance, $name, $bound); } diff --git a/tests/PHPStan/Analyser/data/generics.php b/tests/PHPStan/Analyser/data/generics.php index 672237b853..7b0fe00bde 100644 --- a/tests/PHPStan/Analyser/data/generics.php +++ b/tests/PHPStan/Analyser/data/generics.php @@ -1405,3 +1405,20 @@ function (): void { $array = ['a' => 1, 'b' => 2]; assertType('array(\'a\' => int, \'b\' => int)', a($array)); }; + + +/** + * @template T of bool + * @param T $b + * @return T + */ +function boolBound(bool $b): bool +{ + return $b; +} + +function (bool $b): void { + assertType('true', boolBound(true)); + assertType('false', boolBound(false)); + assertType('bool', boolBound($b)); +}; diff --git a/tests/PHPStan/Rules/Generics/data/function-template.php b/tests/PHPStan/Rules/Generics/data/function-template.php index 994865f88f..7919b18490 100644 --- a/tests/PHPStan/Rules/Generics/data/function-template.php +++ b/tests/PHPStan/Rules/Generics/data/function-template.php @@ -33,3 +33,9 @@ function lorem() { } + +/** @template T of bool */ +function ipsum() +{ + +} From 4e7d6c155c52e11f81bcabf75f84e14a2e0e1727 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 22 Aug 2021 13:16:09 +0200 Subject: [PATCH 0149/1284] Support float as template type bound --- src/Rules/Generics/TemplateTypeCheck.php | 2 + src/Type/Generic/TemplateFloatType.php | 54 +++++++++++++++++++ src/Type/Generic/TemplateTypeFactory.php | 5 ++ tests/PHPStan/Analyser/data/generics.php | 15 ++++++ .../Generics/ClassTemplateTypeRuleTest.php | 8 --- .../Generics/FunctionTemplateTypeRuleTest.php | 8 +-- .../InterfaceTemplateTypeRuleTest.php | 4 -- .../Generics/MethodTemplateTypeRuleTest.php | 4 -- .../Generics/TraitTemplateTypeRuleTest.php | 4 -- .../Rules/Generics/data/function-template.php | 12 +++++ 10 files changed, 92 insertions(+), 24 deletions(-) create mode 100644 src/Type/Generic/TemplateFloatType.php diff --git a/src/Rules/Generics/TemplateTypeCheck.php b/src/Rules/Generics/TemplateTypeCheck.php index b9b8276686..37a6b55e38 100644 --- a/src/Rules/Generics/TemplateTypeCheck.php +++ b/src/Rules/Generics/TemplateTypeCheck.php @@ -8,6 +8,7 @@ use PHPStan\Rules\ClassNameNodePair; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\BooleanType; +use PHPStan\Type\FloatType; use PHPStan\Type\Generic\GenericObjectType; use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\Generic\TemplateTypeScope; @@ -111,6 +112,7 @@ public function check( $boundClass === MixedType::class || $boundClass === StringType::class || $boundClass === IntegerType::class + || $boundClass === FloatType::class || $boundClass === BooleanType::class || $boundClass === ObjectWithoutClassType::class || $boundClass === ObjectType::class diff --git a/src/Type/Generic/TemplateFloatType.php b/src/Type/Generic/TemplateFloatType.php new file mode 100644 index 0000000000..29171e1958 --- /dev/null +++ b/src/Type/Generic/TemplateFloatType.php @@ -0,0 +1,54 @@ + */ + use TemplateTypeTrait; + use UndecidedComparisonCompoundTypeTrait; + + public function __construct( + TemplateTypeScope $scope, + TemplateTypeStrategy $templateTypeStrategy, + TemplateTypeVariance $templateTypeVariance, + string $name, + FloatType $bound + ) + { + parent::__construct(); + $this->scope = $scope; + $this->strategy = $templateTypeStrategy; + $this->variance = $templateTypeVariance; + $this->name = $name; + $this->bound = $bound; + } + + public function traverse(callable $cb): Type + { + $newBound = $cb($this->getBound()); + if ($this->getBound() !== $newBound && $newBound instanceof FloatType) { + return new self( + $this->scope, + $this->strategy, + $this->variance, + $this->name, + $newBound + ); + } + + return $this; + } + + protected function shouldGeneralizeInferredType(): bool + { + return false; + } + +} diff --git a/src/Type/Generic/TemplateTypeFactory.php b/src/Type/Generic/TemplateTypeFactory.php index f352e02cde..4c125db586 100644 --- a/src/Type/Generic/TemplateTypeFactory.php +++ b/src/Type/Generic/TemplateTypeFactory.php @@ -5,6 +5,7 @@ use PHPStan\PhpDoc\Tag\TemplateTag; use PHPStan\Type\BenevolentUnionType; use PHPStan\Type\BooleanType; +use PHPStan\Type\FloatType; use PHPStan\Type\IntegerType; use PHPStan\Type\MixedType; use PHPStan\Type\ObjectType; @@ -45,6 +46,10 @@ public static function create(TemplateTypeScope $scope, string $name, ?Type $bou return new TemplateIntegerType($scope, $strategy, $variance, $name, $bound); } + if ($bound instanceof FloatType && ($boundClass === FloatType::class || $bound instanceof TemplateType)) { + return new TemplateFloatType($scope, $strategy, $variance, $name, $bound); + } + if ($bound instanceof BooleanType && ($boundClass === BooleanType::class || $bound instanceof TemplateType)) { return new TemplateBooleanType($scope, $strategy, $variance, $name, $bound); } diff --git a/tests/PHPStan/Analyser/data/generics.php b/tests/PHPStan/Analyser/data/generics.php index 7b0fe00bde..eb09307440 100644 --- a/tests/PHPStan/Analyser/data/generics.php +++ b/tests/PHPStan/Analyser/data/generics.php @@ -1422,3 +1422,18 @@ function (bool $b): void { assertType('false', boolBound(false)); assertType('bool', boolBound($b)); }; + +/** + * @template T of float + * @param T $f + * @return T + */ +function floatBound(float $f): float +{ + return $f; +} + +function (float $f): void { + assertType('1.0', floatBound(1.0)); + assertType('float', floatBound($f)); +}; diff --git a/tests/PHPStan/Rules/Generics/ClassTemplateTypeRuleTest.php b/tests/PHPStan/Rules/Generics/ClassTemplateTypeRuleTest.php index db13da5a03..e7954fbaa4 100644 --- a/tests/PHPStan/Rules/Generics/ClassTemplateTypeRuleTest.php +++ b/tests/PHPStan/Rules/Generics/ClassTemplateTypeRuleTest.php @@ -41,10 +41,6 @@ public function testRule(): void 'PHPDoc tag @template T for class ClassTemplateType\Bar has invalid bound type ClassTemplateType\Zazzzu.', 16, ], - [ - 'PHPDoc tag @template T for class ClassTemplateType\Baz with bound type float is not supported.', - 24, - ], [ 'Class ClassTemplateType\Baz referenced with incorrect case: ClassTemplateType\baz.', 32, @@ -69,10 +65,6 @@ public function testRule(): void 'PHPDoc tag @template T for anonymous class has invalid bound type ClassTemplateType\Zazzzu.', 63, ], - [ - 'PHPDoc tag @template T for anonymous class with bound type float is not supported.', - 68, - ], [ 'Class ClassTemplateType\Baz referenced with incorrect case: ClassTemplateType\baz.', 73, diff --git a/tests/PHPStan/Rules/Generics/FunctionTemplateTypeRuleTest.php b/tests/PHPStan/Rules/Generics/FunctionTemplateTypeRuleTest.php index 7f3975cf63..77b758c6bc 100644 --- a/tests/PHPStan/Rules/Generics/FunctionTemplateTypeRuleTest.php +++ b/tests/PHPStan/Rules/Generics/FunctionTemplateTypeRuleTest.php @@ -35,14 +35,14 @@ public function testRule(): void 'PHPDoc tag @template T for function FunctionTemplateType\bar() has invalid bound type FunctionTemplateType\Zazzzu.', 16, ], - [ - 'PHPDoc tag @template T for function FunctionTemplateType\baz() with bound type float is not supported.', - 24, - ], [ 'PHPDoc tag @template for function FunctionTemplateType\lorem() cannot have existing type alias TypeAlias as its name.', 32, ], + [ + 'PHPDoc tag @template T for function FunctionTemplateType\resourceBound() with bound type resource is not supported.', + 50, + ], ]); } diff --git a/tests/PHPStan/Rules/Generics/InterfaceTemplateTypeRuleTest.php b/tests/PHPStan/Rules/Generics/InterfaceTemplateTypeRuleTest.php index 231e2eb0c1..e8272578f0 100644 --- a/tests/PHPStan/Rules/Generics/InterfaceTemplateTypeRuleTest.php +++ b/tests/PHPStan/Rules/Generics/InterfaceTemplateTypeRuleTest.php @@ -37,10 +37,6 @@ public function testRule(): void 'PHPDoc tag @template T for interface InterfaceTemplateType\Bar has invalid bound type InterfaceTemplateType\Zazzzu.', 16, ], - [ - 'PHPDoc tag @template T for interface InterfaceTemplateType\Baz with bound type float is not supported.', - 24, - ], [ 'PHPDoc tag @template for interface InterfaceTemplateType\Lorem cannot have existing type alias TypeAlias as its name.', 33, diff --git a/tests/PHPStan/Rules/Generics/MethodTemplateTypeRuleTest.php b/tests/PHPStan/Rules/Generics/MethodTemplateTypeRuleTest.php index d8f2c90422..d953a401c8 100644 --- a/tests/PHPStan/Rules/Generics/MethodTemplateTypeRuleTest.php +++ b/tests/PHPStan/Rules/Generics/MethodTemplateTypeRuleTest.php @@ -41,10 +41,6 @@ public function testRule(): void 'PHPDoc tag @template T for method MethodTemplateType\Bar::doFoo() shadows @template T of Exception for class MethodTemplateType\Bar.', 37, ], - [ - 'PHPDoc tag @template T for method MethodTemplateType\Baz::doFoo() with bound type float is not supported.', - 50, - ], [ 'PHPDoc tag @template for method MethodTemplateType\Lorem::doFoo() cannot have existing type alias TypeAlias as its name.', 66, diff --git a/tests/PHPStan/Rules/Generics/TraitTemplateTypeRuleTest.php b/tests/PHPStan/Rules/Generics/TraitTemplateTypeRuleTest.php index 5ae1a3b1aa..1b90f838f6 100644 --- a/tests/PHPStan/Rules/Generics/TraitTemplateTypeRuleTest.php +++ b/tests/PHPStan/Rules/Generics/TraitTemplateTypeRuleTest.php @@ -37,10 +37,6 @@ public function testRule(): void 'PHPDoc tag @template T for trait TraitTemplateType\Bar has invalid bound type TraitTemplateType\Zazzzu.', 16, ], - [ - 'PHPDoc tag @template T for trait TraitTemplateType\Baz with bound type float is not supported.', - 24, - ], [ 'PHPDoc tag @template for trait TraitTemplateType\Lorem cannot have existing type alias TypeAlias as its name.', 33, diff --git a/tests/PHPStan/Rules/Generics/data/function-template.php b/tests/PHPStan/Rules/Generics/data/function-template.php index 7919b18490..038240fa27 100644 --- a/tests/PHPStan/Rules/Generics/data/function-template.php +++ b/tests/PHPStan/Rules/Generics/data/function-template.php @@ -39,3 +39,15 @@ function ipsum() { } + +/** @template T of float */ +function dolor() +{ + +} + +/** @template T of resource */ +function resourceBound() +{ + +} From 3b17c0e1d5a712f75f85ab604cb84741bdf38c72 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 22 Aug 2021 16:43:45 +0200 Subject: [PATCH 0150/1284] Union normalization - do not take template types apart --- src/Type/TypeCombinator.php | 3 +++ src/Type/UnionType.php | 3 +++ tests/PHPStan/Analyser/data/generics.php | 25 +++++++++++++++++++++++ tests/PHPStan/Type/TypeCombinatorTest.php | 18 ++++++++++++++++ 4 files changed, 49 insertions(+) diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index 986635b1e1..e3a300cfe9 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -185,6 +185,9 @@ public static function union(Type ...$types): Type if (!($types[$i] instanceof UnionType)) { continue; } + if ($types[$i] instanceof TemplateType) { + continue; + } array_splice($types, $i, 1, $types[$i]->getTypes()); } diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index 272daeab81..51fa6e32c3 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -46,6 +46,9 @@ public function __construct(array $types) if (!($type instanceof UnionType)) { continue; } + if ($type instanceof TemplateType) { + continue; + } $throwException(); } diff --git a/tests/PHPStan/Analyser/data/generics.php b/tests/PHPStan/Analyser/data/generics.php index eb09307440..b034b2fe1b 100644 --- a/tests/PHPStan/Analyser/data/generics.php +++ b/tests/PHPStan/Analyser/data/generics.php @@ -1437,3 +1437,28 @@ function (float $f): void { assertType('1.0', floatBound(1.0)); assertType('float', floatBound($f)); }; + +/** + * @template T of string|int|float|bool + */ +class UnionT +{ + + /** + * @param T|null $t + * @return T|null + */ + public function doFoo($t) + { + return $t; + } + +} + +/** + * @param UnionT $foo + */ +function foooo(UnionT $foo): void +{ + assertType('string|null', $foo->doFoo('a')); +} diff --git a/tests/PHPStan/Type/TypeCombinatorTest.php b/tests/PHPStan/Type/TypeCombinatorTest.php index 12bf6ee581..59770b3320 100644 --- a/tests/PHPStan/Type/TypeCombinatorTest.php +++ b/tests/PHPStan/Type/TypeCombinatorTest.php @@ -1804,6 +1804,24 @@ public function dataUnion(): array UnionType::class, 'string|false', ], + [ + [ + TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('doFoo'), + 'T', + new UnionType([ + new StringType(), + new IntegerType(), + new FloatType(), + new BooleanType(), + ]), + TemplateTypeVariance::createInvariant() + ), + new NullType(), + ], + UnionType::class, + 'T of bool|float|int|string (function doFoo(), parameter)|null', + ], ]; } From 9174e02e0146cd0294852c805f750a1ace598e69 Mon Sep 17 00:00:00 2001 From: Craig Francis Date: Mon, 23 Aug 2021 17:45:38 +0100 Subject: [PATCH 0151/1284] Add 'literal-string' as a basic StringType() --- src/PhpDoc/TypeNodeResolver.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index 68bc957b54..3a73dfbd61 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -146,6 +146,9 @@ private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameSco case 'string': return new StringType(); + case 'literal-string': + return new StringType(); + case 'class-string': return new ClassStringType(); From cc8190024bf0e4732eaa00e0e7238c311439f3c5 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Aug 2021 21:55:19 +0200 Subject: [PATCH 0152/1284] literal-string test --- tests/PHPStan/Analyser/NodeScopeResolverTest.php | 2 ++ tests/PHPStan/Analyser/data/literal-string.php | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 tests/PHPStan/Analyser/data/literal-string.php diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 1599e5e069..e7dc953376 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -469,6 +469,8 @@ public function dataFileAsserts(): iterable } yield from $this->gatherAssertTypes(__DIR__ . '/data/modulo-operator.php'); + + yield from $this->gatherAssertTypes(__DIR__ . '/data/literal-string.php'); } /** diff --git a/tests/PHPStan/Analyser/data/literal-string.php b/tests/PHPStan/Analyser/data/literal-string.php new file mode 100644 index 0000000000..d95d0679b6 --- /dev/null +++ b/tests/PHPStan/Analyser/data/literal-string.php @@ -0,0 +1,16 @@ + Date: Mon, 23 Aug 2021 22:02:25 +0200 Subject: [PATCH 0153/1284] substr_count returns non-negative integer --- resources/functionMap.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 704dc36f96..09128f5673 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -6364,7 +6364,7 @@ 'mb_strwidth' => ['int', 'str'=>'string', 'encoding='=>'string'], 'mb_substitute_character' => ['mixed', 'substchar='=>'mixed'], 'mb_substr' => ['string', 'str'=>'string', 'start'=>'int', 'length='=>'?int', 'encoding='=>'string'], -'mb_substr_count' => ['int', 'haystack'=>'string', 'needle'=>'string', 'encoding='=>'string'], +'mb_substr_count' => ['0|positive-int', 'haystack'=>'string', 'needle'=>'string', 'encoding='=>'string'], 'mcrypt_cbc' => ['string', 'cipher'=>'string', 'key'=>'string', 'data'=>'string', 'mode'=>'int', 'iv='=>'string'], 'mcrypt_cfb' => ['string', 'cipher'=>'string', 'key'=>'string', 'data'=>'string', 'mode'=>'int', 'iv='=>'string'], 'mcrypt_create_iv' => ['string', 'size'=>'int', 'source='=>'int'], @@ -11805,7 +11805,7 @@ 'strval' => ['string', 'var'=>'mixed'], 'substr' => ['string', 'string'=>'string', 'start'=>'int', 'length='=>'int'], 'substr_compare' => ['int|false', 'main_str'=>'string', 'str'=>'string', 'offset'=>'int', 'length='=>'int', 'case_sensitivity='=>'bool'], -'substr_count' => ['int', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int', 'length='=>'int'], +'substr_count' => ['0|positive-int', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int', 'length='=>'int'], 'substr_replace' => ['string|array', 'str'=>'string|array', 'repl'=>'mixed', 'start'=>'mixed', 'length='=>'mixed'], 'suhosin_encrypt_cookie' => ['string', 'name'=>'string', 'value'=>'string'], 'suhosin_get_raw_cookies' => ['array'], From 9b51f829b4da30a45c4fc88e7fb2a89e1c3e2519 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Aug 2021 22:28:30 +0200 Subject: [PATCH 0154/1284] Updated BetterReflection --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index c8b408ecd5..b7042244f7 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "nette/utils": "^3.1.3", "nikic/php-parser": "dev-master as 4.12.0", "ondram/ci-detector": "^3.4.0", - "ondrejmirtes/better-reflection": "4.3.66", + "ondrejmirtes/better-reflection": "4.3.67", "phpstan/php-8-stubs": "^0.1.22", "phpstan/phpdoc-parser": "^0.5.5", "react/child-process": "^0.6.1", diff --git a/composer.lock b/composer.lock index 0c3ce1f282..d74c70476e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e7b848558117f89979eb92177fd41ed4", + "content-hash": "a2b823ea1d5f98ab86840a91bea91ef1", "packages": [ { "name": "clue/block-react", @@ -2075,16 +2075,16 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "4.3.66", + "version": "4.3.67", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "fef40df2f74a610409159f27f84175162b919707" + "reference": "f14002eab57de1ec3d0116371cb5239d4415c432" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/fef40df2f74a610409159f27f84175162b919707", - "reference": "fef40df2f74a610409159f27f84175162b919707", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/f14002eab57de1ec3d0116371cb5239d4415c432", + "reference": "f14002eab57de1ec3d0116371cb5239d4415c432", "shasum": "" }, "require": { @@ -2139,9 +2139,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/4.3.66" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/4.3.67" }, - "time": "2021-08-22T07:45:42+00:00" + "time": "2021-08-23T20:27:27+00:00" }, { "name": "phpstan/php-8-stubs", From 8bf2fe0683e21da1735c65545a3567925e27a71c Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 23 Aug 2021 22:34:16 +0200 Subject: [PATCH 0155/1284] mb_str_split() / str_split() always returns `non-empty-array` --- build/baseline-8.0.neon | 2 +- resources/functionMap.php | 2 +- resources/functionMap_php74delta.php | 2 +- resources/functionMap_php80delta.php | 4 +-- .../StrSplitFunctionReturnTypeExtension.php | 2 +- .../Analyser/LegacyNodeScopeResolverTest.php | 26 +++++++++---------- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/build/baseline-8.0.neon b/build/baseline-8.0.neon index 17ff4b0ca8..b9be86042c 100644 --- a/build/baseline-8.0.neon +++ b/build/baseline-8.0.neon @@ -11,7 +11,7 @@ parameters: path: ../src/Type/Php/MbFunctionsReturnTypeExtension.php - - message: "#^Call to function is_array\\(\\) with array\\ will always evaluate to true\\.$#" + message: "#^Strict comparison using \\=\\=\\= between array&nonEmpty and false will always evaluate to false\\.$#" count: 1 path: ../src/Type/Php/StrSplitFunctionReturnTypeExtension.php diff --git a/resources/functionMap.php b/resources/functionMap.php index 09128f5673..170f9be68d 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -11696,7 +11696,7 @@ 'str_replace' => ['string|array', 'search'=>'string|array', 'replace'=>'string|array', 'subject'=>'string|array', '&w_replace_count='=>'int'], 'str_rot13' => ['string', 'str'=>'string'], 'str_shuffle' => ['string', 'str'=>'string'], -'str_split' => ['array|false', 'str'=>'string', 'split_length='=>'int'], +'str_split' => ['non-empty-array|false', 'str'=>'string', 'split_length='=>'int'], 'str_word_count' => ['array|int|false', 'string'=>'string', 'format='=>'int', 'charlist='=>'string'], 'strcasecmp' => ['int', 'str1'=>'string', 'str2'=>'string'], 'strchr' => ['string|false', 'haystack'=>'string', 'needle'=>'string', 'before_needle='=>'bool'], diff --git a/resources/functionMap_php74delta.php b/resources/functionMap_php74delta.php index 73349639fe..73832288f7 100644 --- a/resources/functionMap_php74delta.php +++ b/resources/functionMap_php74delta.php @@ -39,7 +39,7 @@ 'FFI::typeof' => ['FFI\CType', '&ptr'=>'FFI\CData'], 'FFI::type' => ['FFI\CType', 'type'=>'string'], 'get_mangled_object_vars' => ['array', 'obj'=>'object'], - 'mb_str_split' => ['array|false', 'str'=>'string', 'split_length='=>'int', 'encoding='=>'string'], + 'mb_str_split' => ['non-empty-array|false', 'str'=>'string', 'split_length='=>'int', 'encoding='=>'string'], 'password_algos' => ['array'], 'password_hash' => ['string|false', 'password'=>'string', 'algo'=>'string|null', 'options='=>'array'], 'password_needs_rehash' => ['bool', 'hash'=>'string', 'algo'=>'string|null', 'options='=>'array'], diff --git a/resources/functionMap_php80delta.php b/resources/functionMap_php80delta.php index eaf5385734..f6a4efdd98 100644 --- a/resources/functionMap_php80delta.php +++ b/resources/functionMap_php80delta.php @@ -70,7 +70,7 @@ 'imagerotate' => ['false|object', 'src_im'=>'resource', 'angle'=>'float', 'bgdcolor'=>'int', 'ignoretransparent='=>'int'], 'imagescale' => ['false|object', 'im'=>'resource', 'new_width'=>'int', 'new_height='=>'int', 'method='=>'int'], 'mb_decode_numericentity' => ['string|false', 'string'=>'string', 'convmap'=>'array', 'encoding='=>'string'], - 'mb_str_split' => ['array', 'str'=>'string', 'split_length='=>'positive-int', 'encoding='=>'string'], + 'mb_str_split' => ['non-empty-array', 'str'=>'string', 'split_length='=>'positive-int', 'encoding='=>'string'], 'mktime' => ['int|false', 'hour'=>'int', 'minute='=>'int', 'second='=>'int', 'month='=>'int', 'day='=>'int', 'year='=>'int'], 'odbc_exec' => ['resource|false', 'connection_id'=>'resource', 'query'=>'string'], 'parse_str' => ['void', 'encoded_string'=>'string', '&w_result'=>'array'], @@ -84,7 +84,7 @@ 'socket_select' => ['int|false', '&rw_read'=>'Socket[]|null', '&rw_write'=>'Socket[]|null', '&rw_except'=>'Socket[]|null', 'seconds'=>'int|null', 'microseconds='=>'int'], 'sodium_crypto_aead_chacha20poly1305_ietf_decrypt' => ['string|false', 'confidential_message'=>'string', 'public_message'=>'string', 'nonce'=>'string', 'key'=>'string'], 'str_contains' => ['bool', 'haystack'=>'string', 'needle'=>'string'], - 'str_split' => ['array', 'str'=>'string', 'split_length='=>'positive-int'], + 'str_split' => ['non-empty-array', 'str'=>'string', 'split_length='=>'positive-int'], 'str_ends_with' => ['bool', 'haystack'=>'string', 'needle'=>'string'], 'str_starts_with' => ['bool', 'haystack'=>'string', 'needle'=>'string'], 'strchr' => ['string|false', 'haystack'=>'string', 'needle'=>'string', 'before_needle='=>'bool'], diff --git a/src/Type/Php/StrSplitFunctionReturnTypeExtension.php b/src/Type/Php/StrSplitFunctionReturnTypeExtension.php index 192fd5733c..bd78e3993e 100644 --- a/src/Type/Php/StrSplitFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrSplitFunctionReturnTypeExtension.php @@ -101,7 +101,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $items = isset($encoding) ? mb_str_split($stringValue, $splitLength, $encoding) : str_split($stringValue, $splitLength); - if (!is_array($items)) { + if ($items === false) { throw new \PHPStan\ShouldNotHappenException(); } diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index af8dae7a3c..ef8ff856e2 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -5880,7 +5880,7 @@ public function dataFunctions(): array '$gettimeofdayBenevolent', ], [ - PHP_VERSION_ID < 80000 ? 'array|false' : 'array', + PHP_VERSION_ID < 80000 ? '(array&nonEmpty)|false' : 'array&nonEmpty', '$strSplitConstantStringWithoutDefinedParameters', ], [ @@ -5904,7 +5904,7 @@ public function dataFunctions(): array '$strSplitConstantStringWithFailureSplitLength', ], [ - PHP_VERSION_ID < 80000 ? 'array|false' : 'array', + PHP_VERSION_ID < 80000 ? '(array&nonEmpty)|false' : 'array&nonEmpty', '$strSplitConstantStringWithInvalidSplitLengthType', ], [ @@ -5912,7 +5912,7 @@ public function dataFunctions(): array '$strSplitConstantStringWithVariableStringAndConstantSplitLength', ], [ - PHP_VERSION_ID < 80000 ? 'array|false' : 'array', + PHP_VERSION_ID < 80000 ? '(array&nonEmpty)|false' : 'array&nonEmpty', '$strSplitConstantStringWithVariableStringAndVariableSplitLength', ], // parse_url @@ -9674,7 +9674,7 @@ public function dataPhp74Functions(): array { return [ [ - PHP_VERSION_ID < 80000 ? 'array|false' : 'array', + PHP_VERSION_ID < 80000 ? '(array&nonEmpty)|false' : 'array&nonEmpty', '$mbStrSplitConstantStringWithoutDefinedParameters', ], [ @@ -9698,7 +9698,7 @@ public function dataPhp74Functions(): array '$mbStrSplitConstantStringWithFailureSplitLength', ], [ - PHP_VERSION_ID < 80000 ? 'array|false' : 'array', + PHP_VERSION_ID < 80000 ? '(array&nonEmpty)|false' : 'array&nonEmpty', '$mbStrSplitConstantStringWithInvalidSplitLengthType', ], [ @@ -9706,7 +9706,7 @@ public function dataPhp74Functions(): array '$mbStrSplitConstantStringWithVariableStringAndConstantSplitLength', ], [ - PHP_VERSION_ID < 80000 ? 'array|false' : 'array', + PHP_VERSION_ID < 80000 ? '(array&nonEmpty)|false' : 'array&nonEmpty', '$mbStrSplitConstantStringWithVariableStringAndVariableSplitLength', ], [ @@ -9718,7 +9718,7 @@ public function dataPhp74Functions(): array '$mbStrSplitConstantStringWithOneSplitLengthAndInvalidEncoding', ], [ - PHP_VERSION_ID < 80000 ? 'array|false' : 'array', + PHP_VERSION_ID < 80000 ? '(array&nonEmpty)|false' : 'array&nonEmpty', '$mbStrSplitConstantStringWithOneSplitLengthAndVariableEncoding', ], [ @@ -9730,7 +9730,7 @@ public function dataPhp74Functions(): array '$mbStrSplitConstantStringWithGreaterSplitLengthThanStringLengthAndInvalidEncoding', ], [ - PHP_VERSION_ID < 80000 ? 'array|false' : 'array', + PHP_VERSION_ID < 80000 ? '(array&nonEmpty)|false' : 'array&nonEmpty', '$mbStrSplitConstantStringWithGreaterSplitLengthThanStringLengthAndVariableEncoding', ], [ @@ -9746,7 +9746,7 @@ public function dataPhp74Functions(): array '$mbStrSplitConstantStringWithFailureSplitLengthAndVariableEncoding', ], [ - PHP_VERSION_ID < 80000 ? 'array|false' : 'array', + PHP_VERSION_ID < 80000 ? '(array&nonEmpty)|false' : 'array&nonEmpty', '$mbStrSplitConstantStringWithInvalidSplitLengthTypeAndValidEncoding', ], [ @@ -9754,7 +9754,7 @@ public function dataPhp74Functions(): array '$mbStrSplitConstantStringWithInvalidSplitLengthTypeAndInvalidEncoding', ], [ - PHP_VERSION_ID < 80000 ? 'array|false' : 'array', + PHP_VERSION_ID < 80000 ? '(array&nonEmpty)|false' : 'array&nonEmpty', '$mbStrSplitConstantStringWithInvalidSplitLengthTypeAndVariableEncoding', ], [ @@ -9766,11 +9766,11 @@ public function dataPhp74Functions(): array '$mbStrSplitConstantStringWithVariableStringAndConstantSplitLengthAndInvalidEncoding', ], [ - PHP_VERSION_ID < 80000 ? 'array|false' : 'array', + PHP_VERSION_ID < 80000 ? '(array&nonEmpty)|false' : 'array&nonEmpty', '$mbStrSplitConstantStringWithVariableStringAndConstantSplitLengthAndVariableEncoding', ], [ - PHP_VERSION_ID < 80000 ? 'array|false' : 'array', + PHP_VERSION_ID < 80000 ? '(array&nonEmpty)|false' : 'array&nonEmpty', '$mbStrSplitConstantStringWithVariableStringAndVariableSplitLengthAndValidEncoding', ], [ @@ -9778,7 +9778,7 @@ public function dataPhp74Functions(): array '$mbStrSplitConstantStringWithVariableStringAndVariableSplitLengthAndInvalidEncoding', ], [ - PHP_VERSION_ID < 80000 ? 'array|false' : 'array', + PHP_VERSION_ID < 80000 ? '(array&nonEmpty)|false' : 'array&nonEmpty', '$mbStrSplitConstantStringWithVariableStringAndVariableSplitLengthAndVariableEncoding', ], ]; From 5010ef459465fa27a3b0fe3593bdd445b6dae8f3 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 23 Aug 2021 22:52:47 +0200 Subject: [PATCH 0156/1284] self::CONSTANT can be precise even with PHPDoc type --- src/Analyser/MutatingScope.php | 13 ++++++- tests/PHPStan/Analyser/data/bug-5293.php | 2 +- .../data/class-constant-stub-files.php | 8 +++- .../Analyser/data/class-constant-types.php | 38 ++++++++++++++++--- 4 files changed, 52 insertions(+), 9 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index a84eacfa9d..3e76a98ef8 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -1992,7 +1992,18 @@ private function resolveType(Expr $node): Type return new MixedType(); } - $constantType = $constantReflection->getValueType(); + if ( + $isObject + && ( + !$constantReflection instanceof ClassConstantReflection + || !$constantClassReflection->isFinal() + ) + ) { + $constantType = $constantReflection->getValueType(); + } else { + $constantType = ConstantTypeHelper::getTypeFromValue($constantReflection->getValue()); + } + if ( $constantType instanceof ConstantType && in_array(sprintf('%s::%s', $constantClassReflection->getName(), $constantName), $this->dynamicConstantNames, true) diff --git a/tests/PHPStan/Analyser/data/bug-5293.php b/tests/PHPStan/Analyser/data/bug-5293.php index 63f30fe000..40d8ca8cc4 100644 --- a/tests/PHPStan/Analyser/data/bug-5293.php +++ b/tests/PHPStan/Analyser/data/bug-5293.php @@ -49,7 +49,7 @@ public static function getAllSupportedCurrencies(): void { array_map( function (string $currencyCode): int { - assertType('non-empty-string', $currencyCode); + assertType('\'AUD\'|\'BGN\'|\'BRL\'|\'CAD\'|\'CHF\'|\'CZK\'|\'DKK\'|\'EUR\'|\'GBP\'|\'HUF\'|\'NOK\'|\'NZD\'|\'PLN\'|\'RON\'|\'SEK\'|\'SGD\'|\'USD\'', $currencyCode); return 1; }, self::SUPPORTED_CURRENCIES diff --git a/tests/PHPStan/Analyser/data/class-constant-stub-files.php b/tests/PHPStan/Analyser/data/class-constant-stub-files.php index 93f25365f3..4777b0ec22 100644 --- a/tests/PHPStan/Analyser/data/class-constant-stub-files.php +++ b/tests/PHPStan/Analyser/data/class-constant-stub-files.php @@ -15,6 +15,10 @@ class Foo } function (): void { - assertType('int', Foo::BAR); - assertType('int', Foo::BAZ); + assertType('1', Foo::BAR); + assertType('1', Foo::BAZ); + + $foo = new Foo(); + assertType('int', $foo::BAR); + assertType('int', $foo::BAZ); }; diff --git a/tests/PHPStan/Analyser/data/class-constant-types.php b/tests/PHPStan/Analyser/data/class-constant-types.php index c5fd3499b2..9d60af25c5 100644 --- a/tests/PHPStan/Analyser/data/class-constant-types.php +++ b/tests/PHPStan/Analyser/data/class-constant-types.php @@ -21,11 +21,11 @@ public function doFoo() assertType('mixed', static::NO_TYPE); assertType('mixed', $this::NO_TYPE); - assertType('string', self::TYPE); + assertType('\'foo\'', self::TYPE); assertType('string', static::TYPE); assertType('string', $this::TYPE); - assertType('string', self::PRIVATE_TYPE); + assertType('\'foo\'', self::PRIVATE_TYPE); assertType('string', static::PRIVATE_TYPE); assertType('string', $this::PRIVATE_TYPE); } @@ -41,7 +41,7 @@ class Bar extends Foo public function doFoo() { - assertType('string', self::TYPE); + assertType('\'bar\'', self::TYPE); assertType('string', static::TYPE); assertType('string', $this::TYPE); @@ -60,7 +60,7 @@ class Baz extends Foo public function doFoo() { - assertType('int', self::TYPE); + assertType('1', self::TYPE); assertType('int', static::TYPE); assertType('int', $this::TYPE); } @@ -75,9 +75,37 @@ class Lorem extends Foo public function doFoo() { - assertType('string', self::TYPE); + assertType('1', self::TYPE); assertType('string', static::TYPE); assertType('string', $this::TYPE); } } + +final class FinalFoo +{ + + const NO_TYPE = 1; + + /** @var string */ + const TYPE = 'foo'; + + /** @var string */ + private const PRIVATE_TYPE = 'foo'; + + public function doFoo() + { + assertType('1', self::NO_TYPE); + assertType('1', static::NO_TYPE); + assertType('1', $this::NO_TYPE); + + assertType('\'foo\'', self::TYPE); + assertType('\'foo\'', static::TYPE); + assertType('\'foo\'', $this::TYPE); + + assertType('\'foo\'', self::PRIVATE_TYPE); + assertType('\'foo\'', static::PRIVATE_TYPE); + assertType('\'foo\'', $this::PRIVATE_TYPE); + } + +} From 4c4a9bfa9d0b863fbf6bfb12f467145bf00175c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Va=C5=A1ek=20Henzl?= Date: Tue, 24 Aug 2021 23:55:07 +1200 Subject: [PATCH 0157/1284] Make ReflectionAttribute generic --- conf/config.neon | 6 ++ conf/config.stubFiles.neon | 1 + ...GetAttributesMethodReturnTypeExtension.php | 58 +++++++++++++++++++ stubs/ReflectionAttribute.stub | 21 +++++++ stubs/ReflectionClass.stub | 6 ++ stubs/runtime/ReflectionAttribute.php | 38 ++++++++++++ .../Analyser/NodeScopeResolverTest.php | 4 ++ .../data/reflectionclass-issue-5511-php8.php | 51 ++++++++++++++++ tests/PHPStan/Command/CommandHelperTest.php | 1 + 9 files changed, 186 insertions(+) create mode 100644 src/Type/Php/ReflectionClassGetAttributesMethodReturnTypeExtension.php create mode 100644 stubs/ReflectionAttribute.stub create mode 100644 stubs/runtime/ReflectionAttribute.php create mode 100644 tests/PHPStan/Analyser/data/reflectionclass-issue-5511-php8.php diff --git a/conf/config.neon b/conf/config.neon index 24e08980f3..28d944928c 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -4,6 +4,7 @@ parameters: bootstrap: null bootstrapFiles: - ../stubs/runtime/ReflectionUnionType.php + - ../stubs/runtime/ReflectionAttribute.php - ../stubs/runtime/Attribute.php excludes_analyse: [] excludePaths: null @@ -1492,6 +1493,11 @@ services: - phpstan.broker.dynamicMethodReturnTypeExtension - phpstan.broker.dynamicStaticMethodReturnTypeExtension + - + class: PHPStan\Type\Php\ReflectionClassGetAttributesMethodReturnTypeExtension + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + exceptionTypeResolver: class: PHPStan\Rules\Exceptions\ExceptionTypeResolver factory: @PHPStan\Rules\Exceptions\DefaultExceptionTypeResolver diff --git a/conf/config.stubFiles.neon b/conf/config.stubFiles.neon index ecdd61bf77..057c2e4a59 100644 --- a/conf/config.stubFiles.neon +++ b/conf/config.stubFiles.neon @@ -1,5 +1,6 @@ parameters: stubFiles: + - ../stubs/ReflectionAttribute.stub - ../stubs/ReflectionClass.stub - ../stubs/iterable.stub - ../stubs/ArrayObject.stub diff --git a/src/Type/Php/ReflectionClassGetAttributesMethodReturnTypeExtension.php b/src/Type/Php/ReflectionClassGetAttributesMethodReturnTypeExtension.php new file mode 100644 index 0000000000..9ef4dd3f6e --- /dev/null +++ b/src/Type/Php/ReflectionClassGetAttributesMethodReturnTypeExtension.php @@ -0,0 +1,58 @@ +getName() === 'getAttributes'; + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type + { + if (count($methodCall->args) === 0) { + return $this->getDefaultReturnType($scope, $methodCall, $methodReflection); + } + $argType = $scope->getType($methodCall->args[0]->value); + + if ($argType instanceof ConstantStringType) { + $classType = new ObjectType($argType->getValue()); + } elseif ($argType instanceof GenericClassStringType) { + $classType = $argType->getGenericType(); + } else { + return $this->getDefaultReturnType($scope, $methodCall, $methodReflection); + } + + return new ArrayType(new MixedType(), new GenericObjectType(\ReflectionAttribute::class, [$classType])); + } + + private function getDefaultReturnType(Scope $scope, MethodCall $methodCall, MethodReflection $methodReflection): Type + { + return ParametersAcceptorSelector::selectFromArgs( + $scope, + $methodCall->args, + $methodReflection->getVariants() + )->getReturnType(); + } + +} diff --git a/stubs/ReflectionAttribute.stub b/stubs/ReflectionAttribute.stub new file mode 100644 index 0000000000..0bac59de5b --- /dev/null +++ b/stubs/ReflectionAttribute.stub @@ -0,0 +1,21 @@ + + */ + public function getName() : string + { + } + + /** + * @return T + */ + public function newInstance() : object + { + } +} diff --git a/stubs/ReflectionClass.stub b/stubs/ReflectionClass.stub index 09c428b0dc..e5d2a0908a 100644 --- a/stubs/ReflectionClass.stub +++ b/stubs/ReflectionClass.stub @@ -42,4 +42,10 @@ class ReflectionClass */ public function newInstanceWithoutConstructor(); + /** + * @return array> + */ + public function getAttributes(?string $name = null, int $flags = 0) + { + } } diff --git a/stubs/runtime/ReflectionAttribute.php b/stubs/runtime/ReflectionAttribute.php new file mode 100644 index 0000000000..8f0dd0705b --- /dev/null +++ b/stubs/runtime/ReflectionAttribute.php @@ -0,0 +1,38 @@ +gatherAssertTypes(__DIR__ . '/data/bug-3379.php'); } + if (PHP_VERSION_ID >= 80000) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/reflectionclass-issue-5511-php8.php'); + } + yield from $this->gatherAssertTypes(__DIR__ . '/data/modulo-operator.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/literal-string.php'); diff --git a/tests/PHPStan/Analyser/data/reflectionclass-issue-5511-php8.php b/tests/PHPStan/Analyser/data/reflectionclass-issue-5511-php8.php new file mode 100644 index 0000000000..ac12dda38c --- /dev/null +++ b/tests/PHPStan/Analyser/data/reflectionclass-issue-5511-php8.php @@ -0,0 +1,51 @@ + $genericClassName + */ +function testGetAttributes(string $str, string $className, string $genericClassName): void +{ + $class = new \ReflectionClass(X::class); + + $attrsAll = $class->getAttributes(); + $attrsAbc1 = $class->getAttributes(Abc::class); + $attrsAbc2 = $class->getAttributes(Abc::class, \ReflectionAttribute::IS_INSTANCEOF); + $attrsGCN = $class->getAttributes($genericClassName); + $attrsCN = $class->getAttributes($className); + $attrsStr = $class->getAttributes($str); + $attrsNonsense = $class->getAttributes("some random string"); + + assertType('array>', $attrsAll); + assertType('array>', $attrsAbc1); + assertType('array>', $attrsAbc2); + assertType('array>', $attrsGCN); + assertType('array>', $attrsCN); + assertType('array>', $attrsStr); + assertType('array>', $attrsNonsense); +} + +/** + * @param \ReflectionAttribute $ra + */ +function testNewInstance(\ReflectionAttribute $ra): void +{ + assertType('ReflectionAttribute', $ra); + $abc = $ra->newInstance(); + assertType(Abc::class, $abc); +} diff --git a/tests/PHPStan/Command/CommandHelperTest.php b/tests/PHPStan/Command/CommandHelperTest.php index ff2a6fff2e..6ce191d7ce 100644 --- a/tests/PHPStan/Command/CommandHelperTest.php +++ b/tests/PHPStan/Command/CommandHelperTest.php @@ -169,6 +169,7 @@ public function dataParameters(): array 'bootstrap' => __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'here.php', 'bootstrapFiles' => [ realpath(__DIR__ . '/../../../stubs/runtime/ReflectionUnionType.php'), + realpath(__DIR__ . '/../../../stubs/runtime/ReflectionAttribute.php'), realpath(__DIR__ . '/../../../stubs/runtime/Attribute.php'), __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'here.php', ], From 9a9f19380499a4836aa89d46a29477aed17b5da5 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Aug 2021 15:02:00 +0200 Subject: [PATCH 0158/1284] Fix CallableType's string type assumptions --- src/Type/CallableType.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Type/CallableType.php b/src/Type/CallableType.php index 55c0262ebd..6a8a1458ff 100644 --- a/src/Type/CallableType.php +++ b/src/Type/CallableType.php @@ -316,7 +316,12 @@ public function isNumericString(): TrinaryLogic public function isNonEmptyString(): TrinaryLogic { - return TrinaryLogic::createNo(); + return TrinaryLogic::createMaybe(); + } + + public function isLiteralString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); } public function isCommonCallable(): bool From 79067796c1ef4330a3747ec6379ce0f1ed24fed3 Mon Sep 17 00:00:00 2001 From: tehbeard Date: Wed, 25 Aug 2021 19:46:17 +0100 Subject: [PATCH 0159/1284] Third parameter of sodium_base642bin is optional. --- resources/functionMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 170f9be68d..08d2b18169 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -10416,7 +10416,7 @@ 'Sodium\randombytes_uniform' => ['int', 'upperBoundNonInclusive'=>'int'], 'Sodium\version_string' => ['string'], 'sodium_add' => ['string', 'string_1'=>'string', 'string_2'=>'string'], -'sodium_base642bin' => ['string', 'base64'=>'string', 'variant'=>'int', 'ignore'=>'string'], +'sodium_base642bin' => ['string', 'base64'=>'string', 'variant'=>'int', 'ignore='=>'string'], 'sodium_bin2base64' => ['string', 'binary'=>'string', 'variant'=>'int'], 'sodium_bin2hex' => ['string', 'binary'=>'string'], 'sodium_compare' => ['int', 'string_1'=>'string', 'string_2'=>'string'], From 16715c11748a270e93b837e1e68fdd949f3c872d Mon Sep 17 00:00:00 2001 From: Brandon Olivares Date: Wed, 25 Aug 2021 07:37:48 -0400 Subject: [PATCH 0160/1284] Make filter_var() return non-empty-string if input is non-empty --- src/Type/Php/FilterVarDynamicReturnTypeExtension.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Type/Php/FilterVarDynamicReturnTypeExtension.php b/src/Type/Php/FilterVarDynamicReturnTypeExtension.php index c88871a7fd..8c6417aa6a 100644 --- a/src/Type/Php/FilterVarDynamicReturnTypeExtension.php +++ b/src/Type/Php/FilterVarDynamicReturnTypeExtension.php @@ -7,6 +7,7 @@ use PHPStan\Analyser\Scope; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\ReflectionProvider; +use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\ArrayType; use PHPStan\Type\BooleanType; use PHPStan\Type\Constant\ConstantArrayType; @@ -17,6 +18,7 @@ use PHPStan\Type\ErrorType; use PHPStan\Type\FloatType; use PHPStan\Type\IntegerType; +use PHPStan\Type\IntersectionType; use PHPStan\Type\MixedType; use PHPStan\Type\NullType; use PHPStan\Type\StringType; @@ -128,6 +130,10 @@ public function getTypeFromFunctionCall( $type = $this->getFilterTypeMap()[$filterValue] ?? $mixedType; $otherType = $this->getOtherType($flagsArg, $scope); + if ($inputType->isNonEmptyString()->yes()) { + $type = new IntersectionType([$type, new AccessoryNonEmptyStringType()]); + } + if ($otherType->isSuperTypeOf($type)->no()) { $type = new UnionType([$type, $otherType]); } From 0364387aa21960d10b924a7050b1c13050b83654 Mon Sep 17 00:00:00 2001 From: Brandon Olivares Date: Wed, 25 Aug 2021 12:44:35 -0400 Subject: [PATCH 0161/1284] Add unit tests --- .../Analyser/NodeScopeResolverTest.php | 2 ++ .../filter-var-returns-non-empty-string.php | 24 +++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 tests/PHPStan/Analyser/data/filter-var-returns-non-empty-string.php diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 24e5ca4538..16641b436f 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -475,6 +475,8 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/modulo-operator.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/literal-string.php'); + + yield from $this->gatherAssertTypes(__DIR__ . '/data/filter-var-returns-non-empty-string.php'); } /** diff --git a/tests/PHPStan/Analyser/data/filter-var-returns-non-empty-string.php b/tests/PHPStan/Analyser/data/filter-var-returns-non-empty-string.php new file mode 100644 index 0000000000..bd26c5b09b --- /dev/null +++ b/tests/PHPStan/Analyser/data/filter-var-returns-non-empty-string.php @@ -0,0 +1,24 @@ + Date: Thu, 26 Aug 2021 00:46:37 +1200 Subject: [PATCH 0162/1284] Add missing `getAttributes` for all supported reflectors --- conf/config.neon | 33 ++++++++- conf/config.stubFiles.neon | 5 +- ...etAttributesMethodReturnTypeExtension.php} | 15 +++- stubs/ReflectionClassConstant.stub | 11 +++ stubs/ReflectionFunctionAbstract.stub | 6 ++ stubs/ReflectionParameter.stub | 11 +++ stubs/ReflectionProperty.stub | 11 +++ .../data/reflectionclass-issue-5511-php8.php | 74 +++++++++++++------ 8 files changed, 139 insertions(+), 27 deletions(-) rename src/Type/Php/{ReflectionClassGetAttributesMethodReturnTypeExtension.php => ReflectionGetAttributesMethodReturnTypeExtension.php} (81%) create mode 100644 stubs/ReflectionClassConstant.stub create mode 100644 stubs/ReflectionParameter.stub create mode 100644 stubs/ReflectionProperty.stub diff --git a/conf/config.neon b/conf/config.neon index 28d944928c..3c183f59bf 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1494,10 +1494,41 @@ services: - phpstan.broker.dynamicStaticMethodReturnTypeExtension - - class: PHPStan\Type\Php\ReflectionClassGetAttributesMethodReturnTypeExtension + class: PHPStan\Type\Php\ReflectionGetAttributesMethodReturnTypeExtension + arguments: + className: ReflectionClass + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + + - + class: PHPStan\Type\Php\ReflectionGetAttributesMethodReturnTypeExtension + arguments: + className: ReflectionClassConstant + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + + - + class: PHPStan\Type\Php\ReflectionGetAttributesMethodReturnTypeExtension + arguments: + className: ReflectionFunctionAbstract tags: - phpstan.broker.dynamicMethodReturnTypeExtension + - + class: PHPStan\Type\Php\ReflectionGetAttributesMethodReturnTypeExtension + arguments: + className: ReflectionParameter + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + + - + class: PHPStan\Type\Php\ReflectionGetAttributesMethodReturnTypeExtension + arguments: + className: ReflectionProperty + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + + exceptionTypeResolver: class: PHPStan\Rules\Exceptions\ExceptionTypeResolver factory: @PHPStan\Rules\Exceptions\DefaultExceptionTypeResolver diff --git a/conf/config.stubFiles.neon b/conf/config.stubFiles.neon index 057c2e4a59..e42f634273 100644 --- a/conf/config.stubFiles.neon +++ b/conf/config.stubFiles.neon @@ -2,12 +2,15 @@ parameters: stubFiles: - ../stubs/ReflectionAttribute.stub - ../stubs/ReflectionClass.stub + - ../stubs/ReflectionClassConstant.stub + - ../stubs/ReflectionFunctionAbstract.stub + - ../stubs/ReflectionParameter.stub + - ../stubs/ReflectionProperty.stub - ../stubs/iterable.stub - ../stubs/ArrayObject.stub - ../stubs/WeakReference.stub - ../stubs/ext-ds.stub - ../stubs/PDOStatement.stub - - ../stubs/ReflectionFunctionAbstract.stub - ../stubs/date.stub - ../stubs/zip.stub - ../stubs/dom.stub diff --git a/src/Type/Php/ReflectionClassGetAttributesMethodReturnTypeExtension.php b/src/Type/Php/ReflectionGetAttributesMethodReturnTypeExtension.php similarity index 81% rename from src/Type/Php/ReflectionClassGetAttributesMethodReturnTypeExtension.php rename to src/Type/Php/ReflectionGetAttributesMethodReturnTypeExtension.php index 9ef4dd3f6e..330fbddcea 100644 --- a/src/Type/Php/ReflectionClassGetAttributesMethodReturnTypeExtension.php +++ b/src/Type/Php/ReflectionGetAttributesMethodReturnTypeExtension.php @@ -15,12 +15,23 @@ use PHPStan\Type\ObjectType; use PHPStan\Type\Type; -class ReflectionClassGetAttributesMethodReturnTypeExtension implements DynamicMethodReturnTypeExtension +class ReflectionGetAttributesMethodReturnTypeExtension implements DynamicMethodReturnTypeExtension { + /** @var class-string */ + private string $className; + + /** + * @param class-string $className One of reflection classes: https://www.php.net/manual/en/book.reflection.php + */ + public function __construct(string $className) + { + $this->className = $className; + } + public function getClass(): string { - return \ReflectionClass::class; + return $this->className; } public function isMethodSupported(MethodReflection $methodReflection): bool diff --git a/stubs/ReflectionClassConstant.stub b/stubs/ReflectionClassConstant.stub new file mode 100644 index 0000000000..669ccbef89 --- /dev/null +++ b/stubs/ReflectionClassConstant.stub @@ -0,0 +1,11 @@ +> + */ + public function getAttributes(?string $name = null, int $flags = 0) + { + } +} diff --git a/stubs/ReflectionFunctionAbstract.stub b/stubs/ReflectionFunctionAbstract.stub index c096d0f608..36e48df100 100644 --- a/stubs/ReflectionFunctionAbstract.stub +++ b/stubs/ReflectionFunctionAbstract.stub @@ -8,4 +8,10 @@ abstract class ReflectionFunctionAbstract */ public function getFileName () {} + /** + * @return array> + */ + public function getAttributes(?string $name = null, int $flags = 0) + { + } } diff --git a/stubs/ReflectionParameter.stub b/stubs/ReflectionParameter.stub new file mode 100644 index 0000000000..a29fa35e83 --- /dev/null +++ b/stubs/ReflectionParameter.stub @@ -0,0 +1,11 @@ +> + */ + public function getAttributes(?string $name = null, int $flags = 0) + { + } +} diff --git a/stubs/ReflectionProperty.stub b/stubs/ReflectionProperty.stub new file mode 100644 index 0000000000..312688067d --- /dev/null +++ b/stubs/ReflectionProperty.stub @@ -0,0 +1,11 @@ +> + */ + public function getAttributes(?string $name = null, int $flags = 0) + { + } +} diff --git a/tests/PHPStan/Analyser/data/reflectionclass-issue-5511-php8.php b/tests/PHPStan/Analyser/data/reflectionclass-issue-5511-php8.php index ac12dda38c..b657010861 100644 --- a/tests/PHPStan/Analyser/data/reflectionclass-issue-5511-php8.php +++ b/tests/PHPStan/Analyser/data/reflectionclass-issue-5511-php8.php @@ -9,35 +9,63 @@ class Abc { } -#[Abc] -class X -{ -} - /** * @param string $str * @param class-string $className * @param class-string $genericClassName */ -function testGetAttributes(string $str, string $className, string $genericClassName): void +function testGetAttributes( + \ReflectionClass $reflectionClass, + \ReflectionMethod $reflectionMethod, + \ReflectionParameter $reflectionParameter, + \ReflectionProperty $reflectionProperty, + \ReflectionClassConstant $reflectionClassConstant, + \ReflectionFunction $reflectionFunction, + string $str, + string $className, + string $genericClassName +): void { - $class = new \ReflectionClass(X::class); - - $attrsAll = $class->getAttributes(); - $attrsAbc1 = $class->getAttributes(Abc::class); - $attrsAbc2 = $class->getAttributes(Abc::class, \ReflectionAttribute::IS_INSTANCEOF); - $attrsGCN = $class->getAttributes($genericClassName); - $attrsCN = $class->getAttributes($className); - $attrsStr = $class->getAttributes($str); - $attrsNonsense = $class->getAttributes("some random string"); - - assertType('array>', $attrsAll); - assertType('array>', $attrsAbc1); - assertType('array>', $attrsAbc2); - assertType('array>', $attrsGCN); - assertType('array>', $attrsCN); - assertType('array>', $attrsStr); - assertType('array>', $attrsNonsense); + $classAll = $reflectionClass->getAttributes(); + $classAbc1 = $reflectionClass->getAttributes(Abc::class); + $classAbc2 = $reflectionClass->getAttributes(Abc::class, \ReflectionAttribute::IS_INSTANCEOF); + $classGCN = $reflectionClass->getAttributes($genericClassName); + $classCN = $reflectionClass->getAttributes($className); + $classStr = $reflectionClass->getAttributes($str); + $classNonsense = $reflectionClass->getAttributes("some random string"); + + assertType('array>', $classAll); + assertType('array>', $classAbc1); + assertType('array>', $classAbc2); + assertType('array>', $classGCN); + assertType('array>', $classCN); + assertType('array>', $classStr); + assertType('array>', $classNonsense); + + $methodAll = $reflectionMethod->getAttributes(); + $methodAbc = $reflectionMethod->getAttributes(Abc::class); + assertType('array>', $methodAll); + assertType('array>', $methodAbc); + + $paramAll = $reflectionParameter->getAttributes(); + $paramAbc = $reflectionParameter->getAttributes(Abc::class); + assertType('array>', $paramAll); + assertType('array>', $paramAbc); + + $propAll = $reflectionProperty->getAttributes(); + $propAbc = $reflectionProperty->getAttributes(Abc::class); + assertType('array>', $propAll); + assertType('array>', $propAbc); + + $constAll = $reflectionClassConstant->getAttributes(); + $constAbc = $reflectionClassConstant->getAttributes(Abc::class); + assertType('array>', $constAll); + assertType('array>', $constAbc); + + $funcAll = $reflectionFunction->getAttributes(); + $funcAbc = $reflectionFunction->getAttributes(Abc::class); + assertType('array>', $funcAll); + assertType('array>', $funcAbc); } /** From a09a98d251002a061c96e7a49132693d46a0dee9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 25 Aug 2021 21:09:03 +0200 Subject: [PATCH 0163/1284] OptimizedDirectorySourceLocator - fixed crash when parsing Text.php of nl_BE from Faker --- .../OptimizedDirectorySourceLocator.php | 3 + .../OptimizedDirectorySourceLocatorTest.php | 11 + .../SourceLocator/data/bug-5525.php | 25348 ++++++++++++++++ 3 files changed, 25362 insertions(+) create mode 100644 tests/PHPStan/Reflection/BetterReflection/SourceLocator/data/bug-5525.php diff --git a/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php b/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php index 9aaa11a6e4..f92892575e 100644 --- a/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php +++ b/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php @@ -203,6 +203,9 @@ private function findSymbols(string $file): array // strip heredocs/nowdocs $contents = preg_replace('{<<<[ \t]*([\'"]?)(\w+)\\1(?:\r\n|\n|\r)(?:.*?)(?:\r\n|\n|\r)(?:\s*)\\2(?=\s+|[;,.)])}s', 'null', $contents); + if ($contents === null) { + return ['classes' => [], 'functions' => []]; + } // strip strings $contents = preg_replace('{"[^"\\\\]*+(\\\\.[^"\\\\]*+)*+"|\'[^\'\\\\]*+(\\\\.[^\'\\\\]*+)*+\'}s', 'null', $contents); // strip leading non-php code if needed diff --git a/tests/PHPStan/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorTest.php b/tests/PHPStan/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorTest.php index cce937f43d..78562c3f33 100644 --- a/tests/PHPStan/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorTest.php +++ b/tests/PHPStan/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorTest.php @@ -135,4 +135,15 @@ public function testFunctionDoesNotExist(string $functionName): void $functionReflector->reflect($functionName); } + public function testBug5525(): void + { + $factory = self::getContainer()->getByType(OptimizedDirectorySourceLocatorFactory::class); + $locator = $factory->createByFiles([__DIR__ . '/data/bug-5525.php']); + $classReflector = new ClassReflector($locator); + $functionReflector = new FunctionReflector($locator, $classReflector); + + $this->expectException(IdentifierNotFound::class); + $functionReflector->reflect('spl_autoload_register'); + } + } diff --git a/tests/PHPStan/Reflection/BetterReflection/SourceLocator/data/bug-5525.php b/tests/PHPStan/Reflection/BetterReflection/SourceLocator/data/bug-5525.php new file mode 100644 index 0000000000..7bc1aeb4b9 --- /dev/null +++ b/tests/PHPStan/Reflection/BetterReflection/SourceLocator/data/bug-5525.php @@ -0,0 +1,25348 @@ + Date: Wed, 25 Aug 2021 21:22:16 +0200 Subject: [PATCH 0164/1284] More fixes --- phpstan-baseline.neon | 25 ------------------- .../OptimizedDirectorySourceLocator.php | 12 +++++++++ 2 files changed, 12 insertions(+), 25 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 255ba887d3..ce31795698 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -130,31 +130,6 @@ parameters: count: 1 path: src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php - - - message: "#^Parameter \\#1 \\$haystack of function strrpos expects string, string\\|null given\\.$#" - count: 1 - path: src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php - - - - message: "#^Parameter \\#1 \\$string of function substr expects string, string\\|null given\\.$#" - count: 3 - path: src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php - - - - message: "#^Parameter \\#2 \\$subject of function preg_match expects string, string\\|null given\\.$#" - count: 1 - path: src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php - - - - message: "#^Parameter \\#2 \\$subject of function preg_match_all expects string, string\\|null given\\.$#" - count: 1 - path: src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php - - - - message: "#^Parameter \\#3 \\$subject of function preg_replace expects array\\|string, string\\|null given\\.$#" - count: 4 - path: src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php - - message: "#^Method PHPStan\\\\Reflection\\\\ClassReflection\\:\\:__construct\\(\\) has parameter \\$reflection with generic class ReflectionClass but does not specify its types\\: T$#" count: 1 diff --git a/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php b/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php index f92892575e..5add094897 100644 --- a/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php +++ b/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php @@ -208,15 +208,24 @@ private function findSymbols(string $file): array } // strip strings $contents = preg_replace('{"[^"\\\\]*+(\\\\.[^"\\\\]*+)*+"|\'[^\'\\\\]*+(\\\\.[^\'\\\\]*+)*+\'}s', 'null', $contents); + if ($contents === null) { + return ['classes' => [], 'functions' => []]; + } // strip leading non-php code if needed if (substr($contents, 0, 2) !== ' [], 'functions' => []]; + } if ($replacements === 0) { return ['classes' => [], 'functions' => []]; } } // strip non-php blocks in the file $contents = preg_replace('{\?>(?:[^<]++|<(?!\?))*+<\?}s', '?> [], 'functions' => []]; + } // strip trailing non-php code if needed $pos = strrpos($contents, '?>'); if ($pos !== false && strpos(substr($contents, $pos), ' [], 'functions' => []]; + } } preg_match_all('{ From bf6181ea3d26b4431bceb0a1a4b7175f46478d48 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 25 Aug 2021 21:19:07 +0200 Subject: [PATCH 0165/1284] use positive-int in iterator_count() / iterator_apply() --- resources/functionMap.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 08d2b18169..b07e863521 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -5663,8 +5663,8 @@ 'Iterator::next' => ['void'], 'Iterator::rewind' => ['void'], 'Iterator::valid' => ['bool'], -'iterator_apply' => ['int', 'iterator'=>'Traversable', 'function'=>'callable', 'params='=>'array'], -'iterator_count' => ['int', 'iterator'=>'Traversable'], +'iterator_apply' => ['0|positive-int', 'iterator'=>'Traversable', 'function'=>'callable', 'params='=>'array'], +'iterator_count' => ['0|positive-int', 'iterator'=>'Traversable'], 'iterator_to_array' => ['array', 'iterator'=>'Traversable', 'use_keys='=>'bool'], 'IteratorAggregate::getIterator' => ['Traversable'], 'IteratorIterator::__construct' => ['void', 'iterator'=>'Traversable'], From 69c3b3bc0813c6c640ce0f6f25d17268f4ac79c9 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 25 Aug 2021 21:31:53 +0200 Subject: [PATCH 0166/1284] use positive-int in array_count_values() --- resources/functionMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index b07e863521..142179f9f2 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -263,7 +263,7 @@ 'array_chunk' => ['array[]', 'input'=>'array', 'size'=>'int', 'preserve_keys='=>'bool'], 'array_column' => ['array', 'array'=>'array', 'column_key'=>'mixed', 'index_key='=>'mixed'], 'array_combine' => ['array|false', 'keys'=>'array', 'values'=>'array'], -'array_count_values' => ['int[]', 'input'=>'array'], +'array_count_values' => ['array<0|positive-int>', 'input'=>'array'], 'array_diff' => ['array', 'arr1'=>'array', 'arr2'=>'array', '...args='=>'array'], 'array_diff_assoc' => ['array', 'arr1'=>'array', 'arr2'=>'array', '...args='=>'array'], 'array_diff_key' => ['array', 'arr1'=>'array', 'arr2'=>'array', '...args='=>'array'], From 30c9b01fd12bf2a1f655b369629c3a38acff978e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 25 Aug 2021 21:31:02 +0200 Subject: [PATCH 0167/1284] Fix ?parent in return type --- src/Analyser/MutatingScope.php | 2 +- .../Properties/OverridingPropertyRule.php | 4 ++-- src/Type/ParserNodeTypeToPHPStanType.php | 17 +++++++------ .../Analyser/AnalyserIntegrationTest.php | 6 +++++ .../Analyser/NodeScopeResolverTest.php | 2 ++ tests/PHPStan/Analyser/data/bug-5529.php | 24 +++++++++++++++++++ 6 files changed, 45 insertions(+), 10 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/bug-5529.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 3e76a98ef8..872ecc05ba 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -3322,7 +3322,7 @@ public function getFunctionType($type, bool $isNullable, bool $isVariadic): Type } } - return ParserNodeTypeToPHPStanType::resolve($type, $this->isInClass() ? $this->getClassReflection()->getName() : null); + return ParserNodeTypeToPHPStanType::resolve($type, $this->isInClass() ? $this->getClassReflection() : null); } public function enterForeach(Expr $iteratee, string $valueName, ?string $keyName): self diff --git a/src/Rules/Properties/OverridingPropertyRule.php b/src/Rules/Properties/OverridingPropertyRule.php index 07e55ec642..9f5bc22434 100644 --- a/src/Rules/Properties/OverridingPropertyRule.php +++ b/src/Rules/Properties/OverridingPropertyRule.php @@ -123,7 +123,7 @@ public function processNode(Node $node, Scope $scope): array $prototype->getNativeType()->describe(VerbosityLevel::typeOnly()) ))->nonIgnorable()->build(); } else { - $nativeType = ParserNodeTypeToPHPStanType::resolve($node->getNativeType(), $scope->getClassReflection()->getName()); + $nativeType = ParserNodeTypeToPHPStanType::resolve($node->getNativeType(), $scope->getClassReflection()); if (!$prototype->getNativeType()->equals($nativeType)) { $typeErrors[] = RuleErrorBuilder::message(sprintf( 'Type %s of property %s::$%s is not the same as type %s of overridden property %s::$%s.', @@ -141,7 +141,7 @@ public function processNode(Node $node, Scope $scope): array 'Property %s::$%s (%s) overriding property %s::$%s should not have a native type.', $classReflection->getDisplayName(), $node->getName(), - ParserNodeTypeToPHPStanType::resolve($node->getNativeType(), $scope->getClassReflection()->getName())->describe(VerbosityLevel::typeOnly()), + ParserNodeTypeToPHPStanType::resolve($node->getNativeType(), $scope->getClassReflection())->describe(VerbosityLevel::typeOnly()), $prototype->getDeclaringClass()->getDisplayName(), $node->getName() ))->nonIgnorable()->build(); diff --git a/src/Type/ParserNodeTypeToPHPStanType.php b/src/Type/ParserNodeTypeToPHPStanType.php index a6910b65ae..00d3baf951 100644 --- a/src/Type/ParserNodeTypeToPHPStanType.php +++ b/src/Type/ParserNodeTypeToPHPStanType.php @@ -4,6 +4,7 @@ use PhpParser\Node\Name; use PhpParser\Node\NullableType; +use PHPStan\Reflection\ClassReflection; use PHPStan\Type\Constant\ConstantBooleanType; class ParserNodeTypeToPHPStanType @@ -11,22 +12,24 @@ class ParserNodeTypeToPHPStanType /** * @param \PhpParser\Node\Name|\PhpParser\Node\Identifier|\PhpParser\Node\NullableType|\PhpParser\Node\UnionType|null $type - * @param string|null $className + * @param ClassReflection|null $classReflection * @return Type */ - public static function resolve($type, ?string $className): Type + public static function resolve($type, ?ClassReflection $classReflection): Type { if ($type === null) { return new MixedType(); } elseif ($type instanceof Name) { $typeClassName = (string) $type; $lowercasedClassName = strtolower($typeClassName); - if ($className !== null && in_array($lowercasedClassName, ['self', 'static'], true)) { - $typeClassName = $className; + if ($classReflection !== null && in_array($lowercasedClassName, ['self', 'static'], true)) { + $typeClassName = $classReflection->getName(); } elseif ( $lowercasedClassName === 'parent' + && $classReflection !== null + && $classReflection->getParentClass() !== false ) { - throw new \PHPStan\ShouldNotHappenException('parent type is not supported here'); + $typeClassName = $classReflection->getParentClass()->getName(); } if ($lowercasedClassName === 'static') { @@ -35,11 +38,11 @@ public static function resolve($type, ?string $className): Type return new ObjectType($typeClassName); } elseif ($type instanceof NullableType) { - return TypeCombinator::addNull(self::resolve($type->type, $className)); + return TypeCombinator::addNull(self::resolve($type->type, $classReflection)); } elseif ($type instanceof \PhpParser\Node\UnionType) { $types = []; foreach ($type->types as $unionTypeType) { - $types[] = self::resolve($unionTypeType, $className); + $types[] = self::resolve($unionTypeType, $classReflection); } return TypeCombinator::union(...$types); diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 4cff1f74e5..74f051acd9 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -426,6 +426,12 @@ public function testBug5231Two(): void $this->assertNotEmpty($errors); } + public function testBug5529(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-5529.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 16641b436f..7f7ced314a 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -477,6 +477,8 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/literal-string.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/filter-var-returns-non-empty-string.php'); + + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5529.php'); } /** diff --git a/tests/PHPStan/Analyser/data/bug-5529.php b/tests/PHPStan/Analyser/data/bug-5529.php new file mode 100644 index 0000000000..1e787f602e --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-5529.php @@ -0,0 +1,24 @@ +run()); +}; From 707cfb255aa6b3dc2df98ac88d1813bddceec3b1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 25 Aug 2021 21:59:43 +0200 Subject: [PATCH 0168/1284] Constants --- src/Analyser/MutatingScope.php | 2 +- src/Type/Constant/ConstantArrayTypeBuilder.php | 2 ++ src/Type/ConstantTypeHelper.php | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 872ecc05ba..8316e44204 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -1609,7 +1609,7 @@ private function resolveType(Expr $node): Type } elseif ($node instanceof Array_) { $arrayBuilder = ConstantArrayTypeBuilder::createEmpty(); - if (count($node->items) > 256) { + if (count($node->items) > ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT) { $arrayBuilder->degradeToGeneralArray(); } foreach ($node->items as $arrayItem) { diff --git a/src/Type/Constant/ConstantArrayTypeBuilder.php b/src/Type/Constant/ConstantArrayTypeBuilder.php index 6fd74c32e1..aebd713e9e 100644 --- a/src/Type/Constant/ConstantArrayTypeBuilder.php +++ b/src/Type/Constant/ConstantArrayTypeBuilder.php @@ -14,6 +14,8 @@ class ConstantArrayTypeBuilder { + public const ARRAY_COUNT_LIMIT = 256; + /** @var array */ private array $keyTypes; diff --git a/src/Type/ConstantTypeHelper.php b/src/Type/ConstantTypeHelper.php index 287b95ea58..837a7f5f26 100644 --- a/src/Type/ConstantTypeHelper.php +++ b/src/Type/ConstantTypeHelper.php @@ -32,7 +32,7 @@ public static function getTypeFromValue($value): Type return new ConstantStringType($value); } elseif (is_array($value)) { $arrayBuilder = ConstantArrayTypeBuilder::createEmpty(); - if (count($value) > 256) { + if (count($value) > ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT) { $arrayBuilder->degradeToGeneralArray(); } foreach ($value as $k => $v) { From a6ca5f7a7ef60f20ca9c2f5ec5410d28224e2db8 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 25 Aug 2021 22:03:52 +0200 Subject: [PATCH 0169/1284] Optimize calculating scalar values from huge unions --- src/Analyser/MutatingScope.php | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 8316e44204..abae6af931 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -101,6 +101,8 @@ class MutatingScope implements Scope { + public const CALCULATE_SCALARS_LIMIT = 128; + private const OPERATOR_SIGIL_MAP = [ Node\Expr\AssignOp\Plus::class => '+', Node\Expr\AssignOp\Minus::class => '-', @@ -1099,11 +1101,18 @@ private function resolveType(Expr $node): Type $leftTypes = TypeUtils::getConstantScalars($this->getType($left)); $rightTypes = TypeUtils::getConstantScalars($this->getType($right)); - if (count($leftTypes) > 0 && count($rightTypes) > 0) { + $leftTypesCount = count($leftTypes); + $rightTypesCount = count($rightTypes); + if ($leftTypesCount > 0 && $rightTypesCount > 0) { $resultTypes = []; + $generalize = $leftTypesCount * $rightTypesCount > self::CALCULATE_SCALARS_LIMIT; foreach ($leftTypes as $leftType) { foreach ($rightTypes as $rightType) { - $resultTypes[] = $this->calculateFromScalars($node, $leftType, $rightType); + $resultType = $this->calculateFromScalars($node, $leftType, $rightType); + if ($generalize) { + $resultType = TypeUtils::generalizeType($resultType); + } + $resultTypes[] = $resultType; } } return TypeCombinator::union(...$resultTypes); From bec4be445328542ecd11b587f66d06d78bb9b239 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 25 Aug 2021 22:11:31 +0200 Subject: [PATCH 0170/1284] Real fix From https://github.com/composer/composer/commit/42c6a0d7c50c94d79298248f5f8ae51a522f9844 See https://github.com/composer/composer/pull/10050 --- .../SourceLocator/OptimizedDirectorySourceLocator.php | 2 +- .../SourceLocator/OptimizedDirectorySourceLocatorTest.php | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php b/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php index 5add094897..03dfdd4fa3 100644 --- a/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php +++ b/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php @@ -202,7 +202,7 @@ private function findSymbols(string $file): array } // strip heredocs/nowdocs - $contents = preg_replace('{<<<[ \t]*([\'"]?)(\w+)\\1(?:\r\n|\n|\r)(?:.*?)(?:\r\n|\n|\r)(?:\s*)\\2(?=\s+|[;,.)])}s', 'null', $contents); + $contents = preg_replace('{<<<[ \t]*([\'"]?)(\w+)\\1(?:\r\n|\n|\r)(?:.*(?=[\r\n]+[ \t]*\\2))[\r\n]+[ \t]*\\2(?=\s*[;,.)])}s', 'null', $contents); if ($contents === null) { return ['classes' => [], 'functions' => []]; } diff --git a/tests/PHPStan/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorTest.php b/tests/PHPStan/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorTest.php index 78562c3f33..2399a64366 100644 --- a/tests/PHPStan/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorTest.php +++ b/tests/PHPStan/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorTest.php @@ -140,10 +140,9 @@ public function testBug5525(): void $factory = self::getContainer()->getByType(OptimizedDirectorySourceLocatorFactory::class); $locator = $factory->createByFiles([__DIR__ . '/data/bug-5525.php']); $classReflector = new ClassReflector($locator); - $functionReflector = new FunctionReflector($locator, $classReflector); - $this->expectException(IdentifierNotFound::class); - $functionReflector->reflect('spl_autoload_register'); + $class = $classReflector->reflect(\Faker\Provider\nl_BE\Text::class); + $this->assertSame(\Faker\Provider\nl_BE\Text::class, $class->getName()); } } From bd0b492a6e217cd35d841bb751164f88f2a431cf Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 25 Aug 2021 22:17:47 +0200 Subject: [PATCH 0171/1284] Test for #5527 https://github.com/phpstan/phpstan/issues/5527 --- .../Analyser/AnalyserIntegrationTest.php | 6 ++++++ tests/PHPStan/Analyser/data/bug-5527.php | 20 +++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 tests/PHPStan/Analyser/data/bug-5527.php diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 74f051acd9..d2eb33b39d 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -432,6 +432,12 @@ public function testBug5529(): void $this->assertCount(0, $errors); } + public function testBug5527(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-5527.php'); + $this->assertCount(0, $errors); + } + /** * @param string $file * @return \PHPStan\Analyser\Error[] diff --git a/tests/PHPStan/Analyser/data/bug-5527.php b/tests/PHPStan/Analyser/data/bug-5527.php new file mode 100644 index 0000000000..076e6626fc --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-5527.php @@ -0,0 +1,20 @@ + Date: Wed, 25 Aug 2021 06:09:42 +0200 Subject: [PATCH 0172/1284] support array-shapes in "array_merge" --- ...ergeFunctionDynamicReturnTypeExtension.php | 48 +++++++++++---- .../Analyser/LegacyNodeScopeResolverTest.php | 25 ++++++-- .../Analyser/NodeScopeResolverTest.php | 2 + tests/PHPStan/Analyser/data/array-merge.php | 58 +++++++++++++++++++ 4 files changed, 116 insertions(+), 17 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/array-merge.php diff --git a/src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php index ef48894937..ce04dc3775 100644 --- a/src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php @@ -8,11 +8,11 @@ use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\ArrayType; +use PHPStan\Type\Constant\ConstantArrayTypeBuilder; use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use PHPStan\Type\TypeUtils; -use PHPStan\Type\UnionType; class ArrayMergeFunctionDynamicReturnTypeExtension implements \PHPStan\Type\DynamicFunctionReturnTypeExtension { @@ -30,20 +30,33 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $keyTypes = []; $valueTypes = []; + $returnedArrayBuilder = ConstantArrayTypeBuilder::createEmpty(); + $returnedArrayBuilderFilled = false; $nonEmpty = false; foreach ($functionCall->args as $arg) { $argType = $scope->getType($arg->value); + if ($arg->unpack) { $argType = $argType->getIterableValueType(); - if ($argType instanceof UnionType) { - foreach ($argType->getTypes() as $innerType) { - $argType = $innerType; + } + + $arrays = TypeUtils::getConstantArrays($argType); + if (count($arrays) > 0) { + foreach ($arrays as $constantArray) { + foreach ($constantArray->getKeyTypes() as $i => $keyType) { + $returnedArrayBuilderFilled = true; + + $returnedArrayBuilder->setOffsetValueType( + is_numeric($keyType->getValue()) ? null : $keyType, + $constantArray->getValueTypes()[$i] + ); } } - } - $keyTypes[] = TypeUtils::generalizeType($argType->getIterableKeyType(), GeneralizePrecision::moreSpecific()); - $valueTypes[] = $argType->getIterableValueType(); + } else { + $keyTypes[] = TypeUtils::generalizeType($argType->getIterableKeyType(), GeneralizePrecision::moreSpecific()); + $valueTypes[] = $argType->getIterableValueType(); + } if (!$argType->isIterableAtLeastOnce()->yes()) { continue; @@ -52,10 +65,23 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $nonEmpty = true; } - $arrayType = new ArrayType( - TypeCombinator::union(...$keyTypes), - TypeCombinator::union(...$valueTypes) - ); + if (count($keyTypes) > 0) { + $arrayType = new ArrayType( + TypeCombinator::union(...$keyTypes), + TypeCombinator::union(...$valueTypes) + ); + + if ($returnedArrayBuilderFilled) { + $arrayType = TypeCombinator::union($returnedArrayBuilder->getArray(), $arrayType); + } + } elseif ($returnedArrayBuilderFilled) { + $arrayType = $returnedArrayBuilder->getArray(); + } else { + $arrayType = new ArrayType( + TypeCombinator::union(...$keyTypes), + TypeCombinator::union(...$valueTypes) + ); + } if ($nonEmpty) { $arrayType = TypeCombinator::intersect($arrayType, new NonEmptyArrayType()); diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index ef8ff856e2..c12cd88fa2 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -5153,7 +5153,7 @@ public function dataArrayFunctions(): array 'array_values($generalStringKeys)', ], [ - 'array&nonEmpty', + "array('foo' => stdClass, 0 => stdClass)", 'array_merge($stringOrIntegerKeys)', ], [ @@ -5161,23 +5161,36 @@ public function dataArrayFunctions(): array 'array_merge($generalStringKeys, $generalDateTimeValues)', ], [ - 'array&nonEmpty', + 'array<0|string, int|stdClass>&nonEmpty', 'array_merge($generalStringKeys, $stringOrIntegerKeys)', ], [ - 'array&nonEmpty', + 'array<0|string, int|stdClass>&nonEmpty', 'array_merge($stringOrIntegerKeys, $generalStringKeys)', ], [ - 'array&nonEmpty', + "array('foo' => stdClass, 'bar' => stdClass, 0 => stdClass)", 'array_merge($stringKeys, $stringOrIntegerKeys)', ], [ - 'array&nonEmpty', + "array('foo' => 1, 'bar' => 2, 0 => 2, 1 => 3)", + "array_merge(['foo' => 4, 'bar' => 5], ...[['foo' => 1, 'bar' => 2], [2, 3]])", + ], + [ + "array('foo' => 1, 'foo2' => stdClass)", + 'array_merge([\'foo\' => new stdClass()], ...[[\'foo2\' => new stdClass()], [\'foo\' => 1]])', + ], + + [ + "array('foo' => 1, 'foo2' => stdClass)", + 'array_merge([\'foo\' => new stdClass()], ...[[\'foo2\' => new stdClass()], [\'foo\' => 1]])', + ], + [ + "array('foo' => 'foo', 0 => stdClass, 'bar' => stdClass)", 'array_merge($stringOrIntegerKeys, $stringKeys)', ], [ - 'array&nonEmpty', + "array('color' => 'green', 0 => 2, 1 => 4, 2 => 'a', 3 => 'b', 'shape' => 'trapezoid', 4 => 4)", 'array_merge(array("color" => "red", 2, 4), array("a", "b", "color" => "green", "shape" => "trapezoid", 4))', ], [ diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 7f7ced314a..b1ea0225bf 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -134,6 +134,8 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-expr.php'); } + yield from $this->gatherAssertTypes(__DIR__ . '/data/array-merge.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/non-empty-array.php'); if (PHP_VERSION_ID >= 80000 || self::$useStaticReflectionProvider) { diff --git a/tests/PHPStan/Analyser/data/array-merge.php b/tests/PHPStan/Analyser/data/array-merge.php new file mode 100644 index 0000000000..d6d3ef219c --- /dev/null +++ b/tests/PHPStan/Analyser/data/array-merge.php @@ -0,0 +1,58 @@ + '1', 'bar' => '2', 'lall' => '3', 0 => '2', 1 => '3')", array_merge($array1)); + assertType("array('foo' => '1', 'bar' => '2', 'lall' => '3', 0 => '2', 1 => '3')", array_merge([], $array1)); + assertType("array('foo' => '1', 'bar' => '2', 'lall' => '3', 0 => '2', 1 => '3')", array_merge($array1, [])); + assertType("array('foo' => '1', 'bar' => '2', 'lall' => '3', 0 => '2', 1 => '3', 2 => '2', 3 => '3')", array_merge($array1, $array1)); + assertType("array('foo' => '1', 'bar' => '4', 'lall' => '3', 0 => '2', 1 => '3', 'lall2' => '3', 2 => '4', 3 => '6')", array_merge($array1, $array2)); + assertType("array('foo' => '1', 'bar' => '2', 'lall2' => '3', 0 => '4', 1 => '6', 'lall' => '3', 2 => '2', 3 => '3')", array_merge($array2, $array1)); + assertType("array('foo' => 3, 'bar' => '2', 'lall2' => '3', 0 => '4', 1 => '6', 'lall' => '3', 2 => '2', 3 => '3')", array_merge($array2, $array1, ['foo' => 3])); + assertType("array('foo' => 3, 'bar' => '2', 'lall2' => '3', 0 => '4', 1 => '6', 'lall' => '3', 2 => '2', 3 => '3')", array_merge($array2, $array1, ...[['foo' => 3]])); + } + + /** + * @param int[] $array1 + * @param string[] $array2 + */ + public function arrayMergeSimple($array1, $array2): void + { + assertType("array", array_merge($array1, $array1)); + assertType("array", array_merge($array1, $array2)); + assertType("array", array_merge($array2, $array1)); + } + + /** + * @param array $array1 + * @param array $array2 + */ + public function arrayMergeUnionType($array1, $array2): void + { + assertType("array", array_merge($array1, $array1)); + assertType("array", array_merge($array1, $array2)); + assertType("array", array_merge($array2, $array1)); + } + + /** + * @param array $array1 + * @param array $array2 + */ + public function arrayMergeUnionTypeArrayShapes($array1, $array2): void + { + assertType("array '2')|array('foo' => '1')>", array_merge($array1, $array1)); + assertType("array '2'|'3')|array('foo' => '1'|'2')>", array_merge($array1, $array2)); + assertType("array '2'|'3')|array('foo' => '1'|'2')>", array_merge($array2, $array1)); + } +} From b4b44cf128f93f05754ceef9229fa4ec41f0c4c6 Mon Sep 17 00:00:00 2001 From: lmoelleken Date: Wed, 25 Aug 2021 16:09:53 +0200 Subject: [PATCH 0173/1284] Add more tests for "array_merge" --- tests/PHPStan/Analyser/data/array-merge.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/PHPStan/Analyser/data/array-merge.php b/tests/PHPStan/Analyser/data/array-merge.php index d6d3ef219c..960db00a48 100644 --- a/tests/PHPStan/Analyser/data/array-merge.php +++ b/tests/PHPStan/Analyser/data/array-merge.php @@ -7,6 +7,21 @@ class Foo { + private const DEFAULT_SETTINGS = [ + 'remove' => 'first', + 'limit' => PHP_INT_MAX, + ]; + + /** + * @param array $settings + */ + public function arrayMergeWithConst(array $settings): void + { + $settings = array_merge(self::DEFAULT_SETTINGS, $settings); + + assertType("array&nonEmpty", $settings); + } + /** * @param array{foo: '1', bar: '2', lall: '3', 2: '2', 3: '3'} $array1 * @param array{foo: '1', bar: '4', lall2: '3', 2: '4', 3: '6'} $array2 @@ -32,6 +47,8 @@ public function arrayMergeSimple($array1, $array2): void assertType("array", array_merge($array1, $array1)); assertType("array", array_merge($array1, $array2)); assertType("array", array_merge($array2, $array1)); + + assertType("array('foo' => '')", array_merge(['foo' => ''])); // issue #2567 } /** From 71144651be31ea6b14942b8d5b0a3b1be090dd22 Mon Sep 17 00:00:00 2001 From: lmoelleken Date: Wed, 25 Aug 2021 06:16:04 +0200 Subject: [PATCH 0174/1284] Add DynamicFunctionReturnTypeExtension for array_replace --- conf/config.neon | 5 + ...laceFunctionDynamicReturnTypeExtension.php | 93 +++++++++++++++++++ .../Analyser/LegacyNodeScopeResolverTest.php | 4 + .../Analyser/NodeScopeResolverTest.php | 2 + tests/PHPStan/Analyser/data/array-replace.php | 58 ++++++++++++ .../PHPStan/Analyser/data/non-empty-array.php | 5 + 6 files changed, 167 insertions(+) create mode 100644 src/Type/Php/ArrayReplaceFunctionDynamicReturnTypeExtension.php create mode 100644 tests/PHPStan/Analyser/data/array-replace.php diff --git a/conf/config.neon b/conf/config.neon index 3c183f59bf..cb04f7b0bf 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -973,6 +973,11 @@ services: tags: - phpstan.broker.dynamicFunctionReturnTypeExtension + - + class: PHPStan\Type\Php\ArrayReplaceFunctionDynamicReturnTypeExtension + tags: + - phpstan.broker.dynamicFunctionReturnTypeExtension + - class: PHPStan\Type\Php\ArrayKeysFunctionDynamicReturnTypeExtension tags: diff --git a/src/Type/Php/ArrayReplaceFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ArrayReplaceFunctionDynamicReturnTypeExtension.php new file mode 100644 index 0000000000..9cadbb0f06 --- /dev/null +++ b/src/Type/Php/ArrayReplaceFunctionDynamicReturnTypeExtension.php @@ -0,0 +1,93 @@ +getName() === 'array_replace'; + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + if (!isset($functionCall->args[0])) { + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } + + $keyTypes = []; + $valueTypes = []; + $returnedArrayBuilder = ConstantArrayTypeBuilder::createEmpty(); + $returnedArrayBuilderFilled = false; + $nonEmpty = false; + foreach ($functionCall->args as $arg) { + $argType = $scope->getType($arg->value); + + if ($arg->unpack) { + $argType = $argType->getIterableValueType(); + } + + $arrays = TypeUtils::getConstantArrays($argType); + if (count($arrays) > 0) { + foreach ($arrays as $constantArray) { + foreach ($constantArray->getKeyTypes() as $i => $keyType) { + $returnedArrayBuilderFilled = true; + + $returnedArrayBuilder->setOffsetValueType( + $keyType, + $constantArray->getValueTypes()[$i] + ); + } + } + + } else { + $keyTypes[] = TypeUtils::generalizeType($argType->getIterableKeyType(), GeneralizePrecision::moreSpecific()); + $valueTypes[] = $argType->getIterableValueType(); + } + + if (!$argType->isIterableAtLeastOnce()->yes()) { + continue; + } + + $nonEmpty = true; + } + + if (count($keyTypes) > 0) { + $arrayType = new ArrayType( + TypeCombinator::union(...$keyTypes), + TypeCombinator::union(...$valueTypes) + ); + + if ($returnedArrayBuilderFilled) { + $arrayType = TypeCombinator::union($returnedArrayBuilder->getArray(), $arrayType); + } + } elseif ($returnedArrayBuilderFilled) { + $arrayType = $returnedArrayBuilder->getArray(); + } else { + $arrayType = new ArrayType( + TypeCombinator::union(...$keyTypes), + TypeCombinator::union(...$valueTypes) + ); + } + + if ($nonEmpty) { + $arrayType = TypeCombinator::intersect($arrayType, new NonEmptyArrayType()); + } + + return $arrayType; + } + +} diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index c12cd88fa2..68835917f5 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -5152,6 +5152,10 @@ public function dataArrayFunctions(): array 'array', 'array_values($generalStringKeys)', ], + [ + "array('foo' => 'foo', 1 => stdClass, 'bar' => stdClass)", + 'array_replace($stringOrIntegerKeys, $stringKeys)', + ], [ "array('foo' => stdClass, 0 => stdClass)", 'array_merge($stringOrIntegerKeys)', diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index b1ea0225bf..d3a4cecc38 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -136,6 +136,8 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/array-merge.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/array-replace.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/non-empty-array.php'); if (PHP_VERSION_ID >= 80000 || self::$useStaticReflectionProvider) { diff --git a/tests/PHPStan/Analyser/data/array-replace.php b/tests/PHPStan/Analyser/data/array-replace.php new file mode 100644 index 0000000000..b3ae417e45 --- /dev/null +++ b/tests/PHPStan/Analyser/data/array-replace.php @@ -0,0 +1,58 @@ + '1', 'bar' => '2', 'lall' => '3', 2 => '2', 3 => '3')", array_replace($array1)); + assertType("array('foo' => '1', 'bar' => '2', 'lall' => '3', 2 => '2', 3 => '3')", array_replace([], $array1)); + assertType("array('foo' => '1', 'bar' => '2', 'lall' => '3', 2 => '2', 3 => '3')", array_replace($array1, [])); + assertType("array('foo' => '1', 'bar' => '2', 'lall' => '3', 2 => '2', 3 => '3')", array_replace($array1, $array1)); + assertType("array('foo' => '1', 'bar' => '4', 'lall' => '3', 2 => '4', 3 => '6', 'lall2' => '3')", array_replace($array1, $array2)); + assertType("array('foo' => '1', 'bar' => '2', 'lall2' => '3', 2 => '2', 3 => '3', 'lall' => '3')", array_replace($array2, $array1)); + assertType("array('foo' => 3, 'bar' => '2', 'lall2' => '3', 2 => '2', 3 => '3', 'lall' => '3')", array_replace($array2, $array1, ['foo' => 3])); + assertType("array('foo' => 3, 'bar' => '2', 'lall2' => '3', 2 => '2', 3 => '3', 'lall' => '3')", array_replace($array2, $array1, ...[['foo' => 3]])); + } + + /** + * @param int[] $array1 + * @param string[] $array2 + */ + public function arrayReplaceSimple($array1, $array2): void + { + assertType("array", array_merge($array1, $array1)); + assertType("array", array_merge($array1, $array2)); + assertType("array", array_merge($array2, $array1)); + } + + /** + * @param array $array1 + * @param array $array2 + */ + public function arrayReplaceUnionType($array1, $array2): void + { + assertType("array", array_merge($array1, $array1)); + assertType("array", array_merge($array1, $array2)); + assertType("array", array_merge($array2, $array1)); + } + + /** + * @param array $array1 + * @param array $array2 + */ + public function arrayReplaceUnionTypeArrayShapes($array1, $array2): void + { + assertType("array '2')|array('foo' => '1')>", array_merge($array1, $array1)); + assertType("array '2'|'3')|array('foo' => '1'|'2')>", array_merge($array1, $array2)); + assertType("array '2'|'3')|array('foo' => '1'|'2')>", array_merge($array2, $array1)); + } +} diff --git a/tests/PHPStan/Analyser/data/non-empty-array.php b/tests/PHPStan/Analyser/data/non-empty-array.php index 316620e1af..5f3973714d 100644 --- a/tests/PHPStan/Analyser/data/non-empty-array.php +++ b/tests/PHPStan/Analyser/data/non-empty-array.php @@ -49,6 +49,11 @@ public function arrayFunctions($array, $list, $stringArray): void assertType('array&nonEmpty', array_merge($array, [])); assertType('array&nonEmpty', array_merge($array, $array)); + assertType('array&nonEmpty', array_replace($array)); + assertType('array&nonEmpty', array_replace([], $array)); + assertType('array&nonEmpty', array_replace($array, [])); + assertType('array&nonEmpty', array_replace($array, $array)); + assertType('array&nonEmpty', array_flip($array)); assertType('array&nonEmpty', array_flip($stringArray)); } From a37e631f57168ef791b498efb4ffe76a48fe0ac7 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 25 Aug 2021 22:59:32 +0200 Subject: [PATCH 0175/1284] array_replace optimization --- .../Php/ArrayReplaceFunctionDynamicReturnTypeExtension.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Type/Php/ArrayReplaceFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ArrayReplaceFunctionDynamicReturnTypeExtension.php index 9cadbb0f06..756d7a477e 100644 --- a/src/Type/Php/ArrayReplaceFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayReplaceFunctionDynamicReturnTypeExtension.php @@ -33,6 +33,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $returnedArrayBuilder = ConstantArrayTypeBuilder::createEmpty(); $returnedArrayBuilderFilled = false; $nonEmpty = false; + $offsetCount = 0; foreach ($functionCall->args as $arg) { $argType = $scope->getType($arg->value); @@ -45,6 +46,11 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, foreach ($arrays as $constantArray) { foreach ($constantArray->getKeyTypes() as $i => $keyType) { $returnedArrayBuilderFilled = true; + $offsetCount++; + + if ($offsetCount > ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT) { + $returnedArrayBuilder->degradeToGeneralArray(); + } $returnedArrayBuilder->setOffsetValueType( $keyType, From 73406572636be4ae9c63ae892cef9e733181116d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 25 Aug 2021 23:00:07 +0200 Subject: [PATCH 0176/1284] array_merge optimization --- .../Php/ArrayMergeFunctionDynamicReturnTypeExtension.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php index ce04dc3775..78b3524a63 100644 --- a/src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php @@ -33,6 +33,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $returnedArrayBuilder = ConstantArrayTypeBuilder::createEmpty(); $returnedArrayBuilderFilled = false; $nonEmpty = false; + $offsetCount = 0; foreach ($functionCall->args as $arg) { $argType = $scope->getType($arg->value); @@ -45,6 +46,10 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, foreach ($arrays as $constantArray) { foreach ($constantArray->getKeyTypes() as $i => $keyType) { $returnedArrayBuilderFilled = true; + $offsetCount++; + if ($offsetCount > ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT) { + $returnedArrayBuilder->degradeToGeneralArray(); + } $returnedArrayBuilder->setOffsetValueType( is_numeric($keyType->getValue()) ? null : $keyType, From fc7bcff8b5d7d3736e2cb618503b97833b99749d Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 27 Aug 2021 09:21:51 +0200 Subject: [PATCH 0177/1284] sizeof() is an alias of count() --- src/Analyser/TypeSpecifier.php | 2 +- .../Comparison/ImpossibleCheckTypeHelper.php | 2 +- .../Php/CountFunctionReturnTypeExtension.php | 2 +- .../CountFunctionTypeSpecifyingExtension.php | 2 +- tests/PHPStan/Analyser/TypeSpecifierTest.php | 22 ++++++++++++++ tests/PHPStan/Analyser/data/count-type.php | 1 + .../Variables/DefinedVariableRuleTest.php | 12 ++++---- .../PHPStan/Rules/Variables/data/foreach.php | 29 +++++++++++++++++++ 8 files changed, 62 insertions(+), 10 deletions(-) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 375ec1e81b..baf007cb86 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -206,7 +206,7 @@ public function specifyTypesInCondition( && $exprNode instanceof FuncCall && count($exprNode->args) === 1 && $exprNode->name instanceof Name - && strtolower((string) $exprNode->name) === 'count' + && in_array(strtolower((string) $exprNode->name), ['count', 'sizeof'], true) && $constantType instanceof ConstantIntegerType ) { if ($context->truthy() || $constantType->getValue() === 0) { diff --git a/src/Rules/Comparison/ImpossibleCheckTypeHelper.php b/src/Rules/Comparison/ImpossibleCheckTypeHelper.php index a49038600a..141d2c278b 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeHelper.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeHelper.php @@ -78,7 +78,7 @@ public function findSpecifiedType( ], true)) { return null; } - if ($functionName === 'count') { + if (in_array($functionName, ['count', 'sizeof'], true)) { return null; } elseif ($functionName === 'defined') { return null; diff --git a/src/Type/Php/CountFunctionReturnTypeExtension.php b/src/Type/Php/CountFunctionReturnTypeExtension.php index 088717c349..542c011026 100644 --- a/src/Type/Php/CountFunctionReturnTypeExtension.php +++ b/src/Type/Php/CountFunctionReturnTypeExtension.php @@ -17,7 +17,7 @@ class CountFunctionReturnTypeExtension implements \PHPStan\Type\DynamicFunctionR public function isFunctionSupported(FunctionReflection $functionReflection): bool { - return $functionReflection->getName() === 'count'; + return in_array($functionReflection->getName(), ['sizeof', 'count'], true); } public function getTypeFromFunctionCall( diff --git a/src/Type/Php/CountFunctionTypeSpecifyingExtension.php b/src/Type/Php/CountFunctionTypeSpecifyingExtension.php index 7fdde34e13..df5b19df30 100644 --- a/src/Type/Php/CountFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/CountFunctionTypeSpecifyingExtension.php @@ -27,7 +27,7 @@ public function isFunctionSupported( { return !$context->null() && count($node->args) >= 1 - && $functionReflection->getName() === 'count'; + && in_array($functionReflection->getName(), ['sizeof', 'count'], true); } public function specifyTypes( diff --git a/tests/PHPStan/Analyser/TypeSpecifierTest.php b/tests/PHPStan/Analyser/TypeSpecifierTest.php index f71043a5e8..a6e56b4461 100644 --- a/tests/PHPStan/Analyser/TypeSpecifierTest.php +++ b/tests/PHPStan/Analyser/TypeSpecifierTest.php @@ -584,6 +584,28 @@ public function dataCondition(): array '$array' => 'nonEmpty', ], ], + [ + new FuncCall(new Name('sizeof'), [ + new Arg(new Variable('array')), + ]), + [ + '$array' => 'nonEmpty', + ], + [ + '$array' => '~nonEmpty', + ], + ], + [ + new BooleanNot(new FuncCall(new Name('sizeof'), [ + new Arg(new Variable('array')), + ])), + [ + '$array' => '~nonEmpty', + ], + [ + '$array' => 'nonEmpty', + ], + ], [ new Variable('foo'), [ diff --git a/tests/PHPStan/Analyser/data/count-type.php b/tests/PHPStan/Analyser/data/count-type.php index 5f99ef117e..58dadbdd4a 100644 --- a/tests/PHPStan/Analyser/data/count-type.php +++ b/tests/PHPStan/Analyser/data/count-type.php @@ -15,6 +15,7 @@ public function doFoo( ) { assertType('int<1, max>', count($nonEmpty)); + assertType('int<1, max>', sizeof($nonEmpty)); } } diff --git a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php index 9a3c6b606a..f09b906189 100644 --- a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php +++ b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php @@ -452,27 +452,27 @@ public function testForeach(): void ], [ 'Undefined variable: $val', - 171, + 200, ], [ 'Undefined variable: $test', - 172, + 201, ], [ 'Undefined variable: $val', - 187, + 216, ], [ 'Undefined variable: $test', - 188, + 217, ], [ 'Variable $val might not be defined.', - 217, + 246, ], [ 'Variable $test might not be defined.', - 218, + 247, ], ]); } diff --git a/tests/PHPStan/Rules/Variables/data/foreach.php b/tests/PHPStan/Rules/Variables/data/foreach.php index 7d145465d8..7b050056f5 100644 --- a/tests/PHPStan/Rules/Variables/data/foreach.php +++ b/tests/PHPStan/Rules/Variables/data/foreach.php @@ -107,6 +107,35 @@ function (array $arr) { }; +function (array $arr) { + + if (sizeof($arr) === 0) { + return; + } + + foreach ($arr as $val) { + $test = 1; + } + + echo $val; + echo $test; + +}; + +function (array $arr) { + + if (sizeof($arr) === 0) { + return; + } + + if ($arr) { + $test = 1; + } + + echo $test; + +}; + /*function (array $arr) { if (count($arr) > 0) { From 03d8312e3ea62bb2c6a3ed89a88a6d86101a5594 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 27 Aug 2021 09:46:14 +0200 Subject: [PATCH 0178/1284] Fixed missing return rule for native mixed type --- src/Rules/Missing/MissingReturnRule.php | 1 + .../Analyser/NodeScopeResolverTest.php | 4 +++ tests/PHPStan/Analyser/data/model-mixin.php | 30 +++++++++++++++++ .../Rules/Missing/MissingReturnRuleTest.php | 32 +++++++++++++++++++ 4 files changed, 67 insertions(+) create mode 100644 tests/PHPStan/Analyser/data/model-mixin.php diff --git a/src/Rules/Missing/MissingReturnRule.php b/src/Rules/Missing/MissingReturnRule.php index ffa7b2759a..641352b3e6 100644 --- a/src/Rules/Missing/MissingReturnRule.php +++ b/src/Rules/Missing/MissingReturnRule.php @@ -114,6 +114,7 @@ public function processNode(Node $node, Scope $scope): array if ( $returnType instanceof MixedType && !$returnType instanceof TemplateMixedType + && !$node->hasNativeReturnTypehint() && ( !$returnType->isExplicitMixed() || !$this->checkExplicitMixedMissingReturn diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index d3a4cecc38..2e1d412e3d 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -482,6 +482,10 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/filter-var-returns-non-empty-string.php'); + if (PHP_VERSION_ID >= 80000 || self::$useStaticReflectionProvider) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/model-mixin.php'); + } + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5529.php'); } diff --git a/tests/PHPStan/Analyser/data/model-mixin.php b/tests/PHPStan/Analyser/data/model-mixin.php new file mode 100644 index 0000000000..69bd29565a --- /dev/null +++ b/tests/PHPStan/Analyser/data/model-mixin.php @@ -0,0 +1,30 @@ += 8.0 + +namespace ModelMixin; + +use function PHPStan\Testing\assertType; + +/** @mixin Builder */ +class Model +{ + /** @param array $args */ + public static function __callStatic(string $method, array $args): mixed + { + (new self)->$method(...$args); + } +} + +/** @template TModel as Model */ +class Builder +{ + /** @return array */ + public function all() { return []; } +} + +class User extends Model +{ +} + +function (): void { + assertType('array', User::all()); +}; diff --git a/tests/PHPStan/Rules/Missing/MissingReturnRuleTest.php b/tests/PHPStan/Rules/Missing/MissingReturnRuleTest.php index aaa8cf8cc6..2b2640ee7b 100644 --- a/tests/PHPStan/Rules/Missing/MissingReturnRuleTest.php +++ b/tests/PHPStan/Rules/Missing/MissingReturnRuleTest.php @@ -250,4 +250,36 @@ public function testCheckPhpDocMissingReturn(bool $checkPhpDocMissingReturn, arr $this->analyse([__DIR__ . '/data/check-phpdoc-missing-return.php'], $errors); } + public function dataModelMixin(): array + { + return [ + [ + true, + ], + [ + false, + ], + ]; + } + + /** + * @dataProvider dataModelMixin + * @param bool $checkExplicitMixedMissingReturn + */ + public function testModelMixin(bool $checkExplicitMixedMissingReturn): void + { + if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->checkExplicitMixedMissingReturn = $checkExplicitMixedMissingReturn; + $this->checkPhpDocMissingReturn = true; + $this->analyse([__DIR__ . '/../../Analyser/data/model-mixin.php'], [ + [ + 'Method ModelMixin\Model::__callStatic() should return mixed but return statement is missing.', + 13, + ], + ]); + } + } From 114a38f87a7ce2e7a53d55454a61cdfd832ea69f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 27 Aug 2021 10:09:58 +0200 Subject: [PATCH 0179/1284] Make mixin method static if there's __callStatic() in the class --- .../MixinMethodsClassReflectionExtension.php | 1 - .../Rules/Methods/CallMethodsRuleTest.php | 8 ++++ .../Methods/CallStaticMethodsRuleTest.php | 6 +++ tests/PHPStan/Rules/Methods/data/bug-5536.php | 47 +++++++++++++++++++ 4 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Rules/Methods/data/bug-5536.php diff --git a/src/Reflection/Mixin/MixinMethodsClassReflectionExtension.php b/src/Reflection/Mixin/MixinMethodsClassReflectionExtension.php index f5af02cc28..6c47734dcd 100644 --- a/src/Reflection/Mixin/MixinMethodsClassReflectionExtension.php +++ b/src/Reflection/Mixin/MixinMethodsClassReflectionExtension.php @@ -54,7 +54,6 @@ private function findMethod(ClassReflection $classReflection, string $methodName if ( !$static && $classReflection->hasNativeMethod('__callStatic') - && !$classReflection->hasNativeMethod('__call') ) { $static = true; } diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 1548d92209..2677247476 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -2029,6 +2029,14 @@ public function testNonEmptyStringVerbosity(): void ]); } + public function testBug5536(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/bug-5536.php'], []); + } + public function testBug5372(): void { if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { diff --git a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php index e3fafa83c9..bcda414fe4 100644 --- a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php @@ -445,4 +445,10 @@ public function testBug5259(): void $this->analyse([__DIR__ . '/data/bug-5259.php'], []); } + public function testBug5536(): void + { + $this->checkThisOnly = false; + $this->analyse([__DIR__ . '/data/bug-5536.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-5536.php b/tests/PHPStan/Rules/Methods/data/bug-5536.php new file mode 100644 index 0000000000..18c305a9e3 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-5536.php @@ -0,0 +1,47 @@ + + */ +class Model +{ + /** + * @param array $args + */ + public function __call(string $method, array $args) + { + return $this->$method; + } + + /** + * @param array $args + */ + public static function __callStatic(string $method, array $args) + { + return (new static)->$method(...$args); + } +} + +/** + * @template TModel of Model + */ +class Builder +{ + /** + * @return array + */ + public function all(): array + { + return []; + } +} + +class User extends Model {} + +function (): void { + User::all(); + $user = new User(); + $user->all(); +}; From cee5bbe40baaebe7be9d5f3fce955cbf25ccf154 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 27 Aug 2021 12:50:26 +0200 Subject: [PATCH 0180/1284] added `sizeof()` as alias of `count()` for Smaller/SmallerOrEqual --- src/Analyser/TypeSpecifier.php | 26 ++++++++-- .../Analyser/NodeScopeResolverTest.php | 2 + tests/PHPStan/Analyser/data/sizeof.php | 47 +++++++++++++++++++ 3 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/sizeof.php diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index baf007cb86..1550791cd3 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -326,6 +326,26 @@ public function specifyTypesInCondition( $context->true() ? TypeSpecifierContext::createTruthy() : TypeSpecifierContext::createTruthy()->negate() ); } + + if ( + !$context->null() + && $exprNode instanceof FuncCall + && count($exprNode->args) === 1 + && $exprNode->name instanceof Name + && in_array(strtolower((string) $exprNode->name), ['count', 'sizeof'], true) + && $constantType instanceof ConstantIntegerType + ) { + if ($context->truthy() || $constantType->getValue() === 0) { + $newContext = $context; + if ($constantType->getValue() === 0) { + $newContext = $newContext->negate(); + } + $argType = $scope->getType($exprNode->args[0]->value); + if ($argType->isArray()->yes()) { + return $this->create($exprNode->args[0]->value, new NonEmptyArrayType(), $newContext, false, $scope); + } + } + } } $leftType = $scope->getType($expr->left); @@ -404,11 +424,11 @@ public function specifyTypesInCondition( $expr->left instanceof FuncCall && count($expr->left->args) === 1 && $expr->left->name instanceof Name - && in_array(strtolower((string) $expr->left->name), ['count', 'strlen'], true) + && in_array(strtolower((string) $expr->left->name), ['count', 'sizeof', 'strlen'], true) && ( !$expr->right instanceof FuncCall || !$expr->right->name instanceof Name - || !in_array(strtolower((string) $expr->right->name), ['count', 'strlen'], true) + || !in_array(strtolower((string) $expr->right->name), ['count', 'sizeof', 'strlen'], true) ) ) { $inverseOperator = $expr instanceof Node\Expr\BinaryOp\Smaller @@ -429,7 +449,7 @@ public function specifyTypesInCondition( && $expr->right instanceof FuncCall && count($expr->right->args) === 1 && $expr->right->name instanceof Name - && strtolower((string) $expr->right->name) === 'count' + && in_array(strtolower((string) $expr->right->name), ['count', 'sizeof'], true) && (new IntegerType())->isSuperTypeOf($leftType)->yes() ) { if ( diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 2e1d412e3d..ede143a319 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -487,6 +487,8 @@ public function dataFileAsserts(): iterable } yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5529.php'); + + yield from $this->gatherAssertTypes(__DIR__ . '/data/sizeof.php'); } /** diff --git a/tests/PHPStan/Analyser/data/sizeof.php b/tests/PHPStan/Analyser/data/sizeof.php new file mode 100644 index 0000000000..b9333fe19c --- /dev/null +++ b/tests/PHPStan/Analyser/data/sizeof.php @@ -0,0 +1,47 @@ + Date: Fri, 27 Aug 2021 12:51:23 +0200 Subject: [PATCH 0181/1284] implemented math on IntegerRangeType and ConstantIntegerType --- src/Analyser/MutatingScope.php | 96 +++++++++++++++++++ .../ParentDirectoryRelativePathHelper.php | 4 - .../Analyser/data/integer-range-types.php | 90 ++++++++++++++++- 3 files changed, 183 insertions(+), 7 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index abae6af931..1c6f3d6b17 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -1255,6 +1255,102 @@ private function resolveType(Expr $node): Type $leftType = $this->getType($left); $rightType = $this->getType($right); + if (($leftType instanceof IntegerRangeType || $leftType instanceof ConstantIntegerType || $leftType instanceof UnionType) && + ($rightType instanceof IntegerRangeType || $rightType instanceof ConstantIntegerType || $rightType instanceof UnionType) && + !($node instanceof Node\Expr\BinaryOp\Pow || $node instanceof Node\Expr\AssignOp\Pow)) { + + if ($leftType instanceof ConstantIntegerType) { + $leftMin = $leftType->getValue(); + $leftMax = $leftType->getValue(); + } elseif ($leftType instanceof UnionType) { + $leftMin = null; + $leftMax = null; + + foreach ($leftType->getTypes() as $type) { + if ($type instanceof IntegerRangeType) { + $leftMin = $leftMin !== null ? min($leftMin, $type->getMin()) : $type->getMin(); + $leftMax = max($leftMax, $type->getMax()); + } elseif ($type instanceof ConstantIntegerType) { + if ($node instanceof Node\Expr\BinaryOp\Minus || $node instanceof Node\Expr\AssignOp\Minus || + $node instanceof Node\Expr\BinaryOp\Div || $node instanceof Node\Expr\AssignOp\Div) { + $leftMin = max($leftMin, $type->getValue()); + $leftMax = $leftMax !== null ? min($leftMax, $type->getValue()) : $type->getValue(); + } else { + $leftMin = $leftMin !== null ? min($leftMin, $type->getValue()) : $type->getValue(); + $leftMax = max($leftMax, $type->getValue()); + } + } + } + } else { + $leftMin = $leftType->getMin(); + $leftMax = $leftType->getMax(); + } + + if ($rightType instanceof ConstantIntegerType) { + $rightMin = $rightType->getValue(); + $rightMax = $rightType->getValue(); + } elseif ($rightType instanceof UnionType) { + $rightMin = null; + $rightMax = null; + + foreach ($rightType->getTypes() as $type) { + if ($type instanceof IntegerRangeType) { + $rightMin = $rightMin !== null ? min($rightMin, $type->getMin()) : $type->getMin(); + $rightMax = max($rightMax, $type->getMax()); + } elseif ($type instanceof ConstantIntegerType) { + if ($node instanceof Node\Expr\BinaryOp\Minus || $node instanceof Node\Expr\AssignOp\Minus || + $node instanceof Node\Expr\BinaryOp\Div || $node instanceof Node\Expr\AssignOp\Div) { + $rightMin = max($rightMin, $type->getValue()); + $rightMax = $rightMax !== null ? min($rightMax, $type->getValue()) : $type->getValue(); + } else { + $rightMin = $rightMin !== null ? min($rightMin, $type->getValue()) : $type->getValue(); + $rightMax = max($rightMax, $type->getValue()); + } + } + } + } else { + $rightMin = $rightType->getMin(); + $rightMax = $rightType->getMax(); + } + + if ($node instanceof Node\Expr\BinaryOp\Plus || $node instanceof Node\Expr\AssignOp\Plus) { + $min = $leftMin !== null && $rightMin !== null ? $leftMin + $rightMin : null; + $max = $leftMax !== null && $rightMax !== null ? $leftMax + $rightMax : null; + } elseif ($node instanceof Node\Expr\BinaryOp\Minus || $node instanceof Node\Expr\AssignOp\Minus) { + $min = $leftMin !== null && $rightMin !== null ? $leftMin - $rightMin : null; + $max = $leftMax !== null && $rightMax !== null ? $leftMax - $rightMax : null; + + if ($min !== null && $max !== null && $min > $max) { + [$min, $max] = [$max, $min]; + } + } elseif ($node instanceof Node\Expr\BinaryOp\Mul || $node instanceof Node\Expr\AssignOp\Mul) { + $min = $leftMin !== null && $rightMin !== null ? $leftMin * $rightMin : null; + $max = $leftMax !== null && $rightMax !== null ? $leftMax * $rightMax : null; + } else { + $min = $leftMin !== null && $rightMin !== null ? (int) ($leftMin / $rightMin) : null; + $max = $leftMax !== null && $rightMax !== null ? (int) ($leftMax / $rightMax) : null; + + if ($min !== null && $max !== null && $min > $max) { + [$min, $max] = [$max, $min]; + } + } + + if ($min !== null || $max !== null) { + $integerRange = IntegerRangeType::fromInterval($min, $max); + + if ($node instanceof Node\Expr\BinaryOp\Div || $node instanceof Node\Expr\AssignOp\Div) { + if ($min === $max && $min === 0) { + // division of upper and lower bound turns into a tiny 0.x fraction, which casted to int turns into 0. + // this leads to a useless 0|float type; we return only float instead. + return new FloatType(); + } + return TypeCombinator::union($integerRange, new FloatType()); + } + + return $integerRange; + } + } + $operatorSigil = null; if ($node instanceof BinaryOp) { diff --git a/src/File/ParentDirectoryRelativePathHelper.php b/src/File/ParentDirectoryRelativePathHelper.php index b88c930758..3aa54fd267 100644 --- a/src/File/ParentDirectoryRelativePathHelper.php +++ b/src/File/ParentDirectoryRelativePathHelper.php @@ -54,10 +54,6 @@ public function getFilenameParts(string $filename): array } $dotsCount = $parentPartsCount - $i; - if ($dotsCount < 0) { - throw new \PHPStan\ShouldNotHappenException(); - } - return array_merge(array_fill(0, $dotsCount, '..'), array_slice($filenameParts, $i)); } diff --git a/tests/PHPStan/Analyser/data/integer-range-types.php b/tests/PHPStan/Analyser/data/integer-range-types.php index d25e0d801c..421a67a64b 100644 --- a/tests/PHPStan/Analyser/data/integer-range-types.php +++ b/tests/PHPStan/Analyser/data/integer-range-types.php @@ -152,9 +152,9 @@ function (int $a, int $b, int $c): void { assertType('int<14, max>', $c); - assertType('int', $a * $b); - assertType('int', $b * $c); - assertType('int', $a * $b * $c); + assertType('int<156, max>', $a * $b); + assertType('int<182, max>', $b * $c); + assertType('int<2184, max>', $a * $b * $c); }; class X { @@ -194,4 +194,88 @@ public function supportsPhpdocIntegerRange() { assertType('*ERROR*', $this->error2); assertType('int', $this->int); } + + /** + * @param int $i + * @param 1|2|3 $j + * @param 1|-20|3 $z + * @param positive-int $pi + * @param int<1, 10> $r1 + * @param int<5, 10> $r2 + * @param int $rMin + * @param int<5, max> $rMax + * + * @param 20|40|60 $x + * @param 2|4 $y + */ + public function math($i, $j, $z, $pi, $r1, $r2, $rMin, $rMax, $x, $y) { + assertType('int', $r1 + $i); + assertType('int', $r1 - $i); + assertType('int', $r1 * $i); + assertType('(float|int)', $r1 / $i); + + assertType('int<2, 13>', $r1 + $j); + assertType('int<-2, 9>', $r1 - $j); + assertType('int<1, 30>', $r1 * $j); + assertType('float|int<0, 10>', $r1 / $j); + assertType('int', $rMin * $j); + assertType('int<5, max>', $rMax * $j); + + assertType('int<2, 13>', $j + $r1); + assertType('int<-9, 2>', $j - $r1); + assertType('int<1, 30>', $j * $r1); + assertType('float|int<0, 3>', $j / $r1); + assertType('int', $j * $rMin); + assertType('int<5, max>', $j * $rMax); + + assertType('int<-19, 13>', $r1 + $z); + assertType('int<-2, 30>', $r1 - $z); + assertType('int<-20, 30>', $r1 * $z); + assertType('float', $r1 / $z); + assertType('int', $rMin * $z); + assertType('int<-100, max>', $rMax * $z); + + assertType('int<2, max>', $pi + 1); + assertType('int<-1, max>', $pi - 2); + assertType('int<2, max>', $pi * 2); + assertType('float|int<0, max>', $pi / 2); + assertType('int<2, max>', 1 + $pi); + assertType('int<1, max>', 2 - $pi); + assertType('int<2, max>', 2 * $pi); + assertType('float|int<2, max>', 2 / $pi); + + assertType('int<5, 14>', $r1 + 4); + assertType('int<-3, 6>', $r1 - 4); + assertType('int<4, 40>', $r1 * 4); + assertType('float|int<0, 2>', $r1 / 4); + assertType('int<9, max>', $rMax + 4); + assertType('int<1, max>', $rMax - 4); + assertType('int<20, max>', $rMax * 4); + assertType('float|int<1, max>', $rMax / 4); + + assertType('int<6, 20>', $r1 + $r2); + assertType('int<-4, 0>', $r1 - $r2); + assertType('int<5, 100>', $r1 * $r2); + assertType('float|int<0, 1>', $r1 / $r2); + + assertType('int', $r1 + $rMin); + assertType('int', $r1 - $rMin); + assertType('int', $r1 * $rMin); + assertType('float|int', $r1 / $rMin); + assertType('int', $rMin + $r1); + assertType('int', $rMin - $r1); + assertType('int', $rMin * $r1); + assertType('float|int', $rMin / $r1); + + assertType('int<6, max>', $r1 + $rMax); + assertType('int<-4, max>', $r1 - $rMax); + assertType('int<5, max>', $r1 * $rMax); + assertType('float|int<0, max>', $r1 / $rMax); + assertType('int<6, max>', $rMax + $r1); + assertType('int<4, max>', $rMax - $r1); + assertType('int<5, max>', $rMax * $r1); + assertType('float|int<5, max>', $rMax / $r1); + + assertType('5|10|15|20|30', $x / $y); + } } From a5eb5b5982c867b34c8d92530a01f70f559cac43 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 24 Aug 2021 14:50:33 +0200 Subject: [PATCH 0182/1284] literal-string in the type system --- src/Analyser/MutatingScope.php | 15 +- src/PhpDoc/TypeNodeResolver.php | 3 +- .../Accessory/AccessoryLiteralStringType.php | 181 ++++++++++++++++++ .../Accessory/AccessoryNonEmptyStringType.php | 5 + .../Accessory/AccessoryNumericStringType.php | 5 + src/Type/Accessory/HasOffsetType.php | 5 + src/Type/Accessory/NonEmptyArrayType.php | 5 + src/Type/ArrayType.php | 5 + src/Type/ClassStringType.php | 5 + src/Type/ClosureType.php | 5 + src/Type/Constant/ConstantStringType.php | 14 ++ src/Type/FloatType.php | 5 + src/Type/Generic/TemplateTypeHelper.php | 4 + src/Type/IntersectionType.php | 24 ++- src/Type/IterableType.php | 5 + src/Type/JustNullableTypeTrait.php | 5 + src/Type/MixedType.php | 5 + src/Type/NeverType.php | 5 + src/Type/NullType.php | 5 + src/Type/ObjectType.php | 5 + .../ImplodeFunctionReturnTypeExtension.php | 36 ++-- .../Php/StrPadFunctionReturnTypeExtension.php | 27 ++- .../StrRepeatFunctionReturnTypeExtension.php | 15 +- src/Type/StaticType.php | 5 + src/Type/StrictMixedType.php | 5 + src/Type/StringType.php | 5 + src/Type/Traits/ObjectTypeTrait.php | 5 + src/Type/Type.php | 2 + src/Type/UnionType.php | 7 + src/Type/VerbosityLevel.php | 3 +- src/Type/VoidType.php | 5 + .../Analyser/LegacyNodeScopeResolverTest.php | 8 +- tests/PHPStan/Analyser/ScopeTest.php | 6 +- .../PHPStan/Analyser/data/literal-string.php | 32 +++- .../data/class-implements-out-of-phpstan.php | 5 + .../Rules/Methods/CallMethodsRuleTest.php | 49 +++++ tests/PHPStan/Rules/Methods/data/bug-5372.php | 13 ++ .../Rules/Methods/data/literal-string.php | 70 +++++++ .../Type/Constant/ConstantStringTypeTest.php | 8 +- 39 files changed, 562 insertions(+), 55 deletions(-) create mode 100644 src/Type/Accessory/AccessoryLiteralStringType.php create mode 100644 tests/PHPStan/Rules/Methods/data/literal-string.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 1c6f3d6b17..2f44a911da 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -47,6 +47,7 @@ use PHPStan\Reflection\TrivialParametersAcceptor; use PHPStan\Rules\Properties\PropertyReflectionFinder; use PHPStan\TrinaryLogic; +use PHPStan\Type\Accessory\AccessoryLiteralStringType; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\ArrayType; use PHPStan\Type\BenevolentUnionType; @@ -1051,11 +1052,17 @@ private function resolveType(Expr $node): Type return $leftStringType->append($rightStringType); } + $accessoryTypes = []; if ($leftStringType->isNonEmptyString()->or($rightStringType->isNonEmptyString())->yes()) { - return new IntersectionType([ - new StringType(), - new AccessoryNonEmptyStringType(), - ]); + $accessoryTypes[] = new AccessoryNonEmptyStringType(); + } + + if ($leftStringType->isLiteralString()->and($rightStringType->isLiteralString())->yes()) { + $accessoryTypes[] = new AccessoryLiteralStringType(); + } + + if (count($accessoryTypes) > 0) { + return new IntersectionType([new StringType(), ...$accessoryTypes]); } return new StringType(); diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index 3a73dfbd61..797c4955cd 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -28,6 +28,7 @@ use PHPStan\Reflection\Native\NativeParameterReflection; use PHPStan\Reflection\PassedByReference; use PHPStan\Reflection\ReflectionProvider; +use PHPStan\Type\Accessory\AccessoryLiteralStringType; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; use PHPStan\Type\Accessory\NonEmptyArrayType; @@ -147,7 +148,7 @@ private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameSco return new StringType(); case 'literal-string': - return new StringType(); + return new IntersectionType([new StringType(), new AccessoryLiteralStringType()]); case 'class-string': return new ClassStringType(); diff --git a/src/Type/Accessory/AccessoryLiteralStringType.php b/src/Type/Accessory/AccessoryLiteralStringType.php new file mode 100644 index 0000000000..3cb8869e7f --- /dev/null +++ b/src/Type/Accessory/AccessoryLiteralStringType.php @@ -0,0 +1,181 @@ +isLiteralString(); + } + + public function isSuperTypeOf(Type $type): TrinaryLogic + { + if ($type instanceof CompoundType) { + return $type->isSubTypeOf($this); + } + + if ($this->equals($type)) { + return TrinaryLogic::createYes(); + } + + return $type->isLiteralString(); + } + + public function isSubTypeOf(Type $otherType): TrinaryLogic + { + if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) { + return $otherType->isSuperTypeOf($this); + } + + return $otherType->isLiteralString() + ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()); + } + + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic + { + return $this->isSubTypeOf($acceptingType); + } + + public function equals(Type $type): bool + { + return $type instanceof self; + } + + public function describe(\PHPStan\Type\VerbosityLevel $level): string + { + return 'literal-string'; + } + + public function isOffsetAccessible(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function hasOffsetValueType(Type $offsetType): TrinaryLogic + { + return (new IntegerType())->isSuperTypeOf($offsetType)->and(TrinaryLogic::createMaybe()); + } + + public function getOffsetValueType(Type $offsetType): Type + { + if ($this->hasOffsetValueType($offsetType)->no()) { + return new ErrorType(); + } + + return new StringType(); + } + + public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type + { + return $this; + } + + public function isArray(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function toNumber(): Type + { + return new UnionType([ + $this->toInteger(), + $this->toFloat(), + ]); + } + + public function toInteger(): Type + { + return new IntegerType(); + } + + public function toFloat(): Type + { + return new FloatType(); + } + + public function toString(): Type + { + return $this; + } + + public function toArray(): Type + { + return new ConstantArrayType( + [new ConstantIntegerType(0)], + [$this], + 1 + ); + } + + public function isNumericString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function isNonEmptyString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function isLiteralString(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function traverse(callable $cb): Type + { + return $this; + } + + public static function __set_state(array $properties): Type + { + return new self(); + } + +} diff --git a/src/Type/Accessory/AccessoryNonEmptyStringType.php b/src/Type/Accessory/AccessoryNonEmptyStringType.php index 479bffe81a..72f2f4a19e 100644 --- a/src/Type/Accessory/AccessoryNonEmptyStringType.php +++ b/src/Type/Accessory/AccessoryNonEmptyStringType.php @@ -163,6 +163,11 @@ public function isNonEmptyString(): TrinaryLogic return TrinaryLogic::createYes(); } + public function isLiteralString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + public function traverse(callable $cb): Type { return $this; diff --git a/src/Type/Accessory/AccessoryNumericStringType.php b/src/Type/Accessory/AccessoryNumericStringType.php index 9d9cbd5ec0..da0df04efc 100644 --- a/src/Type/Accessory/AccessoryNumericStringType.php +++ b/src/Type/Accessory/AccessoryNumericStringType.php @@ -159,6 +159,11 @@ public function isNonEmptyString(): TrinaryLogic return TrinaryLogic::createYes(); } + public function isLiteralString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + public function traverse(callable $cb): Type { return $this; diff --git a/src/Type/Accessory/HasOffsetType.php b/src/Type/Accessory/HasOffsetType.php index c55f3ebc83..4ecdc13cf7 100644 --- a/src/Type/Accessory/HasOffsetType.php +++ b/src/Type/Accessory/HasOffsetType.php @@ -136,6 +136,11 @@ public function isNonEmptyString(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function isLiteralString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + public function toNumber(): Type { return new ErrorType(); diff --git a/src/Type/Accessory/NonEmptyArrayType.php b/src/Type/Accessory/NonEmptyArrayType.php index dc1f6cb30d..3e85adcc3c 100644 --- a/src/Type/Accessory/NonEmptyArrayType.php +++ b/src/Type/Accessory/NonEmptyArrayType.php @@ -142,6 +142,11 @@ public function isNonEmptyString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isLiteralString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function toNumber(): Type { return new ErrorType(); diff --git a/src/Type/ArrayType.php b/src/Type/ArrayType.php index 48ffdd6e15..73334e6c34 100644 --- a/src/Type/ArrayType.php +++ b/src/Type/ArrayType.php @@ -200,6 +200,11 @@ public function isNonEmptyString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isLiteralString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isOffsetAccessible(): TrinaryLogic { return TrinaryLogic::createYes(); diff --git a/src/Type/ClassStringType.php b/src/Type/ClassStringType.php index 99bfbb1e27..e1c48ed8eb 100644 --- a/src/Type/ClassStringType.php +++ b/src/Type/ClassStringType.php @@ -75,6 +75,11 @@ public function isNonEmptyString(): TrinaryLogic return TrinaryLogic::createYes(); } + public function isLiteralString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + /** * @param mixed[] $properties * @return Type diff --git a/src/Type/ClosureType.php b/src/Type/ClosureType.php index 31af1f1cc3..c16a2845fd 100644 --- a/src/Type/ClosureType.php +++ b/src/Type/ClosureType.php @@ -411,6 +411,11 @@ public function isNonEmptyString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isLiteralString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + /** * @param mixed[] $properties * @return Type diff --git a/src/Type/Constant/ConstantStringType.php b/src/Type/Constant/ConstantStringType.php index 77e2a5b1e0..b261e9a815 100644 --- a/src/Type/Constant/ConstantStringType.php +++ b/src/Type/Constant/ConstantStringType.php @@ -8,6 +8,7 @@ use PHPStan\Reflection\InaccessibleMethod; use PHPStan\Reflection\TrivialParametersAcceptor; use PHPStan\TrinaryLogic; +use PHPStan\Type\Accessory\AccessoryLiteralStringType; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\ClassStringType; use PHPStan\Type\CompoundType; @@ -247,6 +248,11 @@ public function isNonEmptyString(): TrinaryLogic return TrinaryLogic::createFromBoolean($this->getValue() !== ''); } + public function isLiteralString(): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + public function hasOffsetValueType(Type $offsetType): TrinaryLogic { if ($offsetType instanceof ConstantIntegerType) { @@ -309,6 +315,14 @@ public function generalize(?GeneralizePrecision $precision = null): Type return new IntersectionType([ new StringType(), new AccessoryNonEmptyStringType(), + new AccessoryLiteralStringType(), + ]); + } + + if ($precision !== null && $precision->isMoreSpecific()) { + return new IntersectionType([ + new StringType(), + new AccessoryLiteralStringType(), ]); } diff --git a/src/Type/FloatType.php b/src/Type/FloatType.php index cd270a3a1e..d966b978fd 100644 --- a/src/Type/FloatType.php +++ b/src/Type/FloatType.php @@ -143,6 +143,11 @@ public function isNonEmptyString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isLiteralString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function traverse(callable $cb): Type { return $this; diff --git a/src/Type/Generic/TemplateTypeHelper.php b/src/Type/Generic/TemplateTypeHelper.php index 21fe7664c4..b8d15d47e1 100644 --- a/src/Type/Generic/TemplateTypeHelper.php +++ b/src/Type/Generic/TemplateTypeHelper.php @@ -75,6 +75,10 @@ public static function generalizeType(Type $type): Type return new StringType(); } + if ($type->isLiteralString()->yes()) { + return new StringType(); + } + return $traverse($type); }); } diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index 378fa51da5..8003be5471 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -12,6 +12,7 @@ use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection; use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection; use PHPStan\TrinaryLogic; +use PHPStan\Type\Accessory\AccessoryLiteralStringType; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; use PHPStan\Type\Accessory\AccessoryType; @@ -141,7 +142,7 @@ function () use ($level): string { $typeNames = []; $accessoryTypes = []; foreach ($this->types as $type) { - if ($type instanceof AccessoryNonEmptyStringType) { + if ($type instanceof AccessoryNonEmptyStringType || $type instanceof AccessoryLiteralStringType) { $accessoryTypes[] = $type; } if ($type instanceof AccessoryType && !$type instanceof AccessoryNumericStringType && !$type instanceof NonEmptyArrayType && !$type instanceof AccessoryNonEmptyStringType) { @@ -150,8 +151,10 @@ function () use ($level): string { $typeNames[] = $type->describe($level); } - if (count($accessoryTypes) === 1) { - return $accessoryTypes[0]->describe($level); + if (count($accessoryTypes) > 0) { + return implode('&', array_map(static function (Type $type) use ($level): string { + return $type->describe($level); + }, $accessoryTypes)); } return implode('&', $typeNames); @@ -160,14 +163,16 @@ function () use ($level): string { $typeNames = []; $accessoryTypes = []; foreach ($this->types as $type) { - if ($type instanceof AccessoryNonEmptyStringType) { + if ($type instanceof AccessoryNonEmptyStringType || $type instanceof AccessoryLiteralStringType) { $accessoryTypes[] = $type; } $typeNames[] = $type->describe($level); } - if (count($accessoryTypes) === 1) { - return $accessoryTypes[0]->describe($level); + if (count($accessoryTypes) > 0) { + return implode('&', array_map(static function (Type $type) use ($level): string { + return $type->describe($level); + }, $accessoryTypes)); } return implode('&', $typeNames); @@ -333,6 +338,13 @@ public function isNonEmptyString(): TrinaryLogic }); } + public function isLiteralString(): TrinaryLogic + { + return $this->intersectResults(static function (Type $type): TrinaryLogic { + return $type->isLiteralString(); + }); + } + public function isOffsetAccessible(): TrinaryLogic { return $this->intersectResults(static function (Type $type): TrinaryLogic { diff --git a/src/Type/IterableType.php b/src/Type/IterableType.php index 445212b9bf..b69dfb2f89 100644 --- a/src/Type/IterableType.php +++ b/src/Type/IterableType.php @@ -240,6 +240,11 @@ public function isNonEmptyString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isLiteralString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function inferTemplateTypes(Type $receivedType): TemplateTypeMap { if ($receivedType instanceof UnionType || $receivedType instanceof IntersectionType) { diff --git a/src/Type/JustNullableTypeTrait.php b/src/Type/JustNullableTypeTrait.php index 34975d9ca3..1bd0f92005 100644 --- a/src/Type/JustNullableTypeTrait.php +++ b/src/Type/JustNullableTypeTrait.php @@ -67,4 +67,9 @@ public function isNonEmptyString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isLiteralString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + } diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index f8d58304d6..5ae8f3e09f 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -387,6 +387,11 @@ public function isNonEmptyString(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function isLiteralString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + /** * @param mixed[] $properties * @return Type diff --git a/src/Type/NeverType.php b/src/Type/NeverType.php index 8cda153229..049544332d 100644 --- a/src/Type/NeverType.php +++ b/src/Type/NeverType.php @@ -236,6 +236,11 @@ public function isNonEmptyString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isLiteralString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + /** * @param mixed[] $properties * @return Type diff --git a/src/Type/NullType.php b/src/Type/NullType.php index f535ea4db6..b027a1d93f 100644 --- a/src/Type/NullType.php +++ b/src/Type/NullType.php @@ -178,6 +178,11 @@ public function isNonEmptyString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isLiteralString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function getSmallerType(): Type { return new NeverType(); diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 3d23923f28..545048e693 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -734,6 +734,11 @@ public function isNonEmptyString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isLiteralString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + private function isExtraOffsetAccessibleClass(): TrinaryLogic { $classReflection = $this->getClassReflection(); diff --git a/src/Type/Php/ImplodeFunctionReturnTypeExtension.php b/src/Type/Php/ImplodeFunctionReturnTypeExtension.php index 7423e287a2..3b8711984d 100644 --- a/src/Type/Php/ImplodeFunctionReturnTypeExtension.php +++ b/src/Type/Php/ImplodeFunctionReturnTypeExtension.php @@ -5,6 +5,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; use PHPStan\Reflection\FunctionReflection; +use PHPStan\Type\Accessory\AccessoryLiteralStringType; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\IntersectionType; @@ -31,11 +32,16 @@ public function getTypeFromFunctionCall( if (count($args) === 1) { $argType = $scope->getType($args[0]->value); if ($argType->isArray()->yes()) { + $accessoryTypes = []; if ($argType->isIterableAtLeastOnce()->yes() && $argType->getIterableValueType()->isNonEmptyString()->yes()) { - return new IntersectionType([ - new StringType(), - new AccessoryNonEmptyStringType(), - ]); + $accessoryTypes[] = new AccessoryNonEmptyStringType(); + } + if ($argType->getIterableValueType()->isLiteralString()->yes()) { + $accessoryTypes[] = new AccessoryLiteralStringType(); + } + + if (count($accessoryTypes) > 0) { + return new IntersectionType([new StringType(), ...$accessoryTypes]); } return new StringType(); @@ -48,21 +54,21 @@ public function getTypeFromFunctionCall( $separatorType = $scope->getType($args[0]->value); $arrayType = $scope->getType($args[1]->value); + $accessoryTypes = []; if ($arrayType->isIterableAtLeastOnce()->yes()) { - if ($arrayType->getIterableValueType()->isNonEmptyString()->yes()) { - return new IntersectionType([ - new StringType(), - new AccessoryNonEmptyStringType(), - ]); - } - if ($separatorType->isNonEmptyString()->yes()) { - return new IntersectionType([ - new StringType(), - new AccessoryNonEmptyStringType(), - ]); + if ($arrayType->getIterableValueType()->isNonEmptyString()->yes() || $separatorType->isNonEmptyString()->yes()) { + $accessoryTypes[] = new AccessoryNonEmptyStringType(); } } + if ($arrayType->getIterableValueType()->isLiteralString()->yes() && $separatorType->isLiteralString()->yes()) { + $accessoryTypes[] = new AccessoryLiteralStringType(); + } + + if (count($accessoryTypes) > 0) { + return new IntersectionType([new StringType(), ...$accessoryTypes]); + } + return new StringType(); } diff --git a/src/Type/Php/StrPadFunctionReturnTypeExtension.php b/src/Type/Php/StrPadFunctionReturnTypeExtension.php index 37323de1ca..0c01d2ee6f 100644 --- a/src/Type/Php/StrPadFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrPadFunctionReturnTypeExtension.php @@ -5,6 +5,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; use PHPStan\Reflection\FunctionReflection; +use PHPStan\Type\Accessory\AccessoryLiteralStringType; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\IntegerRangeType; @@ -33,18 +34,24 @@ public function getTypeFromFunctionCall( $inputType = $scope->getType($args[0]->value); $lengthType = $scope->getType($args[1]->value); - if ($inputType->isNonEmptyString()->yes()) { - return new IntersectionType([ - new StringType(), - new AccessoryNonEmptyStringType(), - ]); + $accessoryTypes = []; + if ($inputType->isNonEmptyString()->yes() || IntegerRangeType::fromInterval(1, null)->isSuperTypeOf($lengthType)->yes()) { + $accessoryTypes[] = new AccessoryNonEmptyStringType(); } - if (IntegerRangeType::fromInterval(1, null)->isSuperTypeOf($lengthType)->yes()) { - return new IntersectionType([ - new StringType(), - new AccessoryNonEmptyStringType(), - ]); + if ($inputType->isLiteralString()->yes()) { + if (count($args) < 3) { + $accessoryTypes[] = new AccessoryLiteralStringType(); + } else { + $padStringType = $scope->getType($args[2]->value); + if ($padStringType->isLiteralString()->yes()) { + $accessoryTypes[] = new AccessoryLiteralStringType(); + } + } + } + + if (count($accessoryTypes) > 0) { + return new IntersectionType([new StringType(), ...$accessoryTypes]); } return new StringType(); diff --git a/src/Type/Php/StrRepeatFunctionReturnTypeExtension.php b/src/Type/Php/StrRepeatFunctionReturnTypeExtension.php index 33bf751c0b..68488c4b50 100644 --- a/src/Type/Php/StrRepeatFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrRepeatFunctionReturnTypeExtension.php @@ -5,6 +5,7 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; use PHPStan\Reflection\FunctionReflection; +use PHPStan\Type\Accessory\AccessoryLiteralStringType; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; @@ -39,15 +40,21 @@ public function getTypeFromFunctionCall( return new ConstantStringType(''); } + $accessoryTypes = []; if ($inputType->isNonEmptyString()->yes()) { if (IntegerRangeType::fromInterval(1, null)->isSuperTypeOf($multiplierType)->yes()) { - return new IntersectionType([ - new StringType(), - new AccessoryNonEmptyStringType(), - ]); + $accessoryTypes[] = new AccessoryNonEmptyStringType(); } } + if ($inputType->isLiteralString()->yes()) { + $accessoryTypes[] = new AccessoryLiteralStringType(); + } + + if (count($accessoryTypes) > 0) { + return new IntersectionType([new StringType(), ...$accessoryTypes]); + } + return new StringType(); } diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php index d40296efbd..6d628a36bf 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -349,6 +349,11 @@ public function isNonEmptyString(): TrinaryLogic return $this->getStaticObjectType()->isNonEmptyString(); } + public function isLiteralString(): TrinaryLogic + { + return $this->getStaticObjectType()->isLiteralString(); + } + /** * @param \PHPStan\Reflection\ClassMemberAccessAnswerer $scope * @return \PHPStan\Reflection\ParametersAcceptor[] diff --git a/src/Type/StrictMixedType.php b/src/Type/StrictMixedType.php index d8e90b3464..ab6b328ac1 100644 --- a/src/Type/StrictMixedType.php +++ b/src/Type/StrictMixedType.php @@ -146,6 +146,11 @@ public function isNonEmptyString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isLiteralString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function isOffsetAccessible(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/StringType.php b/src/Type/StringType.php index 14d48f2f63..50240f5da6 100644 --- a/src/Type/StringType.php +++ b/src/Type/StringType.php @@ -136,6 +136,11 @@ public function isNonEmptyString(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function isLiteralString(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + /** * @param mixed[] $properties * @return Type diff --git a/src/Type/Traits/ObjectTypeTrait.php b/src/Type/Traits/ObjectTypeTrait.php index f6e79935d9..0089fe1fe4 100644 --- a/src/Type/Traits/ObjectTypeTrait.php +++ b/src/Type/Traits/ObjectTypeTrait.php @@ -119,6 +119,11 @@ public function isNonEmptyString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isLiteralString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function toNumber(): Type { return new ErrorType(); diff --git a/src/Type/Type.php b/src/Type/Type.php index 201963b4f3..099b75d5ae 100644 --- a/src/Type/Type.php +++ b/src/Type/Type.php @@ -100,6 +100,8 @@ public function isNumericString(): TrinaryLogic; public function isNonEmptyString(): TrinaryLogic; + public function isLiteralString(): TrinaryLogic; + public function getSmallerType(): Type; public function getSmallerOrEqualType(): Type; diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index 51fa6e32c3..f1c2ee2aae 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -417,6 +417,13 @@ public function isNonEmptyString(): TrinaryLogic }); } + public function isLiteralString(): TrinaryLogic + { + return $this->unionResults(static function (Type $type): TrinaryLogic { + return $type->isLiteralString(); + }); + } + public function isOffsetAccessible(): TrinaryLogic { return $this->unionResults(static function (Type $type): TrinaryLogic { diff --git a/src/Type/VerbosityLevel.php b/src/Type/VerbosityLevel.php index 742b6fe7a1..8ae7d0060d 100644 --- a/src/Type/VerbosityLevel.php +++ b/src/Type/VerbosityLevel.php @@ -2,6 +2,7 @@ namespace PHPStan\Type; +use PHPStan\Type\Accessory\AccessoryLiteralStringType; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; use PHPStan\Type\Accessory\NonEmptyArrayType; @@ -68,7 +69,7 @@ public static function getRecommendedLevelByType(Type $acceptingType, ?Type $acc $moreVerbose = true; return $type; } - if ($type instanceof AccessoryNumericStringType || $type instanceof AccessoryNonEmptyStringType) { + if ($type instanceof AccessoryNumericStringType || $type instanceof AccessoryNonEmptyStringType || $type instanceof AccessoryLiteralStringType) { $moreVerbose = true; return $type; } diff --git a/src/Type/VoidType.php b/src/Type/VoidType.php index b14bdbfd65..b2688f6ac5 100644 --- a/src/Type/VoidType.php +++ b/src/Type/VoidType.php @@ -108,6 +108,11 @@ public function isNonEmptyString(): TrinaryLogic return TrinaryLogic::createNo(); } + public function isLiteralString(): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + public function traverse(callable $cb): Type { return $this; diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 68835917f5..fcefb50a10 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -3072,15 +3072,15 @@ public function dataBinaryOperations(): array '$decrementedFooString', ], [ - 'non-empty-string', + 'literal-string&non-empty-string', '$conditionalString . $conditionalString', ], [ - 'non-empty-string', + 'literal-string&non-empty-string', '$conditionalString . $anotherConditionalString', ], [ - 'non-empty-string', + 'literal-string&non-empty-string', '$anotherConditionalString . $conditionalString', ], [ @@ -9274,7 +9274,7 @@ public function dataIsset(): array '$anotherArrayCopy', ], [ - 'array', + 'array', '$yetAnotherArrayCopy', ], [ diff --git a/tests/PHPStan/Analyser/ScopeTest.php b/tests/PHPStan/Analyser/ScopeTest.php index 472563fd54..fd63c699de 100644 --- a/tests/PHPStan/Analyser/ScopeTest.php +++ b/tests/PHPStan/Analyser/ScopeTest.php @@ -28,7 +28,7 @@ public function dataGeneralize(): array [ new ConstantStringType('a'), new ConstantStringType('b'), - 'non-empty-string', + 'literal-string&non-empty-string', ], [ new ConstantIntegerType(0), @@ -138,7 +138,7 @@ public function dataGeneralize(): array new ConstantIntegerType(1), new ConstantIntegerType(1), ]), - 'array', + 'array', ], [ new ConstantArrayType([ @@ -153,7 +153,7 @@ public function dataGeneralize(): array new ConstantIntegerType(1), new ConstantIntegerType(2), ]), - 'array', + 'array', ], ]; } diff --git a/tests/PHPStan/Analyser/data/literal-string.php b/tests/PHPStan/Analyser/data/literal-string.php index d95d0679b6..fc9f38106f 100644 --- a/tests/PHPStan/Analyser/data/literal-string.php +++ b/tests/PHPStan/Analyser/data/literal-string.php @@ -7,10 +7,36 @@ class Foo { - /** @param literal-string $s */ - public function doFoo($s) + /** @param literal-string $literalString */ + public function doFoo($literalString, string $string) { - assertType('string', $s); + assertType('literal-string', $literalString); + assertType('literal-string', $literalString . ''); + assertType('literal-string', '' . $literalString); + assertType('literal-string&non-empty-string', $literalString . 'foo'); + assertType('literal-string&non-empty-string', 'foo' . $literalString); + assertType('string', $string . ''); + assertType('string', '' . $string); + assertType('string', $literalString . $string); + assertType('string', $string . $literalString); + assertType('literal-string', $literalString . $literalString); + + assertType('string', str_repeat($string, 10)); + assertType('literal-string', str_repeat($literalString, 10)); + assertType('literal-string', str_repeat('', 10)); + assertType('literal-string&non-empty-string', str_repeat('foo', 10)); + + assertType('non-empty-string', str_pad($string, 5, $string)); + assertType('non-empty-string', str_pad($literalString, 5, $string)); + assertType('non-empty-string', str_pad($string, 5, $literalString)); + assertType('literal-string&non-empty-string', str_pad($literalString, 5, $literalString)); + assertType('literal-string&non-empty-string', str_pad($literalString, 5)); + + assertType('string', implode([$string])); + assertType('literal-string', implode([$literalString])); + assertType('string', implode($string, [$literalString])); + assertType('literal-string', implode($literalString, [$literalString])); + assertType('string', implode($literalString, [$string])); } } diff --git a/tests/PHPStan/Rules/Api/data/class-implements-out-of-phpstan.php b/tests/PHPStan/Rules/Api/data/class-implements-out-of-phpstan.php index 300bedb9ff..aef4bb0cb6 100644 --- a/tests/PHPStan/Rules/Api/data/class-implements-out-of-phpstan.php +++ b/tests/PHPStan/Rules/Api/data/class-implements-out-of-phpstan.php @@ -239,6 +239,11 @@ public function isNonEmptyString(): \PHPStan\TrinaryLogic // TODO: Implement isNumericString() method. } + public function isLiteralString(): \PHPStan\TrinaryLogic + { + // TODO: Implement isLiteralString() method. + } + public function getSmallerType(): \PHPStan\Type\Type { // TODO: Implement getSmallerType() method. diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 2677247476..5f00f182c1 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -2051,6 +2051,55 @@ public function testBug5372(): void 'Parameter #1 $list of method Bug5372\Foo::takesStrings() expects Bug5372\Collection, Bug5372\Collection given.', 72, ], + /*[ + 'Parameter #1 $list of method Bug5372\Foo::takesStrings() expects Bug5372\Collection, Bug5372\Collection given.', + 85, + ],*/ + ]); + } + + public function testLiteralString(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/literal-string.php'], [ + [ + 'Parameter #1 $s of method LiteralStringMethod\Foo::requireLiteralString() expects literal-string, string given.', + 18, + ], + [ + 'Parameter #1 $s of method LiteralStringMethod\Foo::requireLiteralString() expects literal-string, int given.', + 21, + ], + [ + 'Parameter #1 $s of method LiteralStringMethod\Foo::requireLiteralString() expects literal-string, 1 given.', + 22, + ], + [ + 'Parameter #1 $s of method LiteralStringMethod\Foo::requireLiteralString() expects literal-string, mixed given.', + 25, + ], + [ + 'Parameter #1 $a of method LiteralStringMethod\Foo::requireArrayOfLiteralStrings() expects array, array given.', + 58, + ], + [ + 'Parameter #1 $a of method LiteralStringMethod\Foo::requireArrayOfLiteralStrings() expects array, array given.', + 60, + ], + [ + 'Parameter #1 $s of method LiteralStringMethod\Foo::requireLiteralString() expects literal-string, array given.', + 65, + ], + [ + 'Parameter #1 $s of method LiteralStringMethod\Foo::requireLiteralString() expects literal-string, mixed given.', + 66, + ], + [ + 'Parameter #1 $s of method LiteralStringMethod\Foo::requireLiteralString() expects literal-string, mixed given.', + 67, + ], ]); } diff --git a/tests/PHPStan/Rules/Methods/data/bug-5372.php b/tests/PHPStan/Rules/Methods/data/bug-5372.php index 21edc02fae..5626ef795d 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-5372.php +++ b/tests/PHPStan/Rules/Methods/data/bug-5372.php @@ -72,4 +72,17 @@ public function doFoo(string $classString) $this->takesStrings($newCol); } + /** @param literal-string $literalString */ + public function doBar(string $literalString) + { + $col = new Collection(['foo', 'bar']); + $newCol = $col->map(static fn(string $var): string => $literalString); + assertType('Bug5372\Collection', $newCol); + $this->takesStrings($newCol); + + $newCol = $col->map2(static fn(string $var): string => $literalString); + assertType('Bug5372\Collection', $newCol); // should be literal-string + $this->takesStrings($newCol); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/literal-string.php b/tests/PHPStan/Rules/Methods/data/literal-string.php new file mode 100644 index 0000000000..e919270eb0 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/literal-string.php @@ -0,0 +1,70 @@ +requireLiteralString($string); + $this->requireLiteralString($literalString); + $this->requireLiteralString('foo'); + $this->requireLiteralString($int); + $this->requireLiteralString(1); + + $mixed = doFoo(); + $this->requireLiteralString($mixed); + } + + /** + * @param literal-string $s + */ + public function requireLiteralString(string $s): void + { + + } + + /** + * @param array $a + */ + public function requireArrayOfLiteralStrings(array $a): void + { + + } + + /** + * @param mixed $mixed + * @param array $arrayOfStrings + * @param array $arrayOfLiteralStrings + * @param array $arrayOfMixed + */ + public function doBar( + $mixed, + array $arrayOfStrings, + array $arrayOfLiteralStrings, + array $arrayOfMixed + ): void + { + $this->requireArrayOfLiteralStrings($mixed); + $this->requireArrayOfLiteralStrings($arrayOfStrings); + $this->requireArrayOfLiteralStrings($arrayOfLiteralStrings); + $this->requireArrayOfLiteralStrings($arrayOfMixed); + } + + public function doGet(): void + { + $this->requireLiteralString($_GET); + $this->requireLiteralString($_GET['x']); + $this->requireLiteralString($_GET['x']['y']); + } + +} diff --git a/tests/PHPStan/Type/Constant/ConstantStringTypeTest.php b/tests/PHPStan/Type/Constant/ConstantStringTypeTest.php index d7839946af..3f5360f072 100644 --- a/tests/PHPStan/Type/Constant/ConstantStringTypeTest.php +++ b/tests/PHPStan/Type/Constant/ConstantStringTypeTest.php @@ -143,12 +143,12 @@ public function testIsSuperTypeOf(ConstantStringType $type, Type $otherType, Tri public function testGeneralize(): void { - $this->assertSame('non-empty-string', (new ConstantStringType('NonexistentClass'))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); - $this->assertSame('string', (new ConstantStringType(''))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); - $this->assertSame('non-empty-string', (new ConstantStringType('a'))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); + $this->assertSame('literal-string&non-empty-string', (new ConstantStringType('NonexistentClass'))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); + $this->assertSame('literal-string', (new ConstantStringType(''))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); + $this->assertSame('literal-string&non-empty-string', (new ConstantStringType('a'))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); $this->assertSame('string', (new ConstantStringType(''))->generalize(GeneralizePrecision::lessSpecific())->describe(VerbosityLevel::precise())); $this->assertSame('string', (new ConstantStringType('a'))->generalize(GeneralizePrecision::lessSpecific())->describe(VerbosityLevel::precise())); - $this->assertSame('non-empty-string', (new ConstantStringType(\stdClass::class))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); + $this->assertSame('literal-string&non-empty-string', (new ConstantStringType(\stdClass::class))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); $this->assertSame('class-string', (new ConstantStringType(\stdClass::class, true))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); $this->assertSame('class-string', (new ConstantStringType('NonexistentClass', true))->generalize(GeneralizePrecision::moreSpecific())->describe(VerbosityLevel::precise())); } From e303360b55160ebbb705c3ba403a1570e588fa7c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 27 Aug 2021 13:00:41 +0200 Subject: [PATCH 0183/1284] Fix --- src/Analyser/MutatingScope.php | 3 ++- src/Type/Php/ImplodeFunctionReturnTypeExtension.php | 6 ++++-- src/Type/Php/StrPadFunctionReturnTypeExtension.php | 3 ++- src/Type/Php/StrRepeatFunctionReturnTypeExtension.php | 3 ++- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 2f44a911da..470771b3bb 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -1062,7 +1062,8 @@ private function resolveType(Expr $node): Type } if (count($accessoryTypes) > 0) { - return new IntersectionType([new StringType(), ...$accessoryTypes]); + $accessoryTypes[] = new StringType(); + return new IntersectionType($accessoryTypes); } return new StringType(); diff --git a/src/Type/Php/ImplodeFunctionReturnTypeExtension.php b/src/Type/Php/ImplodeFunctionReturnTypeExtension.php index 3b8711984d..2321c44b68 100644 --- a/src/Type/Php/ImplodeFunctionReturnTypeExtension.php +++ b/src/Type/Php/ImplodeFunctionReturnTypeExtension.php @@ -41,7 +41,8 @@ public function getTypeFromFunctionCall( } if (count($accessoryTypes) > 0) { - return new IntersectionType([new StringType(), ...$accessoryTypes]); + $accessoryTypes[] = new StringType(); + return new IntersectionType($accessoryTypes); } return new StringType(); @@ -66,7 +67,8 @@ public function getTypeFromFunctionCall( } if (count($accessoryTypes) > 0) { - return new IntersectionType([new StringType(), ...$accessoryTypes]); + $accessoryTypes[] = new StringType(); + return new IntersectionType($accessoryTypes); } return new StringType(); diff --git a/src/Type/Php/StrPadFunctionReturnTypeExtension.php b/src/Type/Php/StrPadFunctionReturnTypeExtension.php index 0c01d2ee6f..3aeac0890e 100644 --- a/src/Type/Php/StrPadFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrPadFunctionReturnTypeExtension.php @@ -51,7 +51,8 @@ public function getTypeFromFunctionCall( } if (count($accessoryTypes) > 0) { - return new IntersectionType([new StringType(), ...$accessoryTypes]); + $accessoryTypes[] = new StringType(); + return new IntersectionType($accessoryTypes); } return new StringType(); diff --git a/src/Type/Php/StrRepeatFunctionReturnTypeExtension.php b/src/Type/Php/StrRepeatFunctionReturnTypeExtension.php index 68488c4b50..f0ad8bb0e5 100644 --- a/src/Type/Php/StrRepeatFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrRepeatFunctionReturnTypeExtension.php @@ -52,7 +52,8 @@ public function getTypeFromFunctionCall( } if (count($accessoryTypes) > 0) { - return new IntersectionType([new StringType(), ...$accessoryTypes]); + $accessoryTypes[] = new StringType(); + return new IntersectionType($accessoryTypes); } return new StringType(); From b08e63ad14bdd18921777a65cdc540bea79ef182 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 27 Aug 2021 13:15:52 +0200 Subject: [PATCH 0184/1284] Fix --- src/Type/Accessory/AccessoryLiteralStringType.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Type/Accessory/AccessoryLiteralStringType.php b/src/Type/Accessory/AccessoryLiteralStringType.php index 3cb8869e7f..0208a69b33 100644 --- a/src/Type/Accessory/AccessoryLiteralStringType.php +++ b/src/Type/Accessory/AccessoryLiteralStringType.php @@ -3,6 +3,7 @@ namespace PHPStan\Type\Accessory; use PHPStan\TrinaryLogic; +use PHPStan\Type\BooleanType; use PHPStan\Type\CompoundType; use PHPStan\Type\CompoundTypeHelper; use PHPStan\Type\Constant\ConstantArrayType; @@ -17,7 +18,6 @@ use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\NonIterableTypeTrait; use PHPStan\Type\Traits\NonObjectTypeTrait; -use PHPStan\Type\Traits\TruthyBooleanTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; use PHPStan\Type\Type; use PHPStan\Type\UnionType; @@ -28,7 +28,6 @@ class AccessoryLiteralStringType implements CompoundType, AccessoryType use MaybeCallableTypeTrait; use NonObjectTypeTrait; use NonIterableTypeTrait; - use TruthyBooleanTypeTrait; use UndecidedComparisonCompoundTypeTrait; use NonGenericTypeTrait; @@ -144,6 +143,11 @@ public function toString(): Type return $this; } + public function toBoolean(): BooleanType + { + return new BooleanType(); + } + public function toArray(): Type { return new ConstantArrayType( From 2bef413d82166f7818fafc14003b4b6ffc3185a1 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 29 Aug 2021 12:02:44 +0200 Subject: [PATCH 0185/1284] support non-empty-array type in `[] != $arr` conditions --- src/Analyser/TypeSpecifier.php | 17 +++++++++++++++++ tests/PHPStan/Analyser/data/sizeof.php | 16 ++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 1550791cd3..014746073d 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -25,6 +25,7 @@ use PHPStan\Type\Accessory\HasPropertyType; use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\BooleanType; +use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; @@ -374,6 +375,22 @@ public function specifyTypesInCondition( ); } + if ( + $context->falsey() + && $rightType->isArray()->yes() + && $leftType instanceof ConstantArrayType && $leftType->isEmpty() + ) { + return $this->create($expr->right, new NonEmptyArrayType(), $context->negate(), false, $scope); + } + + if ( + $context->falsey() + && $leftType->isArray()->yes() + && $rightType instanceof ConstantArrayType && $rightType->isEmpty() + ) { + return $this->create($expr->left, new NonEmptyArrayType(), $context->negate(), false, $scope); + } + if ( $expr->left instanceof FuncCall && $expr->left->name instanceof Name diff --git a/tests/PHPStan/Analyser/data/sizeof.php b/tests/PHPStan/Analyser/data/sizeof.php index b9333fe19c..f569d8902a 100644 --- a/tests/PHPStan/Analyser/data/sizeof.php +++ b/tests/PHPStan/Analyser/data/sizeof.php @@ -44,4 +44,20 @@ function doFoo4(array $arr): string } return ""; } + + function doFoo5(array $arr): void + { + if ([] != $arr) { + assertType('array&nonEmpty', $arr); + } + assertType('array', $arr); + } + + function doFoo6(array $arr): void + { + if ($arr != []) { + assertType('array&nonEmpty', $arr); + } + assertType('array', $arr); + } } From 9cf20f3a14584b20852605af6676cbf21c9465d9 Mon Sep 17 00:00:00 2001 From: Brandon Olivares Date: Sun, 29 Aug 2021 11:38:36 -0400 Subject: [PATCH 0186/1284] filter_var() should return non empty string only when it will not be sanitized --- .../FilterVarDynamicReturnTypeExtension.php | 28 ++++++++++++- .../filter-var-returns-non-empty-string.php | 42 ++++++++++++++++++- 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/src/Type/Php/FilterVarDynamicReturnTypeExtension.php b/src/Type/Php/FilterVarDynamicReturnTypeExtension.php index 8c6417aa6a..0de45a325f 100644 --- a/src/Type/Php/FilterVarDynamicReturnTypeExtension.php +++ b/src/Type/Php/FilterVarDynamicReturnTypeExtension.php @@ -28,6 +28,11 @@ class FilterVarDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { + /** + * All validation filters match 0x100. + */ + private const VALIDATION_FILTER_BITMASK = 0x100; + private ReflectionProvider $reflectionProvider; private ConstantStringType $flagsString; @@ -66,6 +71,7 @@ private function getFilterTypeMap(): array $this->getConstant('FILTER_SANITIZE_STRING') => $stringType, $this->getConstant('FILTER_SANITIZE_URL') => $stringType, $this->getConstant('FILTER_VALIDATE_BOOLEAN') => $booleanType, + $this->getConstant('FILTER_VALIDATE_DOMAIN') => $stringType, $this->getConstant('FILTER_VALIDATE_EMAIL') => $stringType, $this->getConstant('FILTER_VALIDATE_FLOAT') => $floatType, $this->getConstant('FILTER_VALIDATE_INT') => $intType, @@ -130,7 +136,9 @@ public function getTypeFromFunctionCall( $type = $this->getFilterTypeMap()[$filterValue] ?? $mixedType; $otherType = $this->getOtherType($flagsArg, $scope); - if ($inputType->isNonEmptyString()->yes()) { + if ($inputType->isNonEmptyString()->yes() + && $type instanceof StringType + && !$this->canStringBeSanitized($filterValue, $flagsArg, $scope)) { $type = new IntersectionType([$type, new AccessoryNonEmptyStringType()]); } @@ -222,4 +230,22 @@ private function getFlagsValue(Type $exprType): Type return $exprType->getOffsetValueType($this->flagsString); } + private function canStringBeSanitized(int $filterValue, ?Node\Arg $flagsArg, Scope $scope): bool + { + // If it is a validation filter, the string will not be changed + if (($filterValue & self::VALIDATION_FILTER_BITMASK) !== 0) { + return false; + } + + // FILTER_DEFAULT will not sanitize, unless it has FILTER_FLAG_STRIP_LOW, + // FILTER_FLAG_STRIP_HIGH, or FILTER_FLAG_STRIP_BACKTICK + if ($filterValue === $this->getConstant('FILTER_DEFAULT')) { + return $this->hasFlag($this->getConstant('FILTER_FLAG_STRIP_LOW'), $flagsArg, $scope) + || $this->hasFlag($this->getConstant('FILTER_FLAG_STRIP_HIGH'), $flagsArg, $scope) + || $this->hasFlag($this->getConstant('FILTER_FLAG_STRIP_BACKTICK'), $flagsArg, $scope); + } + + return true; + } + } diff --git a/tests/PHPStan/Analyser/data/filter-var-returns-non-empty-string.php b/tests/PHPStan/Analyser/data/filter-var-returns-non-empty-string.php index bd26c5b09b..bcdf815694 100644 --- a/tests/PHPStan/Analyser/data/filter-var-returns-non-empty-string.php +++ b/tests/PHPStan/Analyser/data/filter-var-returns-non-empty-string.php @@ -14,11 +14,51 @@ public function run(string $str): void $return = filter_var($str, FILTER_DEFAULT); assertType('non-empty-string|false', $return); - $return = filter_var($str, FILTER_SANITIZE_STRING); + $return = filter_var($str, FILTER_DEFAULT, FILTER_FLAG_STRIP_LOW); + assertType('string|false', $return); + + $return = filter_var($str, FILTER_DEFAULT, FILTER_FLAG_STRIP_HIGH); + assertType('string|false', $return); + + $return = filter_var($str, FILTER_DEFAULT, FILTER_FLAG_STRIP_BACKTICK); + assertType('string|false', $return); + + $return = filter_var($str, FILTER_VALIDATE_EMAIL); assertType('non-empty-string|false', $return); + $return = filter_var($str, FILTER_VALIDATE_REGEXP); + assertType('non-empty-string|false', $return); + + $return = filter_var($str, FILTER_VALIDATE_URL); + assertType('non-empty-string|false', $return); + + $return = filter_var($str, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE); + assertType('non-empty-string|null', $return); + + $return = filter_var($str, FILTER_VALIDATE_IP); + assertType('non-empty-string|false', $return); + + $return = filter_var($str, FILTER_VALIDATE_MAC); + assertType('non-empty-string|false', $return); + + $return = filter_var($str, FILTER_VALIDATE_DOMAIN); + assertType('non-empty-string|false', $return); + + $return = filter_var($str, FILTER_SANITIZE_STRING); + assertType('string|false', $return); + + $return = filter_var($str, FILTER_VALIDATE_INT); + assertType('int|false', $return); + $str2 = ''; $return = filter_var($str2, FILTER_DEFAULT); assertType('string|false', $return); + + $return = filter_var($str2, FILTER_VALIDATE_URL); + assertType('string|false', $return); + + $str2 = 'foo'; + $return = filter_var($str2, FILTER_VALIDATE_INT); + assertType('int|false', $return); } } From 9660b8d74ec526d40b4c6ec4cc5aac5b2a30d7ae Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 29 Aug 2021 18:07:35 +0200 Subject: [PATCH 0187/1284] PostInc of literal-string leads to literal-string --- src/Analyser/MutatingScope.php | 3 +++ tests/PHPStan/Analyser/data/literal-string.php | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 470771b3bb..c418348513 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -1846,6 +1846,9 @@ private function resolveType(Expr $node): Type $stringType = new StringType(); if ($stringType->isSuperTypeOf($varType)->yes()) { + if ($varType->isLiteralString()->yes()) { + return new IntersectionType([$stringType, new AccessoryLiteralStringType()]); + } return $stringType; } diff --git a/tests/PHPStan/Analyser/data/literal-string.php b/tests/PHPStan/Analyser/data/literal-string.php index fc9f38106f..aae44ef3f6 100644 --- a/tests/PHPStan/Analyser/data/literal-string.php +++ b/tests/PHPStan/Analyser/data/literal-string.php @@ -39,4 +39,14 @@ public function doFoo($literalString, string $string) assertType('string', implode($literalString, [$string])); } + /** @param literal-string $literalString */ + public function increment($literalString, string $string) + { + $literalString++; + assertType('literal-string', $literalString); + + $string++; + assertType('string', $string); + } + } From d28f78575de4e7fbd071c4a64a36a75c7ac9f166 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 30 Aug 2021 09:58:14 +0200 Subject: [PATCH 0188/1284] Revert Add DynamicFunctionReturnTypeExtension for array_replace This reverts commit 71144651be31ea6b14942b8d5b0a3b1be090dd22. --- conf/config.neon | 5 - ...laceFunctionDynamicReturnTypeExtension.php | 99 ------------------- .../Analyser/LegacyNodeScopeResolverTest.php | 4 - .../Analyser/NodeScopeResolverTest.php | 2 - tests/PHPStan/Analyser/data/array-replace.php | 58 ----------- .../PHPStan/Analyser/data/non-empty-array.php | 5 - 6 files changed, 173 deletions(-) delete mode 100644 src/Type/Php/ArrayReplaceFunctionDynamicReturnTypeExtension.php delete mode 100644 tests/PHPStan/Analyser/data/array-replace.php diff --git a/conf/config.neon b/conf/config.neon index cb04f7b0bf..3c183f59bf 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -973,11 +973,6 @@ services: tags: - phpstan.broker.dynamicFunctionReturnTypeExtension - - - class: PHPStan\Type\Php\ArrayReplaceFunctionDynamicReturnTypeExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - class: PHPStan\Type\Php\ArrayKeysFunctionDynamicReturnTypeExtension tags: diff --git a/src/Type/Php/ArrayReplaceFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ArrayReplaceFunctionDynamicReturnTypeExtension.php deleted file mode 100644 index 756d7a477e..0000000000 --- a/src/Type/Php/ArrayReplaceFunctionDynamicReturnTypeExtension.php +++ /dev/null @@ -1,99 +0,0 @@ -getName() === 'array_replace'; - } - - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - if (!isset($functionCall->args[0])) { - return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - } - - $keyTypes = []; - $valueTypes = []; - $returnedArrayBuilder = ConstantArrayTypeBuilder::createEmpty(); - $returnedArrayBuilderFilled = false; - $nonEmpty = false; - $offsetCount = 0; - foreach ($functionCall->args as $arg) { - $argType = $scope->getType($arg->value); - - if ($arg->unpack) { - $argType = $argType->getIterableValueType(); - } - - $arrays = TypeUtils::getConstantArrays($argType); - if (count($arrays) > 0) { - foreach ($arrays as $constantArray) { - foreach ($constantArray->getKeyTypes() as $i => $keyType) { - $returnedArrayBuilderFilled = true; - $offsetCount++; - - if ($offsetCount > ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT) { - $returnedArrayBuilder->degradeToGeneralArray(); - } - - $returnedArrayBuilder->setOffsetValueType( - $keyType, - $constantArray->getValueTypes()[$i] - ); - } - } - - } else { - $keyTypes[] = TypeUtils::generalizeType($argType->getIterableKeyType(), GeneralizePrecision::moreSpecific()); - $valueTypes[] = $argType->getIterableValueType(); - } - - if (!$argType->isIterableAtLeastOnce()->yes()) { - continue; - } - - $nonEmpty = true; - } - - if (count($keyTypes) > 0) { - $arrayType = new ArrayType( - TypeCombinator::union(...$keyTypes), - TypeCombinator::union(...$valueTypes) - ); - - if ($returnedArrayBuilderFilled) { - $arrayType = TypeCombinator::union($returnedArrayBuilder->getArray(), $arrayType); - } - } elseif ($returnedArrayBuilderFilled) { - $arrayType = $returnedArrayBuilder->getArray(); - } else { - $arrayType = new ArrayType( - TypeCombinator::union(...$keyTypes), - TypeCombinator::union(...$valueTypes) - ); - } - - if ($nonEmpty) { - $arrayType = TypeCombinator::intersect($arrayType, new NonEmptyArrayType()); - } - - return $arrayType; - } - -} diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index fcefb50a10..c3ea92ffc7 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -5152,10 +5152,6 @@ public function dataArrayFunctions(): array 'array', 'array_values($generalStringKeys)', ], - [ - "array('foo' => 'foo', 1 => stdClass, 'bar' => stdClass)", - 'array_replace($stringOrIntegerKeys, $stringKeys)', - ], [ "array('foo' => stdClass, 0 => stdClass)", 'array_merge($stringOrIntegerKeys)', diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index ede143a319..2344d7653a 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -136,8 +136,6 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/array-merge.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/array-replace.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/non-empty-array.php'); if (PHP_VERSION_ID >= 80000 || self::$useStaticReflectionProvider) { diff --git a/tests/PHPStan/Analyser/data/array-replace.php b/tests/PHPStan/Analyser/data/array-replace.php deleted file mode 100644 index b3ae417e45..0000000000 --- a/tests/PHPStan/Analyser/data/array-replace.php +++ /dev/null @@ -1,58 +0,0 @@ - '1', 'bar' => '2', 'lall' => '3', 2 => '2', 3 => '3')", array_replace($array1)); - assertType("array('foo' => '1', 'bar' => '2', 'lall' => '3', 2 => '2', 3 => '3')", array_replace([], $array1)); - assertType("array('foo' => '1', 'bar' => '2', 'lall' => '3', 2 => '2', 3 => '3')", array_replace($array1, [])); - assertType("array('foo' => '1', 'bar' => '2', 'lall' => '3', 2 => '2', 3 => '3')", array_replace($array1, $array1)); - assertType("array('foo' => '1', 'bar' => '4', 'lall' => '3', 2 => '4', 3 => '6', 'lall2' => '3')", array_replace($array1, $array2)); - assertType("array('foo' => '1', 'bar' => '2', 'lall2' => '3', 2 => '2', 3 => '3', 'lall' => '3')", array_replace($array2, $array1)); - assertType("array('foo' => 3, 'bar' => '2', 'lall2' => '3', 2 => '2', 3 => '3', 'lall' => '3')", array_replace($array2, $array1, ['foo' => 3])); - assertType("array('foo' => 3, 'bar' => '2', 'lall2' => '3', 2 => '2', 3 => '3', 'lall' => '3')", array_replace($array2, $array1, ...[['foo' => 3]])); - } - - /** - * @param int[] $array1 - * @param string[] $array2 - */ - public function arrayReplaceSimple($array1, $array2): void - { - assertType("array", array_merge($array1, $array1)); - assertType("array", array_merge($array1, $array2)); - assertType("array", array_merge($array2, $array1)); - } - - /** - * @param array $array1 - * @param array $array2 - */ - public function arrayReplaceUnionType($array1, $array2): void - { - assertType("array", array_merge($array1, $array1)); - assertType("array", array_merge($array1, $array2)); - assertType("array", array_merge($array2, $array1)); - } - - /** - * @param array $array1 - * @param array $array2 - */ - public function arrayReplaceUnionTypeArrayShapes($array1, $array2): void - { - assertType("array '2')|array('foo' => '1')>", array_merge($array1, $array1)); - assertType("array '2'|'3')|array('foo' => '1'|'2')>", array_merge($array1, $array2)); - assertType("array '2'|'3')|array('foo' => '1'|'2')>", array_merge($array2, $array1)); - } -} diff --git a/tests/PHPStan/Analyser/data/non-empty-array.php b/tests/PHPStan/Analyser/data/non-empty-array.php index 5f3973714d..316620e1af 100644 --- a/tests/PHPStan/Analyser/data/non-empty-array.php +++ b/tests/PHPStan/Analyser/data/non-empty-array.php @@ -49,11 +49,6 @@ public function arrayFunctions($array, $list, $stringArray): void assertType('array&nonEmpty', array_merge($array, [])); assertType('array&nonEmpty', array_merge($array, $array)); - assertType('array&nonEmpty', array_replace($array)); - assertType('array&nonEmpty', array_replace([], $array)); - assertType('array&nonEmpty', array_replace($array, [])); - assertType('array&nonEmpty', array_replace($array, $array)); - assertType('array&nonEmpty', array_flip($array)); assertType('array&nonEmpty', array_flip($stringArray)); } From 53817aad4e13d06861381247ce3b8a1d79dcc167 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 30 Aug 2021 09:58:33 +0200 Subject: [PATCH 0189/1284] Revert support array-shapes in array_merge This reverts commit f21b2186437a434a1df3d791e0e6ef0657cabf25. --- ...ergeFunctionDynamicReturnTypeExtension.php | 53 +++---------- .../Analyser/LegacyNodeScopeResolverTest.php | 25 ++----- .../Analyser/NodeScopeResolverTest.php | 2 - tests/PHPStan/Analyser/data/array-merge.php | 75 ------------------- 4 files changed, 17 insertions(+), 138 deletions(-) delete mode 100644 tests/PHPStan/Analyser/data/array-merge.php diff --git a/src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php index 78b3524a63..ef48894937 100644 --- a/src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php @@ -8,11 +8,11 @@ use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\ArrayType; -use PHPStan\Type\Constant\ConstantArrayTypeBuilder; use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use PHPStan\Type\TypeUtils; +use PHPStan\Type\UnionType; class ArrayMergeFunctionDynamicReturnTypeExtension implements \PHPStan\Type\DynamicFunctionReturnTypeExtension { @@ -30,39 +30,21 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $keyTypes = []; $valueTypes = []; - $returnedArrayBuilder = ConstantArrayTypeBuilder::createEmpty(); - $returnedArrayBuilderFilled = false; $nonEmpty = false; - $offsetCount = 0; foreach ($functionCall->args as $arg) { $argType = $scope->getType($arg->value); - if ($arg->unpack) { $argType = $argType->getIterableValueType(); - } - - $arrays = TypeUtils::getConstantArrays($argType); - if (count($arrays) > 0) { - foreach ($arrays as $constantArray) { - foreach ($constantArray->getKeyTypes() as $i => $keyType) { - $returnedArrayBuilderFilled = true; - $offsetCount++; - if ($offsetCount > ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT) { - $returnedArrayBuilder->degradeToGeneralArray(); - } - - $returnedArrayBuilder->setOffsetValueType( - is_numeric($keyType->getValue()) ? null : $keyType, - $constantArray->getValueTypes()[$i] - ); + if ($argType instanceof UnionType) { + foreach ($argType->getTypes() as $innerType) { + $argType = $innerType; } } - - } else { - $keyTypes[] = TypeUtils::generalizeType($argType->getIterableKeyType(), GeneralizePrecision::moreSpecific()); - $valueTypes[] = $argType->getIterableValueType(); } + $keyTypes[] = TypeUtils::generalizeType($argType->getIterableKeyType(), GeneralizePrecision::moreSpecific()); + $valueTypes[] = $argType->getIterableValueType(); + if (!$argType->isIterableAtLeastOnce()->yes()) { continue; } @@ -70,23 +52,10 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $nonEmpty = true; } - if (count($keyTypes) > 0) { - $arrayType = new ArrayType( - TypeCombinator::union(...$keyTypes), - TypeCombinator::union(...$valueTypes) - ); - - if ($returnedArrayBuilderFilled) { - $arrayType = TypeCombinator::union($returnedArrayBuilder->getArray(), $arrayType); - } - } elseif ($returnedArrayBuilderFilled) { - $arrayType = $returnedArrayBuilder->getArray(); - } else { - $arrayType = new ArrayType( - TypeCombinator::union(...$keyTypes), - TypeCombinator::union(...$valueTypes) - ); - } + $arrayType = new ArrayType( + TypeCombinator::union(...$keyTypes), + TypeCombinator::union(...$valueTypes) + ); if ($nonEmpty) { $arrayType = TypeCombinator::intersect($arrayType, new NonEmptyArrayType()); diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index c3ea92ffc7..8f9a2a6146 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -5153,7 +5153,7 @@ public function dataArrayFunctions(): array 'array_values($generalStringKeys)', ], [ - "array('foo' => stdClass, 0 => stdClass)", + 'array&nonEmpty', 'array_merge($stringOrIntegerKeys)', ], [ @@ -5161,36 +5161,23 @@ public function dataArrayFunctions(): array 'array_merge($generalStringKeys, $generalDateTimeValues)', ], [ - 'array<0|string, int|stdClass>&nonEmpty', + 'array&nonEmpty', 'array_merge($generalStringKeys, $stringOrIntegerKeys)', ], [ - 'array<0|string, int|stdClass>&nonEmpty', + 'array&nonEmpty', 'array_merge($stringOrIntegerKeys, $generalStringKeys)', ], [ - "array('foo' => stdClass, 'bar' => stdClass, 0 => stdClass)", + 'array&nonEmpty', 'array_merge($stringKeys, $stringOrIntegerKeys)', ], [ - "array('foo' => 1, 'bar' => 2, 0 => 2, 1 => 3)", - "array_merge(['foo' => 4, 'bar' => 5], ...[['foo' => 1, 'bar' => 2], [2, 3]])", - ], - [ - "array('foo' => 1, 'foo2' => stdClass)", - 'array_merge([\'foo\' => new stdClass()], ...[[\'foo2\' => new stdClass()], [\'foo\' => 1]])', - ], - - [ - "array('foo' => 1, 'foo2' => stdClass)", - 'array_merge([\'foo\' => new stdClass()], ...[[\'foo2\' => new stdClass()], [\'foo\' => 1]])', - ], - [ - "array('foo' => 'foo', 0 => stdClass, 'bar' => stdClass)", + 'array&nonEmpty', 'array_merge($stringOrIntegerKeys, $stringKeys)', ], [ - "array('color' => 'green', 0 => 2, 1 => 4, 2 => 'a', 3 => 'b', 'shape' => 'trapezoid', 4 => 4)", + 'array&nonEmpty', 'array_merge(array("color" => "red", 2, 4), array("a", "b", "color" => "green", "shape" => "trapezoid", 4))', ], [ diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 2344d7653a..1059926359 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -134,8 +134,6 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/throw-expr.php'); } - yield from $this->gatherAssertTypes(__DIR__ . '/data/array-merge.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/non-empty-array.php'); if (PHP_VERSION_ID >= 80000 || self::$useStaticReflectionProvider) { diff --git a/tests/PHPStan/Analyser/data/array-merge.php b/tests/PHPStan/Analyser/data/array-merge.php deleted file mode 100644 index 960db00a48..0000000000 --- a/tests/PHPStan/Analyser/data/array-merge.php +++ /dev/null @@ -1,75 +0,0 @@ - 'first', - 'limit' => PHP_INT_MAX, - ]; - - /** - * @param array $settings - */ - public function arrayMergeWithConst(array $settings): void - { - $settings = array_merge(self::DEFAULT_SETTINGS, $settings); - - assertType("array&nonEmpty", $settings); - } - - /** - * @param array{foo: '1', bar: '2', lall: '3', 2: '2', 3: '3'} $array1 - * @param array{foo: '1', bar: '4', lall2: '3', 2: '4', 3: '6'} $array2 - */ - public function arrayMergeArrayShapes($array1, $array2): void - { - assertType("array('foo' => '1', 'bar' => '2', 'lall' => '3', 0 => '2', 1 => '3')", array_merge($array1)); - assertType("array('foo' => '1', 'bar' => '2', 'lall' => '3', 0 => '2', 1 => '3')", array_merge([], $array1)); - assertType("array('foo' => '1', 'bar' => '2', 'lall' => '3', 0 => '2', 1 => '3')", array_merge($array1, [])); - assertType("array('foo' => '1', 'bar' => '2', 'lall' => '3', 0 => '2', 1 => '3', 2 => '2', 3 => '3')", array_merge($array1, $array1)); - assertType("array('foo' => '1', 'bar' => '4', 'lall' => '3', 0 => '2', 1 => '3', 'lall2' => '3', 2 => '4', 3 => '6')", array_merge($array1, $array2)); - assertType("array('foo' => '1', 'bar' => '2', 'lall2' => '3', 0 => '4', 1 => '6', 'lall' => '3', 2 => '2', 3 => '3')", array_merge($array2, $array1)); - assertType("array('foo' => 3, 'bar' => '2', 'lall2' => '3', 0 => '4', 1 => '6', 'lall' => '3', 2 => '2', 3 => '3')", array_merge($array2, $array1, ['foo' => 3])); - assertType("array('foo' => 3, 'bar' => '2', 'lall2' => '3', 0 => '4', 1 => '6', 'lall' => '3', 2 => '2', 3 => '3')", array_merge($array2, $array1, ...[['foo' => 3]])); - } - - /** - * @param int[] $array1 - * @param string[] $array2 - */ - public function arrayMergeSimple($array1, $array2): void - { - assertType("array", array_merge($array1, $array1)); - assertType("array", array_merge($array1, $array2)); - assertType("array", array_merge($array2, $array1)); - - assertType("array('foo' => '')", array_merge(['foo' => ''])); // issue #2567 - } - - /** - * @param array $array1 - * @param array $array2 - */ - public function arrayMergeUnionType($array1, $array2): void - { - assertType("array", array_merge($array1, $array1)); - assertType("array", array_merge($array1, $array2)); - assertType("array", array_merge($array2, $array1)); - } - - /** - * @param array $array1 - * @param array $array2 - */ - public function arrayMergeUnionTypeArrayShapes($array1, $array2): void - { - assertType("array '2')|array('foo' => '1')>", array_merge($array1, $array1)); - assertType("array '2'|'3')|array('foo' => '1'|'2')>", array_merge($array1, $array2)); - assertType("array '2'|'3')|array('foo' => '1'|'2')>", array_merge($array2, $array1)); - } -} From 4ec86b7ca2ff67658bc2c3115fef581c41cce3cc Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 30 Aug 2021 10:24:14 +0200 Subject: [PATCH 0190/1284] Fix tests --- tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 8f9a2a6146..294f140e85 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -5153,7 +5153,7 @@ public function dataArrayFunctions(): array 'array_values($generalStringKeys)', ], [ - 'array&nonEmpty', + 'array&nonEmpty', 'array_merge($stringOrIntegerKeys)', ], [ @@ -5169,15 +5169,15 @@ public function dataArrayFunctions(): array 'array_merge($stringOrIntegerKeys, $generalStringKeys)', ], [ - 'array&nonEmpty', + 'array&nonEmpty', 'array_merge($stringKeys, $stringOrIntegerKeys)', ], [ - 'array&nonEmpty', + 'array&nonEmpty', 'array_merge($stringOrIntegerKeys, $stringKeys)', ], [ - 'array&nonEmpty', + 'array&nonEmpty', 'array_merge(array("color" => "red", 2, 4), array("a", "b", "color" => "green", "shape" => "trapezoid", 4))', ], [ From ea736ccc6bb1e830194bd32e670137cffee81da1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 30 Aug 2021 16:13:50 +0200 Subject: [PATCH 0191/1284] Fix div by zero errors --- src/Analyser/MutatingScope.php | 4 ++-- .../Analyser/NodeScopeResolverTest.php | 2 ++ tests/PHPStan/Analyser/data/div-by-zero.php | 21 +++++++++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/div-by-zero.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index c418348513..854fab17f5 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -1335,8 +1335,8 @@ private function resolveType(Expr $node): Type $min = $leftMin !== null && $rightMin !== null ? $leftMin * $rightMin : null; $max = $leftMax !== null && $rightMax !== null ? $leftMax * $rightMax : null; } else { - $min = $leftMin !== null && $rightMin !== null ? (int) ($leftMin / $rightMin) : null; - $max = $leftMax !== null && $rightMax !== null ? (int) ($leftMax / $rightMax) : null; + $min = $leftMin !== null && $rightMin !== null && $rightMin !== 0 ? (int) ($leftMin / $rightMin) : null; + $max = $leftMax !== null && $rightMax !== null && $rightMax !== 0 ? (int) ($leftMax / $rightMax) : null; if ($min !== null && $max !== null && $min > $max) { [$min, $max] = [$max, $min]; diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 1059926359..eb1c538747 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -485,6 +485,8 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5529.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/sizeof.php'); + + yield from $this->gatherAssertTypes(__DIR__ . '/data/div-by-zero.php'); } /** diff --git a/tests/PHPStan/Analyser/data/div-by-zero.php b/tests/PHPStan/Analyser/data/div-by-zero.php new file mode 100644 index 0000000000..75635d30dc --- /dev/null +++ b/tests/PHPStan/Analyser/data/div-by-zero.php @@ -0,0 +1,21 @@ + $range1 + * @param int $range2 + */ + public function doFoo(int $range1, int $range2): void + { + assertType('(float|int)', 5 / $range1); + assertType('(float|int)', 5 / $range2); + assertType('*ERROR*', 5 / 0); + } + +} From ea6b1476babe4202cec56b810f9e47ac57a47e24 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 31 Aug 2021 10:09:37 +0200 Subject: [PATCH 0192/1284] Update phpdoc-parser --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index b7042244f7..afafdacc09 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "4.3.67", "phpstan/php-8-stubs": "^0.1.22", - "phpstan/phpdoc-parser": "^0.5.5", + "phpstan/phpdoc-parser": "^0.5.6", "react/child-process": "^0.6.1", "react/event-loop": "^1.1", "react/http": "^1.1", diff --git a/composer.lock b/composer.lock index d74c70476e..f7e71e461b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a2b823ea1d5f98ab86840a91bea91ef1", + "content-hash": "8b4fbce456130a08a834574155c6be86", "packages": [ { "name": "clue/block-react", @@ -2177,16 +2177,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "0.5.5", + "version": "0.5.6", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "ea0b17460ec38e20d7eb64e7ec49b5d44af5d28c" + "reference": "fac86158ffc7392e49636f77e63684c026df43b8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/ea0b17460ec38e20d7eb64e7ec49b5d44af5d28c", - "reference": "ea0b17460ec38e20d7eb64e7ec49b5d44af5d28c", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/fac86158ffc7392e49636f77e63684c026df43b8", + "reference": "fac86158ffc7392e49636f77e63684c026df43b8", "shasum": "" }, "require": { @@ -2220,9 +2220,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/0.5.5" + "source": "https://github.com/phpstan/phpdoc-parser/tree/0.5.6" }, - "time": "2021-06-11T13:24:46+00:00" + "time": "2021-08-31T08:08:22+00:00" }, { "name": "psr/container", From a82605fbb80c5b09614ad6b82d872f2833eecce8 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 31 Aug 2021 10:09:59 +0200 Subject: [PATCH 0193/1284] Update PHP 8 stubs --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index afafdacc09..a0acc34938 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "nikic/php-parser": "dev-master as 4.12.0", "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "4.3.67", - "phpstan/php-8-stubs": "^0.1.22", + "phpstan/php-8-stubs": "^0.1.23", "phpstan/phpdoc-parser": "^0.5.6", "react/child-process": "^0.6.1", "react/event-loop": "^1.1", diff --git a/composer.lock b/composer.lock index f7e71e461b..28e4ffa134 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "8b4fbce456130a08a834574155c6be86", + "content-hash": "5da3ab556c01220b08f037130f2e2062", "packages": [ { "name": "clue/block-react", @@ -2145,16 +2145,16 @@ }, { "name": "phpstan/php-8-stubs", - "version": "0.1.22", + "version": "0.1.23", "source": { "type": "git", "url": "https://github.com/phpstan/php-8-stubs.git", - "reference": "1e74d25745ce476a360423607be6250c94a50e2f" + "reference": "3882ffe35d87a7eb7a133916907a44739d020184" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/1e74d25745ce476a360423607be6250c94a50e2f", - "reference": "1e74d25745ce476a360423607be6250c94a50e2f", + "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/3882ffe35d87a7eb7a133916907a44739d020184", + "reference": "3882ffe35d87a7eb7a133916907a44739d020184", "shasum": "" }, "type": "library", @@ -2171,9 +2171,9 @@ "description": "PHP stubs extracted from php-src", "support": { "issues": "https://github.com/phpstan/php-8-stubs/issues", - "source": "https://github.com/phpstan/php-8-stubs/tree/0.1.22" + "source": "https://github.com/phpstan/php-8-stubs/tree/0.1.23" }, - "time": "2021-07-29T00:44:48+00:00" + "time": "2021-08-31T00:08:55+00:00" }, { "name": "phpstan/phpdoc-parser", From f7c6464067e04d5fc3f11b6f624a29fee067b1be Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 31 Aug 2021 10:34:35 +0200 Subject: [PATCH 0194/1284] Sync parse_url signature with PHP 8 --- resources/functionMap.php | 2 +- .../Php/ParseUrlFunctionDynamicReturnTypeExtension.php | 8 +++----- tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 142179f9f2..5d1a69eb1a 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -8230,7 +8230,7 @@ 'parse_ini_file' => ['array|false', 'filename'=>'string', 'process_sections='=>'bool', 'scanner_mode='=>'int'], 'parse_ini_string' => ['array|false', 'ini_string'=>'string', 'process_sections='=>'bool', 'scanner_mode='=>'int'], 'parse_str' => ['void', 'encoded_string'=>'string', '&w_result='=>'array'], -'parse_url' => ['mixed', 'url'=>'string', 'url_component='=>'int'], +'parse_url' => ['array|int|string|false|null', 'url'=>'string', 'url_component='=>'int'], 'ParseError::__clone' => ['void'], 'ParseError::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'(?Throwable)|(?ParseError)'], 'ParseError::__toString' => ['string'], diff --git a/src/Type/Php/ParseUrlFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ParseUrlFunctionDynamicReturnTypeExtension.php index 724ed2dca8..0c0053371f 100644 --- a/src/Type/Php/ParseUrlFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ParseUrlFunctionDynamicReturnTypeExtension.php @@ -36,12 +36,10 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type { - $defaultReturnType = ParametersAcceptorSelector::selectSingle( - $functionReflection->getVariants() - )->getReturnType(); - if (count($functionCall->args) < 1) { - return $defaultReturnType; + return ParametersAcceptorSelector::selectSingle( + $functionReflection->getVariants() + )->getReturnType(); } $this->cacheReturnTypes(); diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 294f140e85..9f37b047cd 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -5917,7 +5917,7 @@ public function dataFunctions(): array ], // parse_url [ - 'mixed', + 'array|int|string|false|null', '$parseUrlWithoutParameters', ], [ From 29d7e086435389bc96aab62f7090d826dc42e716 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 31 Aug 2021 10:59:31 +0200 Subject: [PATCH 0195/1284] Regression test Ref https://github.com/phpstan/phpstan/issues/3555 --- .../Rules/Methods/CallMethodsRuleTest.php | 13 ++++++++ tests/PHPStan/Rules/Methods/data/bug-3555.php | 31 +++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 tests/PHPStan/Rules/Methods/data/bug-3555.php diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 5f00f182c1..c108bf2555 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -2103,4 +2103,17 @@ public function testLiteralString(): void ]); } + public function testBug3555(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/bug-3555.php'], [ + [ + 'Parameter #1 $arg of method Bug3555\Enum::run() expects 1|2|3|4|5|6|7|8|9, 100 given.', + 28, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-3555.php b/tests/PHPStan/Rules/Methods/data/bug-3555.php new file mode 100644 index 0000000000..2a877f75b4 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-3555.php @@ -0,0 +1,31 @@ +run(100); + } + +} From 3c411136b24aca5e4bb252ead6f581337dfdf90f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 31 Aug 2021 11:05:54 +0200 Subject: [PATCH 0196/1284] Regression tests Ref https://github.com/phpstan/phpstan/issues/4793 Ref https://github.com/phpstan/phpstan/issues/5062 Ref https://github.com/phpstan/phpstan/issues/5447 Ref https://github.com/phpstan/phpstan/issues/5454 Ref https://github.com/phpstan/phpstan/issues/3366 Ref https://github.com/phpstan/phpstan/issues/5072 Ref https://github.com/phpstan/phpstan/issues/3530 Ref https://github.com/phpstan/phpstan/issues/5530 Ref https://github.com/phpstan/phpstan/issues/1861 Ref https://github.com/phpstan/phpstan/issues/4843 Ref https://github.com/phpstan/phpstan/issues/4602 Ref https://github.com/phpstan/phpstan/issues/4499 Ref https://github.com/phpstan/phpstan/issues/2142 --- src/PhpDoc/TypeNodeResolver.php | 2 +- .../Analyser/NodeScopeResolverTest.php | 7 ++ tests/PHPStan/Analyser/data/bug-1861.php | 29 ++++++ tests/PHPStan/Analyser/data/bug-2142.php | 99 +++++++++++++++++++ tests/PHPStan/Analyser/data/bug-4499.php | 19 ++++ tests/PHPStan/Analyser/data/bug-4602.php | 21 ++++ tests/PHPStan/Analyser/data/bug-4843.php | 17 ++++ tests/PHPStan/Analyser/data/bug-5072.php | 29 ++++++ tests/PHPStan/Analyser/data/bug-5530.php | 29 ++++++ .../Arrays/AppendedArrayKeyTypeRuleTest.php | 5 + tests/PHPStan/Rules/Arrays/data/bug-5447.php | 36 +++++++ .../Comparison/MatchExpressionRuleTest.php | 8 ++ ...rictComparisonOfDifferentTypesRuleTest.php | 18 ++++ .../Rules/Comparison/data/bug-3366.php | 26 +++++ .../Rules/Comparison/data/bug-4793.php | 41 ++++++++ .../Rules/Comparison/data/bug-5062.php | 25 +++++ .../Rules/Comparison/data/bug-5454.php | 50 ++++++++++ .../Rules/Methods/CallMethodsRuleTest.php | 8 ++ tests/PHPStan/Rules/Methods/data/bug-3530.php | 98 ++++++++++++++++++ 19 files changed, 566 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Analyser/data/bug-1861.php create mode 100644 tests/PHPStan/Analyser/data/bug-2142.php create mode 100644 tests/PHPStan/Analyser/data/bug-4499.php create mode 100644 tests/PHPStan/Analyser/data/bug-4602.php create mode 100644 tests/PHPStan/Analyser/data/bug-4843.php create mode 100644 tests/PHPStan/Analyser/data/bug-5072.php create mode 100644 tests/PHPStan/Analyser/data/bug-5530.php create mode 100644 tests/PHPStan/Rules/Arrays/data/bug-5447.php create mode 100644 tests/PHPStan/Rules/Comparison/data/bug-3366.php create mode 100644 tests/PHPStan/Rules/Comparison/data/bug-4793.php create mode 100644 tests/PHPStan/Rules/Comparison/data/bug-5062.php create mode 100644 tests/PHPStan/Rules/Comparison/data/bug-5454.php create mode 100644 tests/PHPStan/Rules/Methods/data/bug-3530.php diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index 797c4955cd..a4e4102155 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -724,7 +724,7 @@ private function resolveConstTypeNode(ConstTypeNode $typeNode, NameScope $nameSc return new ErrorType(); } - return $classReflection->getConstant($constantName)->getValueType(); + return ConstantTypeHelper::getTypeFromValue($classReflection->getConstant($constantName)->getValue()); } if ($constExpr instanceof ConstExprFloatNode) { diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index eb1c538747..fc58313a97 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -487,6 +487,13 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/sizeof.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/div-by-zero.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5072.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5530.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-1861.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4843.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4602.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4499.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2142.php'); } /** diff --git a/tests/PHPStan/Analyser/data/bug-1861.php b/tests/PHPStan/Analyser/data/bug-1861.php new file mode 100644 index 0000000000..0f00af5b8a --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-1861.php @@ -0,0 +1,29 @@ +children)) { + case 0: + assertType('array()', $this->children); + break; + case 1: + assertType('array<' . self::class . '>&nonEmpty', $this->children); + assertType(self::class, reset($this->children)); + break; + default: + assertType('array<' . self::class . '>&nonEmpty', $this->children); + break; + } + } +} diff --git a/tests/PHPStan/Analyser/data/bug-2142.php b/tests/PHPStan/Analyser/data/bug-2142.php new file mode 100644 index 0000000000..9834c2dc28 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-2142.php @@ -0,0 +1,99 @@ + 0) { + assertType('array&nonEmpty', $arr); + } + } + + /** + * @param string[] $arr + */ + function doFoo2(array $arr): void + { + if (count($arr) != 0) { + assertType('array&nonEmpty', $arr); + } + } + + /** + * @param string[] $arr + */ + function doFoo3(array $arr): void + { + if (count($arr) == 1) { + assertType('array&nonEmpty', $arr); + } + } + + /** + * @param string[] $arr + */ + function doFoo4(array $arr): void + { + if ($arr != []) { + assertType('array&nonEmpty', $arr); + } + } + + /** + * @param string[] $arr + */ + function doFoo5(array $arr): void + { + if (sizeof($arr) !== 0) { + assertType('array&nonEmpty', $arr); + } + } + + /** + * @param string[] $arr + */ + function doFoo6(array $arr): void + { + if (count($arr) !== 0) { + assertType('array&nonEmpty', $arr); + } + } + + + /** + * @param string[] $arr + */ + function doFoo7(array $arr): void + { + if (!empty($arr)) { + assertType('array&nonEmpty', $arr); + } + } + + /** + * @param string[] $arr + */ + function doFoo8(array $arr): void + { + if (count($arr) === 1) { + assertType('array&nonEmpty', $arr); + } + } + + + /** + * @param string[] $arr + */ + function doFoo9(array $arr): void + { + if ($arr !== []) { + assertType('array&nonEmpty', $arr); + } + } + +} diff --git a/tests/PHPStan/Analyser/data/bug-4499.php b/tests/PHPStan/Analyser/data/bug-4499.php new file mode 100644 index 0000000000..5c4b78f63f --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-4499.php @@ -0,0 +1,19 @@ + $things */ + function thing(array $things) : void{ + switch(count($things)){ + case 1: + assertType('array&nonEmpty', $things); + assertType('int', array_shift($things)); + } + } + +} diff --git a/tests/PHPStan/Analyser/data/bug-4602.php b/tests/PHPStan/Analyser/data/bug-4602.php new file mode 100644 index 0000000000..2e843364e5 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-4602.php @@ -0,0 +1,21 @@ +', $limit - 1); + } +} diff --git a/tests/PHPStan/Analyser/data/bug-4843.php b/tests/PHPStan/Analyser/data/bug-4843.php new file mode 100644 index 0000000000..ab021b55af --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-4843.php @@ -0,0 +1,17 @@ +', $this->depth + ($isRoot ? 0 : 1)); + } +} diff --git a/tests/PHPStan/Analyser/data/bug-5072.php b/tests/PHPStan/Analyser/data/bug-5072.php new file mode 100644 index 0000000000..0de9da5e91 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-5072.php @@ -0,0 +1,29 @@ + $params + */ + public function incorrect(array $params): void + { + $page = isset($params['page']) ? intval($params['page']) : 1; + assertType('int<1, max>', max(1, $page)); + } + + public function incorrectWithConstant(): void + { + assertType('int<1, max>', max(1, PHP_INT_MAX)); + } +} diff --git a/tests/PHPStan/Analyser/data/bug-5530.php b/tests/PHPStan/Analyser/data/bug-5530.php new file mode 100644 index 0000000000..6cf8dd8f9a --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-5530.php @@ -0,0 +1,29 @@ +analyse([__DIR__ . '/data/bug-5372_2.php'], []); } + public function testBug5447(): void + { + $this->analyse([__DIR__ . '/data/bug-5447.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Arrays/data/bug-5447.php b/tests/PHPStan/Rules/Arrays/data/bug-5447.php new file mode 100644 index 0000000000..3f7c871d49 --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/bug-5447.php @@ -0,0 +1,36 @@ + + */ + private array $parameters = []; + + /** + * @phpstan-param self::FIELD_* $key + */ + public function setParameter(string $key, string $value) : void + { + $this->parameters[$key] = $value; + } + + /** + * @phpstan-return array + */ + public function getParameters() : array + { + return $this->parameters; + } +} diff --git a/tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php b/tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php index 068306875b..39f6610643 100644 --- a/tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php @@ -123,4 +123,12 @@ public function testBug4857(): void ]); } + public function testBug5454(): void + { + if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + $this->analyse([__DIR__ . '/data/bug-5454.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php index 154ca46df3..6ab3efd944 100644 --- a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php @@ -475,4 +475,22 @@ public function testBug4848(): void ]); } + public function testBug4793(): void + { + $this->checkAlwaysTrueStrictComparison = true; + $this->analyse([__DIR__ . '/data/bug-4793.php'], []); + } + + public function testBug5062(): void + { + $this->checkAlwaysTrueStrictComparison = true; + $this->analyse([__DIR__ . '/data/bug-5062.php'], []); + } + + public function testBug3366(): void + { + $this->checkAlwaysTrueStrictComparison = true; + $this->analyse([__DIR__ . '/data/bug-3366.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-3366.php b/tests/PHPStan/Rules/Comparison/data/bug-3366.php new file mode 100644 index 0000000000..e7076b6c50 --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-3366.php @@ -0,0 +1,26 @@ += 8.0 + +namespace Bug5454; + +class TextFormat{ + public const ESCAPE = "\xc2\xa7"; //§ + + public const BLACK = TextFormat::ESCAPE . "0"; + public const DARK_BLUE = TextFormat::ESCAPE . "1"; + public const DARK_GREEN = TextFormat::ESCAPE . "2"; + public const DARK_AQUA = TextFormat::ESCAPE . "3"; + public const DARK_RED = TextFormat::ESCAPE . "4"; + public const DARK_PURPLE = TextFormat::ESCAPE . "5"; + + public const OBFUSCATED = TextFormat::ESCAPE . "k"; + public const BOLD = TextFormat::ESCAPE . "l"; + public const STRIKETHROUGH = TextFormat::ESCAPE . "m"; + public const UNDERLINE = TextFormat::ESCAPE . "n"; + public const ITALIC = TextFormat::ESCAPE . "o"; + public const RESET = TextFormat::ESCAPE . "r"; +} + +class Terminal +{ + + /** + * @param string[] $string + */ + public static function toANSI(array $string) : string{ + $newString = ""; + foreach($string as $token){ + $newString .= match ($token){ + TextFormat::BOLD => "bold", + TextFormat::OBFUSCATED => "obf", + TextFormat::ITALIC => "italic", + TextFormat::UNDERLINE => "underline", + TextFormat::STRIKETHROUGH => "strike", + TextFormat::RESET => "reset", + TextFormat::BLACK => "black", + TextFormat::DARK_BLUE => "blue", + TextFormat::DARK_GREEN => "green", + TextFormat::DARK_AQUA => "aqua", + TextFormat::DARK_RED => "red", + default => $token, + }; + } + + return $newString; + } +} diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index c108bf2555..13f7f0a93b 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -2116,4 +2116,12 @@ public function testBug3555(): void ]); } + public function testBug3530(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/bug-3530.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-3530.php b/tests/PHPStan/Rules/Methods/data/bug-3530.php new file mode 100644 index 0000000000..d68eca7285 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-3530.php @@ -0,0 +1,98 @@ +> */ + const SYNC_CLASSES = [ + 'a' => A::class, + 'b' => B::class, + 'c' => C::class, + 'd' => D::class, + 'e' => E::class, + 'f' => F::class, + 'g' => G::class, + 'h' => H::class, + 'i' => I::class, + 'j' => J::class, + ]; + + /** @param class-string $class */ + private function getRepository($class) : void + { + } + + public function getSyncEntity(string $type, string $syncId) : void + { + $class = self::SYNC_CLASSES[$type] ?? null; + if($class === null) { + return; + } + + $this->getRepository($class); + } + + public function getSyncEntity2(string $type, string $syncId) : void + { + $class = static::SYNC_CLASSES[$type] ?? null; + if($class === null) { + return; + } + + $this->getRepository($class); + } +} + +class HelloWorld2 +{ + const SYNC_CLASSES = [ + 'a' => A::class, + 'b' => B::class, + 'c' => C::class, + 'd' => D::class, + 'e' => E::class, + 'f' => F::class, + 'g' => G::class, + 'h' => H::class, + 'i' => I::class, + 'j' => J::class, + ]; + + /** @param class-string $class */ + private function getRepository($class) : void + { + } + + public function getSyncEntity(string $type, string $syncId) : void + { + $class = self::SYNC_CLASSES[$type] ?? null; + if($class === null) { + return; + } + + $this->getRepository($class); + } + + public function getSyncEntity2(string $type, string $syncId) : void + { + $class = static::SYNC_CLASSES[$type] ?? null; + if($class === null) { + return; + } + + $this->getRepository($class); + } +} From b105b9ebe3496c1c1f7ad9757a1a143ffedb88a7 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 31 Aug 2021 13:15:18 +0200 Subject: [PATCH 0197/1284] Fix --- tests/PHPStan/Rules/Arrays/data/bug-5447.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Rules/Arrays/data/bug-5447.php b/tests/PHPStan/Rules/Arrays/data/bug-5447.php index 3f7c871d49..d758be05b5 100644 --- a/tests/PHPStan/Rules/Arrays/data/bug-5447.php +++ b/tests/PHPStan/Rules/Arrays/data/bug-5447.php @@ -16,7 +16,7 @@ class A { /** * @phpstan-var array */ - private array $parameters = []; + private $parameters = []; /** * @phpstan-param self::FIELD_* $key From 6e90da1413bfbd38f3a68d244c3021d6be2e8c01 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 31 Aug 2021 15:09:17 +0200 Subject: [PATCH 0198/1284] Rework IntegerRange math --- src/Analyser/MutatingScope.php | 214 +++++++++++------- .../ParentDirectoryRelativePathHelper.php | 5 + src/Type/IntegerRangeType.php | 5 - .../Analyser/NodeScopeResolverTest.php | 3 + tests/PHPStan/Analyser/data/div-by-zero.php | 7 +- .../Analyser/data/integer-range-types.php | 10 +- tests/PHPStan/Analyser/data/math.php | 98 ++++++++ 7 files changed, 250 insertions(+), 92 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/math.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 854fab17f5..6b1e572bb6 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -1268,95 +1268,32 @@ private function resolveType(Expr $node): Type !($node instanceof Node\Expr\BinaryOp\Pow || $node instanceof Node\Expr\AssignOp\Pow)) { if ($leftType instanceof ConstantIntegerType) { - $leftMin = $leftType->getValue(); - $leftMax = $leftType->getValue(); + return $this->integerRangeMath( + $leftType, + $node, + $rightType + ); } elseif ($leftType instanceof UnionType) { - $leftMin = null; - $leftMax = null; - foreach ($leftType->getTypes() as $type) { - if ($type instanceof IntegerRangeType) { - $leftMin = $leftMin !== null ? min($leftMin, $type->getMin()) : $type->getMin(); - $leftMax = max($leftMax, $type->getMax()); - } elseif ($type instanceof ConstantIntegerType) { - if ($node instanceof Node\Expr\BinaryOp\Minus || $node instanceof Node\Expr\AssignOp\Minus || - $node instanceof Node\Expr\BinaryOp\Div || $node instanceof Node\Expr\AssignOp\Div) { - $leftMin = max($leftMin, $type->getValue()); - $leftMax = $leftMax !== null ? min($leftMax, $type->getValue()) : $type->getValue(); - } else { - $leftMin = $leftMin !== null ? min($leftMin, $type->getValue()) : $type->getValue(); - $leftMax = max($leftMax, $type->getValue()); - } - } - } - } else { - $leftMin = $leftType->getMin(); - $leftMax = $leftType->getMax(); - } + $unionParts = []; - if ($rightType instanceof ConstantIntegerType) { - $rightMin = $rightType->getValue(); - $rightMax = $rightType->getValue(); - } elseif ($rightType instanceof UnionType) { - $rightMin = null; - $rightMax = null; - - foreach ($rightType->getTypes() as $type) { - if ($type instanceof IntegerRangeType) { - $rightMin = $rightMin !== null ? min($rightMin, $type->getMin()) : $type->getMin(); - $rightMax = max($rightMax, $type->getMax()); - } elseif ($type instanceof ConstantIntegerType) { - if ($node instanceof Node\Expr\BinaryOp\Minus || $node instanceof Node\Expr\AssignOp\Minus || - $node instanceof Node\Expr\BinaryOp\Div || $node instanceof Node\Expr\AssignOp\Div) { - $rightMin = max($rightMin, $type->getValue()); - $rightMax = $rightMax !== null ? min($rightMax, $type->getValue()) : $type->getValue(); - } else { - $rightMin = $rightMin !== null ? min($rightMin, $type->getValue()) : $type->getValue(); - $rightMax = max($rightMax, $type->getValue()); - } + foreach ($leftType->getTypes() as $type) { + if ($type instanceof IntegerRangeType || $type instanceof ConstantIntegerType) { + $unionParts[] = $this->integerRangeMath($type, $node, $rightType); + } else { + $unionParts[] = $type; } } - } else { - $rightMin = $rightType->getMin(); - $rightMax = $rightType->getMax(); - } - - if ($node instanceof Node\Expr\BinaryOp\Plus || $node instanceof Node\Expr\AssignOp\Plus) { - $min = $leftMin !== null && $rightMin !== null ? $leftMin + $rightMin : null; - $max = $leftMax !== null && $rightMax !== null ? $leftMax + $rightMax : null; - } elseif ($node instanceof Node\Expr\BinaryOp\Minus || $node instanceof Node\Expr\AssignOp\Minus) { - $min = $leftMin !== null && $rightMin !== null ? $leftMin - $rightMin : null; - $max = $leftMax !== null && $rightMax !== null ? $leftMax - $rightMax : null; - if ($min !== null && $max !== null && $min > $max) { - [$min, $max] = [$max, $min]; + $union = TypeCombinator::union(...$unionParts); + if ($leftType instanceof BenevolentUnionType) { + return TypeUtils::toBenevolentUnion($union)->toNumber(); } - } elseif ($node instanceof Node\Expr\BinaryOp\Mul || $node instanceof Node\Expr\AssignOp\Mul) { - $min = $leftMin !== null && $rightMin !== null ? $leftMin * $rightMin : null; - $max = $leftMax !== null && $rightMax !== null ? $leftMax * $rightMax : null; - } else { - $min = $leftMin !== null && $rightMin !== null && $rightMin !== 0 ? (int) ($leftMin / $rightMin) : null; - $max = $leftMax !== null && $rightMax !== null && $rightMax !== 0 ? (int) ($leftMax / $rightMax) : null; - if ($min !== null && $max !== null && $min > $max) { - [$min, $max] = [$max, $min]; - } + return $union->toNumber(); } - if ($min !== null || $max !== null) { - $integerRange = IntegerRangeType::fromInterval($min, $max); - - if ($node instanceof Node\Expr\BinaryOp\Div || $node instanceof Node\Expr\AssignOp\Div) { - if ($min === $max && $min === 0) { - // division of upper and lower bound turns into a tiny 0.x fraction, which casted to int turns into 0. - // this leads to a useless 0|float type; we return only float instead. - return new FloatType(); - } - return TypeCombinator::union($integerRange, new FloatType()); - } - - return $integerRange; - } + return $this->integerRangeMath($leftType, $node, $rightType); } $operatorSigil = null; @@ -5175,4 +5112,123 @@ private function propertyFetchType(Type $fetchedOnType, string $propertyName, Ex return $propertyReflection->getReadableType(); } + /** + * @param ConstantIntegerType|IntegerRangeType $range + * @param \PhpParser\Node\Expr\AssignOp\Div|\PhpParser\Node\Expr\AssignOp\Minus|\PhpParser\Node\Expr\AssignOp\Mul|\PhpParser\Node\Expr\AssignOp\Plus|\PhpParser\Node\Expr\BinaryOp\Div|\PhpParser\Node\Expr\BinaryOp\Minus|\PhpParser\Node\Expr\BinaryOp\Mul|\PhpParser\Node\Expr\BinaryOp\Plus $node + * @param IntegerRangeType|ConstantIntegerType|UnionType $operand + */ + private function integerRangeMath(Type $range, Expr $node, Type $operand): Type + { + if ($range instanceof IntegerRangeType) { + $rangeMin = $range->getMin(); + $rangeMax = $range->getMax(); + } else { + $rangeMin = $range->getValue(); + $rangeMax = $rangeMin; + } + + if ($operand instanceof UnionType) { + + $unionParts = []; + + foreach ($operand->getTypes() as $type) { + if ($type instanceof IntegerRangeType || $type instanceof ConstantIntegerType) { + $unionParts[] = $this->integerRangeMath($range, $node, $type); + } else { + $unionParts[] = $type->toNumber(); + } + } + + $union = TypeCombinator::union(...$unionParts); + if ($operand instanceof BenevolentUnionType) { + return TypeUtils::toBenevolentUnion($union)->toNumber(); + } + + return $union->toNumber(); + } + + if ($node instanceof Node\Expr\BinaryOp\Plus || $node instanceof Node\Expr\AssignOp\Plus) { + if ($operand instanceof ConstantIntegerType) { + $min = $rangeMin !== null ? $rangeMin + $operand->getValue() : null; + $max = $rangeMax !== null ? $rangeMax + $operand->getValue() : null; + } else { + $min = $rangeMin !== null && $operand->getMin() !== null ? $rangeMin + $operand->getMin() : null; + $max = $rangeMax !== null && $operand->getMax() !== null ? $rangeMax + $operand->getMax() : null; + } + } elseif ($node instanceof Node\Expr\BinaryOp\Minus || $node instanceof Node\Expr\AssignOp\Minus) { + if ($operand instanceof ConstantIntegerType) { + $min = $rangeMin !== null ? $rangeMin - $operand->getValue() : null; + $max = $rangeMax !== null ? $rangeMax - $operand->getValue() : null; + } else { + if ($rangeMin === $rangeMax && $rangeMin !== null + && ($operand->getMin() === null || $operand->getMax() === null)) { + $min = null; + $max = $rangeMin; + } else { + if ($operand->getMin() === null) { + $min = null; + } elseif ($rangeMin !== null) { + $min = $rangeMin - $operand->getMin(); + } else { + $min = null; + } + + if ($operand->getMax() === null) { + $max = null; + } elseif ($rangeMax !== null) { + $max = $rangeMax - $operand->getMax(); + } else { + $max = null; + } + + if ($min !== null && $max !== null && $min > $max) { + [$min, $max] = [$max, $min]; + } + } + } + } elseif ($node instanceof Node\Expr\BinaryOp\Mul || $node instanceof Node\Expr\AssignOp\Mul) { + if ($operand instanceof ConstantIntegerType) { + $min = $rangeMin !== null ? $rangeMin * $operand->getValue() : null; + $max = $rangeMax !== null ? $rangeMax * $operand->getValue() : null; + } else { + $min = $rangeMin !== null && $operand->getMin() !== null ? $rangeMin * $operand->getMin() : null; + $max = $rangeMax !== null && $operand->getMax() !== null ? $rangeMax * $operand->getMax() : null; + } + + if ($min !== null && $max !== null && $min > $max) { + [$min, $max] = [$max, $min]; + } + + } else { + if ($operand instanceof ConstantIntegerType) { + $min = $rangeMin !== null && $operand->getValue() !== 0 ? $rangeMin / $operand->getValue() : null; + $max = $rangeMax !== null && $operand->getValue() !== 0 ? $rangeMax / $operand->getValue() : null; + } else { + $min = $rangeMin !== null && $operand->getMin() !== null && $operand->getMin() !== 0 ? $rangeMin / $operand->getMin() : null; + $max = $rangeMax !== null && $operand->getMax() !== null && $operand->getMax() !== 0 ? $rangeMax / $operand->getMax() : null; + } + + if ($operand instanceof IntegerRangeType + && ($operand->getMin() === null || $operand->getMax() === null) + || ($rangeMin === null || $rangeMax === null) + || is_float($min) || is_float($max) + ) { + if (is_float($min)) { + $min = (int) $min; + } + if (is_float($max)) { + $max = (int) $max; + } + + if ($min !== null && $max !== null && $min > $max) { + [$min, $max] = [$max, $min]; + } + + return TypeCombinator::union(IntegerRangeType::fromInterval($min, $max), new FloatType()); + } + } + + return IntegerRangeType::fromInterval($min, $max); + } + } diff --git a/src/File/ParentDirectoryRelativePathHelper.php b/src/File/ParentDirectoryRelativePathHelper.php index 3aa54fd267..54582fd5ff 100644 --- a/src/File/ParentDirectoryRelativePathHelper.php +++ b/src/File/ParentDirectoryRelativePathHelper.php @@ -54,6 +54,11 @@ public function getFilenameParts(string $filename): array } $dotsCount = $parentPartsCount - $i; + + if ($dotsCount < 0) { + throw new \PHPStan\ShouldNotHappenException(); + } + return array_merge(array_fill(0, $dotsCount, '..'), array_slice($filenameParts, $i)); } diff --git a/src/Type/IntegerRangeType.php b/src/Type/IntegerRangeType.php index 7d2982c8d7..fc686d5e94 100644 --- a/src/Type/IntegerRangeType.php +++ b/src/Type/IntegerRangeType.php @@ -400,11 +400,6 @@ public function getGreaterOrEqualType(): Type return TypeCombinator::remove(new MixedType(), TypeCombinator::union(...$subtractedTypes)); } - public function toNumber(): Type - { - return new parent(); - } - public function toBoolean(): BooleanType { $isZero = (new ConstantIntegerType(0))->isSuperTypeOf($this); diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index fc58313a97..63b062e924 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -487,6 +487,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/sizeof.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/div-by-zero.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5072.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5530.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-1861.php'); @@ -494,6 +495,8 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4602.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4499.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2142.php'); + + yield from $this->gatherAssertTypes(__DIR__ . '/data/math.php'); } /** diff --git a/tests/PHPStan/Analyser/data/div-by-zero.php b/tests/PHPStan/Analyser/data/div-by-zero.php index 75635d30dc..379a07c00b 100644 --- a/tests/PHPStan/Analyser/data/div-by-zero.php +++ b/tests/PHPStan/Analyser/data/div-by-zero.php @@ -11,10 +11,11 @@ class Foo * @param int<0, max> $range1 * @param int $range2 */ - public function doFoo(int $range1, int $range2): void + public function doFoo(int $range1, int $range2, int $int): void { - assertType('(float|int)', 5 / $range1); - assertType('(float|int)', 5 / $range2); + assertType('float|int', 5 / $range1); + assertType('float|int', 5 / $range2); + assertType('(float|int)', 5 / $int); assertType('*ERROR*', 5 / 0); } diff --git a/tests/PHPStan/Analyser/data/integer-range-types.php b/tests/PHPStan/Analyser/data/integer-range-types.php index 421a67a64b..41998a4830 100644 --- a/tests/PHPStan/Analyser/data/integer-range-types.php +++ b/tests/PHPStan/Analyser/data/integer-range-types.php @@ -228,10 +228,10 @@ public function math($i, $j, $z, $pi, $r1, $r2, $rMin, $rMax, $x, $y) { assertType('int', $j * $rMin); assertType('int<5, max>', $j * $rMax); - assertType('int<-19, 13>', $r1 + $z); - assertType('int<-2, 30>', $r1 - $z); - assertType('int<-20, 30>', $r1 * $z); - assertType('float', $r1 / $z); + assertType('int<-19, -10>|int<2, 13>', $r1 + $z); + assertType('int<-2, 9>|int<21, 30>', $r1 - $z); + assertType('int<-200, -20>|int<1, 30>', $r1 * $z); + assertType('float|int<0, 10>', $r1 / $z); assertType('int', $rMin * $z); assertType('int<-100, max>', $rMax * $z); @@ -240,7 +240,7 @@ public function math($i, $j, $z, $pi, $r1, $r2, $rMin, $rMax, $x, $y) { assertType('int<2, max>', $pi * 2); assertType('float|int<0, max>', $pi / 2); assertType('int<2, max>', 1 + $pi); - assertType('int<1, max>', 2 - $pi); + assertType('int', 2 - $pi); assertType('int<2, max>', 2 * $pi); assertType('float|int<2, max>', 2 / $pi); diff --git a/tests/PHPStan/Analyser/data/math.php b/tests/PHPStan/Analyser/data/math.php new file mode 100644 index 0000000000..5a1efcfffd --- /dev/null +++ b/tests/PHPStan/Analyser/data/math.php @@ -0,0 +1,98 @@ +', self::MAX_TOTAL_PRODUCTS - count($excluded)); + assertType('int', self::MAX_TOTAL_PRODUCTS - $i); + + $maxOrPlusOne = self::MAX_TOTAL_PRODUCTS; + if (rand(0, 1)) { + $maxOrPlusOne++; + } + + assertType('22|23', $maxOrPlusOne); + assertType('int', $maxOrPlusOne - count($excluded)); + } + + public function doBar(int $notZero): void + { + if ($notZero === 0) { + return; + } + + assertType('int|int<2, max>', $notZero + 1); + } + + /** + * @param int<-5, 5> $rangeFiveBoth + * @param int<-5, max> $rangeFiveLeft + * @param int $rangeFiveRight + */ + public function doBaz(int $rangeFiveBoth, int $rangeFiveLeft, int $rangeFiveRight): void + { + assertType('int<-4, 6>', $rangeFiveBoth + 1); + assertType('int<-4, max>', $rangeFiveLeft + 1); + assertType('int<-6, max>', $rangeFiveLeft - 1); + assertType('int', $rangeFiveRight + 1); + assertType('int', $rangeFiveRight - 1); + + assertType('int', $rangeFiveLeft + $rangeFiveRight); + assertType('int', $rangeFiveLeft - $rangeFiveRight); + + assertType('int', $rangeFiveRight + $rangeFiveLeft); + assertType('int', $rangeFiveRight - $rangeFiveLeft); + + assertType('int<-10, 10>', $rangeFiveBoth + $rangeFiveBoth); + assertType('0', $rangeFiveBoth - $rangeFiveBoth); + + assertType('int<-10, max>', $rangeFiveBoth + $rangeFiveLeft); + assertType('int<0, max>', $rangeFiveBoth - $rangeFiveLeft); + + assertType('int', $rangeFiveBoth + $rangeFiveRight); + assertType('int', $rangeFiveBoth - $rangeFiveRight); + + assertType('int<-10, max>', $rangeFiveLeft + $rangeFiveBoth); + assertType('int<0, max>', $rangeFiveLeft - $rangeFiveBoth); + + assertType('int', $rangeFiveRight + $rangeFiveBoth); + assertType('int', $rangeFiveRight - $rangeFiveBoth); + } + + public function doLorem($a, $b): void + { + $nullsReverse = rand(0, 1) ? 1 : -1; + $comparison = $a <=> $b; + assertType('int<-1, 1>', $comparison); + assertType('-1|1', $nullsReverse); + assertType('int<-1, 1>', $comparison * $nullsReverse); + } + + public function doIpsum(int $newLevel): void + { + $min = min(30, $newLevel); + assertType('int', $min); + $minDivFive = $min / 5; + assertType('float|int', $minDivFive); + $volume = 0x10000000 * $minDivFive; + assertType('float|int', $volume); + } + + public function doDolor(int $i): void + { + $chunks = min(200, $i); + assertType('int', $chunks); + $divThirty = $chunks / 30; + assertType('float|int', $divThirty); + assertType('float|int', $divThirty + 3); + } + +} From c7370959e101c0b7503ddfffe08eb1ad942b1c51 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 31 Aug 2021 16:03:12 +0200 Subject: [PATCH 0199/1284] Division of int ranges produces BenevolentUnionType --- src/Analyser/MutatingScope.php | 4 ++++ tests/PHPStan/Analyser/data/div-by-zero.php | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 6b1e572bb6..6bf66d46ce 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -5224,6 +5224,10 @@ private function integerRangeMath(Type $range, Expr $node, Type $operand): Type [$min, $max] = [$max, $min]; } + if ($min === null && $max === null) { + return new BenevolentUnionType([new IntegerType(), new FloatType()]); + } + return TypeCombinator::union(IntegerRangeType::fromInterval($min, $max), new FloatType()); } } diff --git a/tests/PHPStan/Analyser/data/div-by-zero.php b/tests/PHPStan/Analyser/data/div-by-zero.php index 379a07c00b..0c38a50c10 100644 --- a/tests/PHPStan/Analyser/data/div-by-zero.php +++ b/tests/PHPStan/Analyser/data/div-by-zero.php @@ -13,8 +13,9 @@ class Foo */ public function doFoo(int $range1, int $range2, int $int): void { - assertType('float|int', 5 / $range1); - assertType('float|int', 5 / $range2); + assertType('(float|int)', 5 / $range1); + assertType('(float|int)', 5 / $range2); + assertType('(float|int)', $range1 / $range2); assertType('(float|int)', 5 / $int); assertType('*ERROR*', 5 / 0); } From a6a23dcaf6254c182e39117cbeb9f8087c1ee22c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 1 Sep 2021 09:08:59 +0200 Subject: [PATCH 0200/1284] Refactor PreInc and PreDec --- src/Analyser/MutatingScope.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 6bf66d46ce..20098d854f 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -1763,6 +1763,7 @@ private function resolveType(Expr $node): Type } elseif ($node instanceof Expr\PreInc || $node instanceof Expr\PreDec) { $varType = $this->getType($node->var); $varScalars = TypeUtils::getConstantScalars($varType); + $stringType = new StringType(); if (count($varScalars) > 0) { $newTypes = []; @@ -1777,19 +1778,18 @@ private function resolveType(Expr $node): Type $newTypes[] = $this->getTypeFromValue($varValue); } return TypeCombinator::union(...$newTypes); - } elseif ($varType instanceof IntegerRangeType) { - return $varType->shift($node instanceof Expr\PreInc ? +1 : -1); - } - - $stringType = new StringType(); - if ($stringType->isSuperTypeOf($varType)->yes()) { + } elseif ($stringType->isSuperTypeOf($varType)->yes()) { if ($varType->isLiteralString()->yes()) { return new IntersectionType([$stringType, new AccessoryLiteralStringType()]); } return $stringType; } - return $varType->toNumber(); + if ($node instanceof Expr\PreInc) { + return $this->getType(new BinaryOp\Plus($node->var, new LNumber(1))); + } + + return $this->getType(new BinaryOp\Minus($node->var, new LNumber(1))); } elseif ($node instanceof Expr\Yield_) { $functionReflection = $this->getFunction(); if ($functionReflection === null) { From f6e4f87a2d19236c322176871ffe2b0f34d16efb Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 1 Sep 2021 09:29:22 +0200 Subject: [PATCH 0201/1284] Regression test Ref https://github.com/phpstan/phpstan/issues/1870 --- .../Analyser/NodeScopeResolverTest.php | 2 ++ tests/PHPStan/Analyser/data/bug-1870.php | 23 +++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 tests/PHPStan/Analyser/data/bug-1870.php diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 63b062e924..7fe63f2d18 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -497,6 +497,8 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2142.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/math.php'); + + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-1870.php'); } /** diff --git a/tests/PHPStan/Analyser/data/bug-1870.php b/tests/PHPStan/Analyser/data/bug-1870.php new file mode 100644 index 0000000000..2010b6cfc1 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-1870.php @@ -0,0 +1,23 @@ + Date: Wed, 1 Sep 2021 09:34:30 +0200 Subject: [PATCH 0202/1284] Fix inferring TemplateUnionType --- src/Type/Generic/TemplateTypeTrait.php | 2 +- .../Analyser/NodeScopeResolverTest.php | 1 + .../Rules/Methods/CallMethodsRuleTest.php | 8 +++++ tests/PHPStan/Rules/Methods/data/bug-5562.php | 34 +++++++++++++++++++ 4 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Rules/Methods/data/bug-5562.php diff --git a/src/Type/Generic/TemplateTypeTrait.php b/src/Type/Generic/TemplateTypeTrait.php index 498c1de77a..d62acb63a5 100644 --- a/src/Type/Generic/TemplateTypeTrait.php +++ b/src/Type/Generic/TemplateTypeTrait.php @@ -162,7 +162,7 @@ public function isSubTypeOf(Type $type): TrinaryLogic public function inferTemplateTypes(Type $receivedType): TemplateTypeMap { - if ($receivedType instanceof UnionType || $receivedType instanceof IntersectionType) { + if (!$receivedType instanceof TemplateType && ($receivedType instanceof UnionType || $receivedType instanceof IntersectionType)) { return $receivedType->inferTemplateTypesOn($this); } diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 7fe63f2d18..de90465d96 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -499,6 +499,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/math.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-1870.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/bug-5562.php'); } /** diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 13f7f0a93b..842737f589 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -2124,4 +2124,12 @@ public function testBug3530(): void $this->analyse([__DIR__ . '/data/bug-3530.php'], []); } + public function testBug5562(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/bug-5562.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-5562.php b/tests/PHPStan/Rules/Methods/data/bug-5562.php new file mode 100644 index 0000000000..3bcf4b1d4e --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-5562.php @@ -0,0 +1,34 @@ +bar($test); + assertType('T of int|string (method Bug5562\Foo::foo(), argument)', $bar); + + return $bar; + } + + /** + * @template T of int|string + * @param T $test + * @return T + */ + public function bar($test) + { + return $test; + } + +} From 0fcd93bbe86b460c077d8100c37aab5637e1a7a0 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 1 Sep 2021 11:17:00 +0200 Subject: [PATCH 0203/1284] Fix IntegerRange math edge case --- src/Analyser/MutatingScope.php | 1 + .../Analyser/data/integer-range-types.php | 2 +- tests/PHPStan/Analyser/data/math.php | 16 +++++++++++++++- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 20098d854f..123b46204f 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -5174,6 +5174,7 @@ private function integerRangeMath(Type $range, Expr $node, Type $operand): Type } if ($operand->getMax() === null) { + $min = null; $max = null; } elseif ($rangeMax !== null) { $max = $rangeMax - $operand->getMax(); diff --git a/tests/PHPStan/Analyser/data/integer-range-types.php b/tests/PHPStan/Analyser/data/integer-range-types.php index 41998a4830..45e3c55717 100644 --- a/tests/PHPStan/Analyser/data/integer-range-types.php +++ b/tests/PHPStan/Analyser/data/integer-range-types.php @@ -268,7 +268,7 @@ public function math($i, $j, $z, $pi, $r1, $r2, $rMin, $rMax, $x, $y) { assertType('float|int', $rMin / $r1); assertType('int<6, max>', $r1 + $rMax); - assertType('int<-4, max>', $r1 - $rMax); + assertType('int', $r1 - $rMax); assertType('int<5, max>', $r1 * $rMax); assertType('float|int<0, max>', $r1 / $rMax); assertType('int<6, max>', $rMax + $r1); diff --git a/tests/PHPStan/Analyser/data/math.php b/tests/PHPStan/Analyser/data/math.php index 5a1efcfffd..99a364ab09 100644 --- a/tests/PHPStan/Analyser/data/math.php +++ b/tests/PHPStan/Analyser/data/math.php @@ -55,7 +55,7 @@ public function doBaz(int $rangeFiveBoth, int $rangeFiveLeft, int $rangeFiveRigh assertType('0', $rangeFiveBoth - $rangeFiveBoth); assertType('int<-10, max>', $rangeFiveBoth + $rangeFiveLeft); - assertType('int<0, max>', $rangeFiveBoth - $rangeFiveLeft); + assertType('int', $rangeFiveBoth - $rangeFiveLeft); assertType('int', $rangeFiveBoth + $rangeFiveRight); assertType('int', $rangeFiveBoth - $rangeFiveRight); @@ -95,4 +95,18 @@ public function doDolor(int $i): void assertType('float|int', $divThirty + 3); } + public function doSit(int $i, int $j): void + { + if ($i < 0) { + return; + } + if ($j < 1) { + return; + } + + assertType('int<0, max>', $i); + assertType('int<1, max>', $j); + assertType('int', $i - $j); + } + } From 3d6a42179375a49e31888da3a645a3a94a65e44d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 1 Sep 2021 11:59:32 +0200 Subject: [PATCH 0204/1284] Change UnionType description if it contains TemplateUnionType --- src/Type/UnionType.php | 3 +- .../Rules/Methods/ReturnTypeRuleTest.php | 15 ++++++++ .../Methods/data/return-template-union.php | 38 +++++++++++++++++++ tests/PHPStan/Type/TypeCombinatorTest.php | 2 +- 4 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Rules/Methods/data/return-template-union.php diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index f1c2ee2aae..d24baf3bbc 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -16,6 +16,7 @@ use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\Generic\TemplateTypeMap; use PHPStan\Type\Generic\TemplateTypeVariance; +use PHPStan\Type\Generic\TemplateUnionType; /** @api */ class UnionType implements CompoundType @@ -153,7 +154,7 @@ public function describe(VerbosityLevel $level): string $joinTypes = static function (array $types) use ($level): string { $typeNames = []; foreach ($types as $type) { - if ($type instanceof ClosureType || $type instanceof CallableType) { + if ($type instanceof ClosureType || $type instanceof CallableType || $type instanceof TemplateUnionType) { $typeNames[] = sprintf('(%s)', $type->describe($level)); } elseif ($type instanceof IntersectionType) { $intersectionDescription = $type->describe($level); diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index 2a59ec2ed7..cdc7ad1b48 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -512,4 +512,19 @@ public function testBug3151(): void $this->analyse([__DIR__ . '/data/bug-3151.php'], []); } + public function testTemplateUnion(): void + { + $this->analyse([__DIR__ . '/data/return-template-union.php'], [ + [ + 'Method ReturnTemplateUnion\Foo::doFoo2() should return T of bool|float|int|string but returns (T of bool|float|int|string)|null.', + 25, + ], + [ + // should not be reported + 'Method ReturnTemplateUnion\Foo::doFoo3() should return (T of bool|float|int|string)|null but returns (T of bool|float|int|string)|null.', + 35, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/return-template-union.php b/tests/PHPStan/Rules/Methods/data/return-template-union.php new file mode 100644 index 0000000000..a154bfd03d --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/return-template-union.php @@ -0,0 +1,38 @@ + Date: Thu, 2 Sep 2021 13:22:01 +0200 Subject: [PATCH 0205/1284] Introduce StubPhpDocProviderFactory --- conf/config.neon | 4 +++ src/PhpDoc/StubPhpDocProviderFactory.php | 42 ++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 src/PhpDoc/StubPhpDocProviderFactory.php diff --git a/conf/config.neon b/conf/config.neon index 3c183f59bf..d4b92dd5b2 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1612,6 +1612,10 @@ services: stubPhpDocProvider: class: PHPStan\PhpDoc\StubPhpDocProvider + factory: @PHPStan\PhpDoc\StubPhpDocProviderFactory::create() + + - + class: PHPStan\PhpDoc\StubPhpDocProviderFactory arguments: stubFiles: %stubFiles% diff --git a/src/PhpDoc/StubPhpDocProviderFactory.php b/src/PhpDoc/StubPhpDocProviderFactory.php new file mode 100644 index 0000000000..7b83eae369 --- /dev/null +++ b/src/PhpDoc/StubPhpDocProviderFactory.php @@ -0,0 +1,42 @@ +parser = $parser; + $this->fileTypeMapper = $fileTypeMapper; + $this->stubFiles = $stubFiles; + } + + public function create(): StubPhpDocProvider + { + return new StubPhpDocProvider( + $this->parser, + $this->fileTypeMapper, + $this->stubFiles + ); + } + +} From 2ba9332d29c1acdde0f85a2781308d2e8972f00e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 2 Sep 2021 13:42:38 +0200 Subject: [PATCH 0206/1284] Allow dynamic list of stub files thanks to StubFilesExtension --- src/PhpDoc/StubFilesExtension.php | 13 +++++++++++++ src/PhpDoc/StubPhpDocProviderFactory.php | 16 +++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 src/PhpDoc/StubFilesExtension.php diff --git a/src/PhpDoc/StubFilesExtension.php b/src/PhpDoc/StubFilesExtension.php new file mode 100644 index 0000000000..961b2f4b89 --- /dev/null +++ b/src/PhpDoc/StubFilesExtension.php @@ -0,0 +1,13 @@ +parser = $parser; $this->fileTypeMapper = $fileTypeMapper; + $this->container = $container; $this->stubFiles = $stubFiles; } public function create(): StubPhpDocProvider { + $stubFiles = $this->stubFiles; + $extensions = $this->container->getServicesByTag(StubFilesExtension::EXTENSION_TAG); + foreach ($extensions as $extension) { + $extensionFiles = $extension->getFiles(); + foreach ($extensionFiles as $extensionFile) { + $stubFiles[] = $extensionFile; + } + } + return new StubPhpDocProvider( $this->parser, $this->fileTypeMapper, - $this->stubFiles + $stubFiles ); } From a4991f0afd539ef8e4bdd8317f9ffa1c141b4394 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 2 Sep 2021 14:05:05 +0200 Subject: [PATCH 0207/1284] Dynamic stub files list done differently --- conf/config.neon | 4 -- src/PhpDoc/StubPhpDocProvider.php | 24 +++++++++- src/PhpDoc/StubPhpDocProviderFactory.php | 56 ------------------------ 3 files changed, 23 insertions(+), 61 deletions(-) delete mode 100644 src/PhpDoc/StubPhpDocProviderFactory.php diff --git a/conf/config.neon b/conf/config.neon index d4b92dd5b2..3c183f59bf 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1612,10 +1612,6 @@ services: stubPhpDocProvider: class: PHPStan\PhpDoc\StubPhpDocProvider - factory: @PHPStan\PhpDoc\StubPhpDocProviderFactory::create() - - - - class: PHPStan\PhpDoc\StubPhpDocProviderFactory arguments: stubFiles: %stubFiles% diff --git a/src/PhpDoc/StubPhpDocProvider.php b/src/PhpDoc/StubPhpDocProvider.php index c4c3619e84..b1dadd9444 100644 --- a/src/PhpDoc/StubPhpDocProvider.php +++ b/src/PhpDoc/StubPhpDocProvider.php @@ -7,6 +7,7 @@ use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\Interface_; use PhpParser\Node\Stmt\Trait_; +use PHPStan\DependencyInjection\Container; use PHPStan\Parser\Parser; use PHPStan\Type\FileTypeMapper; use function array_key_exists; @@ -18,6 +19,8 @@ class StubPhpDocProvider private \PHPStan\Type\FileTypeMapper $fileTypeMapper; + private Container $container; + /** @var string[] */ private array $stubFiles; @@ -68,11 +71,13 @@ class StubPhpDocProvider public function __construct( Parser $parser, FileTypeMapper $fileTypeMapper, + Container $container, array $stubFiles ) { $this->parser = $parser; $this->fileTypeMapper = $fileTypeMapper; + $this->container = $container; $this->stubFiles = $stubFiles; } @@ -280,7 +285,7 @@ private function initializeKnownElements(): void $this->initializing = true; try { - foreach ($this->stubFiles as $stubFile) { + foreach ($this->getStubFiles() as $stubFile) { $nodes = $this->parser->parseFile($stubFile); foreach ($nodes as $node) { $this->initializeKnownElementNode($stubFile, $node); @@ -292,6 +297,23 @@ private function initializeKnownElements(): void } } + /** + * @return string[] + */ + private function getStubFiles(): array + { + $stubFiles = $this->stubFiles; + $extensions = $this->container->getServicesByTag(StubFilesExtension::EXTENSION_TAG); + foreach ($extensions as $extension) { + $extensionFiles = $extension->getFiles(); + foreach ($extensionFiles as $extensionFile) { + $stubFiles[] = $extensionFile; + } + } + + return $stubFiles; + } + private function initializeKnownElementNode(string $stubFile, Node $node): void { if ($node instanceof Node\Stmt\Namespace_) { diff --git a/src/PhpDoc/StubPhpDocProviderFactory.php b/src/PhpDoc/StubPhpDocProviderFactory.php deleted file mode 100644 index 382891ab4c..0000000000 --- a/src/PhpDoc/StubPhpDocProviderFactory.php +++ /dev/null @@ -1,56 +0,0 @@ -parser = $parser; - $this->fileTypeMapper = $fileTypeMapper; - $this->container = $container; - $this->stubFiles = $stubFiles; - } - - public function create(): StubPhpDocProvider - { - $stubFiles = $this->stubFiles; - $extensions = $this->container->getServicesByTag(StubFilesExtension::EXTENSION_TAG); - foreach ($extensions as $extension) { - $extensionFiles = $extension->getFiles(); - foreach ($extensionFiles as $extensionFile) { - $stubFiles[] = $extensionFile; - } - } - - return new StubPhpDocProvider( - $this->parser, - $this->fileTypeMapper, - $stubFiles - ); - } - -} From 0ebfea013b4d625bc0bc31642679e85f78b456ca Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 2 Sep 2021 14:15:18 +0200 Subject: [PATCH 0208/1284] StubFilesExtension is part of BC promise --- src/PhpDoc/StubFilesExtension.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/PhpDoc/StubFilesExtension.php b/src/PhpDoc/StubFilesExtension.php index 961b2f4b89..237f2a04d3 100644 --- a/src/PhpDoc/StubFilesExtension.php +++ b/src/PhpDoc/StubFilesExtension.php @@ -2,6 +2,7 @@ namespace PHPStan\PhpDoc; +/** @api */ interface StubFilesExtension { From ddd520fd89d628ac9f0b0b029f8d5e3a92b1851a Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 3 Sep 2021 10:17:57 +0200 Subject: [PATCH 0209/1284] Fix false-positive when merging unions with plus operator --- src/Analyser/MutatingScope.php | 104 +++++++++--------- .../Analyser/NodeScopeResolverTest.php | 1 + tests/PHPStan/Analyser/data/bug-5584.php | 24 ++++ 3 files changed, 77 insertions(+), 52 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/bug-5584.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 123b46204f..0adf29cc8d 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -1263,6 +1263,58 @@ private function resolveType(Expr $node): Type $leftType = $this->getType($left); $rightType = $this->getType($right); + if ($node instanceof Expr\AssignOp\Plus || $node instanceof Expr\BinaryOp\Plus) { + $leftConstantArrays = TypeUtils::getConstantArrays($leftType); + $rightConstantArrays = TypeUtils::getConstantArrays($rightType); + + if (count($leftConstantArrays) > 0 && count($rightConstantArrays) > 0) { + $resultTypes = []; + foreach ($rightConstantArrays as $rightConstantArray) { + foreach ($leftConstantArrays as $leftConstantArray) { + $newArrayBuilder = ConstantArrayTypeBuilder::createFromConstantArray($rightConstantArray); + foreach ($leftConstantArray->getKeyTypes() as $leftKeyType) { + $newArrayBuilder->setOffsetValueType( + $leftKeyType, + $leftConstantArray->getOffsetValueType($leftKeyType) + ); + } + $resultTypes[] = $newArrayBuilder->getArray(); + } + } + + return TypeCombinator::union(...$resultTypes); + } + $arrayType = new ArrayType(new MixedType(), new MixedType()); + + if ($arrayType->isSuperTypeOf($leftType)->yes() && $arrayType->isSuperTypeOf($rightType)->yes()) { + if ($leftType->getIterableKeyType()->equals($rightType->getIterableKeyType())) { + // to preserve BenevolentUnionType + $keyType = $leftType->getIterableKeyType(); + } else { + $keyTypes = []; + foreach ([ + $leftType->getIterableKeyType(), + $rightType->getIterableKeyType(), + ] as $keyType) { + $keyTypes[] = $keyType; + } + $keyType = TypeCombinator::union(...$keyTypes); + } + return new ArrayType( + $keyType, + TypeCombinator::union($leftType->getIterableValueType(), $rightType->getIterableValueType()) + ); + } + + if ($leftType instanceof MixedType && $rightType instanceof MixedType) { + return new BenevolentUnionType([ + new FloatType(), + new IntegerType(), + new ArrayType(new MixedType(), new MixedType()), + ]); + } + } + if (($leftType instanceof IntegerRangeType || $leftType instanceof ConstantIntegerType || $leftType instanceof UnionType) && ($rightType instanceof IntegerRangeType || $rightType instanceof ConstantIntegerType || $rightType instanceof UnionType) && !($node instanceof Node\Expr\BinaryOp\Pow || $node instanceof Node\Expr\AssignOp\Pow)) { @@ -1321,58 +1373,6 @@ private function resolveType(Expr $node): Type } } - if ($node instanceof Expr\AssignOp\Plus || $node instanceof Expr\BinaryOp\Plus) { - $leftConstantArrays = TypeUtils::getConstantArrays($leftType); - $rightConstantArrays = TypeUtils::getConstantArrays($rightType); - - if (count($leftConstantArrays) > 0 && count($rightConstantArrays) > 0) { - $resultTypes = []; - foreach ($rightConstantArrays as $rightConstantArray) { - foreach ($leftConstantArrays as $leftConstantArray) { - $newArrayBuilder = ConstantArrayTypeBuilder::createFromConstantArray($rightConstantArray); - foreach ($leftConstantArray->getKeyTypes() as $leftKeyType) { - $newArrayBuilder->setOffsetValueType( - $leftKeyType, - $leftConstantArray->getOffsetValueType($leftKeyType) - ); - } - $resultTypes[] = $newArrayBuilder->getArray(); - } - } - - return TypeCombinator::union(...$resultTypes); - } - $arrayType = new ArrayType(new MixedType(), new MixedType()); - - if ($arrayType->isSuperTypeOf($leftType)->yes() && $arrayType->isSuperTypeOf($rightType)->yes()) { - if ($leftType->getIterableKeyType()->equals($rightType->getIterableKeyType())) { - // to preserve BenevolentUnionType - $keyType = $leftType->getIterableKeyType(); - } else { - $keyTypes = []; - foreach ([ - $leftType->getIterableKeyType(), - $rightType->getIterableKeyType(), - ] as $keyType) { - $keyTypes[] = $keyType; - } - $keyType = TypeCombinator::union(...$keyTypes); - } - return new ArrayType( - $keyType, - TypeCombinator::union($leftType->getIterableValueType(), $rightType->getIterableValueType()) - ); - } - - if ($leftType instanceof MixedType && $rightType instanceof MixedType) { - return new BenevolentUnionType([ - new FloatType(), - new IntegerType(), - new ArrayType(new MixedType(), new MixedType()), - ]); - } - } - $types = TypeCombinator::union($leftType, $rightType); if ( $leftType instanceof ArrayType diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index de90465d96..87d5a2ff20 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -495,6 +495,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4602.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4499.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2142.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5584.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/math.php'); diff --git a/tests/PHPStan/Analyser/data/bug-5584.php b/tests/PHPStan/Analyser/data/bug-5584.php new file mode 100644 index 0000000000..76632898e7 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-5584.php @@ -0,0 +1,24 @@ + 5]; + } + + $b = []; + + if (rand(0,1) === 0) { + $b = ['b' => 6]; + } + + assertType("array()|array(?'b' => 6, ?'a' => 5)", $a + $b); + } +} From bfadbdd81ae827ab7f5d76dd929994ff13bbe747 Mon Sep 17 00:00:00 2001 From: Fabien Villepinte Date: Mon, 6 Sep 2021 13:26:33 +0200 Subject: [PATCH 0210/1284] Reduce the amount of calls to hasMethodSignature() --- src/Reflection/Php/PhpClassReflectionExtension.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Reflection/Php/PhpClassReflectionExtension.php b/src/Reflection/Php/PhpClassReflectionExtension.php index b88e6dace3..223476e4c6 100644 --- a/src/Reflection/Php/PhpClassReflectionExtension.php +++ b/src/Reflection/Php/PhpClassReflectionExtension.php @@ -454,8 +454,8 @@ private function createMethod( } if ($this->signatureMapProvider->hasMethodSignature($declaringClassName, $methodReflection->getName())) { - $variantNumbers = []; - $i = 0; + $variantNumbers = [0]; + $i = 1; while ($this->signatureMapProvider->hasMethodSignature($declaringClassName, $methodReflection->getName(), $i)) { $variantNumbers[] = $i; $i++; From 92ea742968425afc38ffbe611434786039763866 Mon Sep 17 00:00:00 2001 From: howyi Date: Wed, 8 Sep 2021 20:07:39 +0900 Subject: [PATCH 0211/1284] Fix parameter definition for Grpc\ChannelCredentials::createSsl --- resources/functionMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 5d1a69eb1a..a52dbdbeeb 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -3665,7 +3665,7 @@ 'Grpc\ChannelCredentials::createComposite' => ['Grpc\ChannelCredentials', 'cred1'=>'Grpc\ChannelCredentials', 'cred2'=>'Grpc\CallCredentials'], 'Grpc\ChannelCredentials::createDefault' => ['Grpc\ChannelCredentials'], 'Grpc\ChannelCredentials::createInsecure' => ['null'], -'Grpc\ChannelCredentials::createSsl' => ['Grpc\ChannelCredentials', 'pem_root_certs'=>'string', 'pem_private_key='=>'string', 'pem_cert_chain='=>'string'], +'Grpc\ChannelCredentials::createSsl' => ['Grpc\ChannelCredentials', 'pem_root_certs='=>'string|null', 'pem_private_key='=>'string|null', 'pem_cert_chain='=>'string|null'], 'Grpc\ChannelCredentials::setDefaultRootsPem' => ['', 'pem_roots'=>'string'], 'Grpc\Server::__construct' => ['void', 'args'=>'array'], 'Grpc\Server::addHttp2Port' => ['bool', 'addr'=>'string'], From b4f81dbd777f071d78ccfc453c12e82b6f950f11 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 8 Sep 2021 13:23:49 +0200 Subject: [PATCH 0212/1284] checkExplicitMixed - replace mixed type recursively --- src/Rules/RuleLevelHelper.php | 14 +++++++++++--- .../PHPStan/Rules/Methods/ReturnTypeRuleTest.php | 16 +++++++++++++++- tests/PHPStan/Rules/Methods/data/bug-5218.php | 16 ++++++++++++++++ 3 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 tests/PHPStan/Rules/Methods/data/bug-5218.php diff --git a/src/Rules/RuleLevelHelper.php b/src/Rules/RuleLevelHelper.php index 7fc9724b81..151da55f84 100644 --- a/src/Rules/RuleLevelHelper.php +++ b/src/Rules/RuleLevelHelper.php @@ -18,6 +18,7 @@ use PHPStan\Type\StrictMixedType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; +use PHPStan\Type\TypeTraverser; use PHPStan\Type\TypeUtils; use PHPStan\Type\UnionType; @@ -60,10 +61,17 @@ public function accepts(Type $acceptingType, Type $acceptedType, bool $strictTyp { if ( $this->checkExplicitMixed - && $acceptedType instanceof MixedType - && $acceptedType->isExplicitMixed() ) { - $acceptedType = new StrictMixedType(); + $acceptedType = TypeTraverser::map($acceptedType, static function (Type $type, callable $traverse): Type { + if ( + $type instanceof MixedType + && $type->isExplicitMixed() + ) { + return new StrictMixedType(); + } + + return $traverse($type); + }); } if ( diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index cdc7ad1b48..5da2727615 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -11,9 +11,12 @@ class ReturnTypeRuleTest extends \PHPStan\Testing\RuleTestCase { + /** @var bool */ + private $checkExplicitMixed = false; + protected function getRule(): \PHPStan\Rules\Rule { - return new ReturnTypeRule(new FunctionReturnTypeCheck(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false))); + return new ReturnTypeRule(new FunctionReturnTypeCheck(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed))); } public function testReturnTypeRule(): void @@ -527,4 +530,15 @@ public function testTemplateUnion(): void ]); } + public function testBug5218(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/bug-5218.php'], [ + [ + 'Method Bug5218\IA::getIterator() should return Traversable but returns ArrayIterator.', + 14, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-5218.php b/tests/PHPStan/Rules/Methods/data/bug-5218.php new file mode 100644 index 0000000000..9388a22e07 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-5218.php @@ -0,0 +1,16 @@ + + */ +final class IA implements \IteratorAggregate +{ + /** @var array */ + private $data = []; + + public function getIterator() : \Traversable { + return new \ArrayIterator($this->data); + } +} From 6ba9ef29cb5ccd5b540815e899c95e6a1602ccad Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 8 Sep 2021 13:35:53 +0200 Subject: [PATCH 0213/1284] Fixed checkExplicitMixed with TemplateMixedType --- src/Rules/RuleLevelHelper.php | 9 ++- src/Type/Generic/TemplateMixedType.php | 12 ++++ src/Type/Generic/TemplateStrictMixedType.php | 58 +++++++++++++++++++ src/Type/StrictMixedType.php | 8 +++ .../Rules/Functions/CallCallablesRuleTest.php | 16 ++++- .../PHPStan/Rules/Functions/data/bug-3566.php | 40 +++++++++++++ .../Methods/data/check-explicit-mixed.php | 22 +++++++ 7 files changed, 162 insertions(+), 3 deletions(-) create mode 100644 src/Type/Generic/TemplateStrictMixedType.php create mode 100644 tests/PHPStan/Rules/Functions/data/bug-3566.php diff --git a/src/Rules/RuleLevelHelper.php b/src/Rules/RuleLevelHelper.php index 151da55f84..4a119f416a 100644 --- a/src/Rules/RuleLevelHelper.php +++ b/src/Rules/RuleLevelHelper.php @@ -62,7 +62,10 @@ public function accepts(Type $acceptingType, Type $acceptedType, bool $strictTyp if ( $this->checkExplicitMixed ) { - $acceptedType = TypeTraverser::map($acceptedType, static function (Type $type, callable $traverse): Type { + $traverse = static function (Type $type, callable $traverse): Type { + if ($type instanceof TemplateMixedType) { + return $type->toStrictMixedType(); + } if ( $type instanceof MixedType && $type->isExplicitMixed() @@ -71,7 +74,9 @@ public function accepts(Type $acceptingType, Type $acceptedType, bool $strictTyp } return $traverse($type); - }); + }; + $acceptingType = TypeTraverser::map($acceptingType, $traverse); + $acceptedType = TypeTraverser::map($acceptedType, $traverse); } if ( diff --git a/src/Type/Generic/TemplateMixedType.php b/src/Type/Generic/TemplateMixedType.php index 2c61b0885c..d661e2e77e 100644 --- a/src/Type/Generic/TemplateMixedType.php +++ b/src/Type/Generic/TemplateMixedType.php @@ -4,6 +4,7 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\MixedType; +use PHPStan\Type\StrictMixedType; use PHPStan\Type\Type; /** @api */ @@ -60,4 +61,15 @@ public function traverse(callable $cb): Type return $this; } + public function toStrictMixedType(): TemplateStrictMixedType + { + return new TemplateStrictMixedType( + $this->scope, + $this->strategy, + $this->variance, + $this->name, + new StrictMixedType() + ); + } + } diff --git a/src/Type/Generic/TemplateStrictMixedType.php b/src/Type/Generic/TemplateStrictMixedType.php new file mode 100644 index 0000000000..0bda962a6f --- /dev/null +++ b/src/Type/Generic/TemplateStrictMixedType.php @@ -0,0 +1,58 @@ + */ + use TemplateTypeTrait; + + public function __construct( + TemplateTypeScope $scope, + TemplateTypeStrategy $templateTypeStrategy, + TemplateTypeVariance $templateTypeVariance, + string $name, + StrictMixedType $bound + ) + { + $this->scope = $scope; + $this->strategy = $templateTypeStrategy; + $this->variance = $templateTypeVariance; + $this->name = $name; + $this->bound = $bound; + } + + public function isSuperTypeOfMixed(MixedType $type): TrinaryLogic + { + return $this->isSuperTypeOf($type); + } + + public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic + { + return $this->isSubTypeOf($acceptingType); + } + + public function traverse(callable $cb): Type + { + $newBound = $cb($this->getBound()); + if ($this->getBound() !== $newBound && $newBound instanceof MixedType) { + return new self( + $this->scope, + $this->strategy, + $this->variance, + $this->name, + $newBound + ); + } + + return $this; + } + +} diff --git a/src/Type/StrictMixedType.php b/src/Type/StrictMixedType.php index ab6b328ac1..90fbf8aebf 100644 --- a/src/Type/StrictMixedType.php +++ b/src/Type/StrictMixedType.php @@ -26,6 +26,14 @@ public function getReferencedClasses(): array public function accepts(Type $type, bool $strictTypes): TrinaryLogic { + if ($type instanceof static) { + return TrinaryLogic::createYes(); + } + + if ($type instanceof CompoundType) { + return CompoundTypeHelper::accepts($type, $this, $strictTypes); + } + return TrinaryLogic::createNo(); } diff --git a/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php b/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php index 7535f3b8c0..29bf8cddaa 100644 --- a/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php @@ -14,9 +14,12 @@ class CallCallablesRuleTest extends \PHPStan\Testing\RuleTestCase { + /** @var bool */ + private $checkExplicitMixed = false; + protected function getRule(): \PHPStan\Rules\Rule { - $ruleLevelHelper = new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false); + $ruleLevelHelper = new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed); return new CallCallablesRule( new FunctionCallParametersCheck( $ruleLevelHelper, @@ -166,4 +169,15 @@ public function testNamedArguments(): void ]); } + public function testBug3566(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/bug-3566.php'], [ + [ + 'Parameter #1 $ of closure expects int, TMemberType given.', + 29, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/bug-3566.php b/tests/PHPStan/Rules/Functions/data/bug-3566.php new file mode 100644 index 0000000000..6a015f9bfc --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-3566.php @@ -0,0 +1,40 @@ + $array + * @phpstan-param \Closure(TMemberType) : void $validator + */ + public static function validateArrayValueType(array $array, \Closure $validator) : void{ + foreach($array as $k => $v){ + try{ + $validator($v); + }catch(\TypeError $e){ + throw new \TypeError("Incorrect type of element at \"$k\": " . $e->getMessage(), 0, $e); + } + } + } + + /** + * @phpstan-template TMemberType + * @phpstan-param TMemberType $t + * @phpstan-param \Closure(int) : void $validator + */ + public static function doFoo($t, \Closure $validator) : void{ + $validator($t); + } + + /** + * @phpstan-template TMemberType + * @phpstan-param TMemberType $t + * @phpstan-param \Closure(mixed) : void $validator + */ + public static function doFoo2($t, \Closure $validator) : void{ + $validator($t); + } +} diff --git a/tests/PHPStan/Rules/Methods/data/check-explicit-mixed.php b/tests/PHPStan/Rules/Methods/data/check-explicit-mixed.php index 7d50d87121..da65ff526b 100644 --- a/tests/PHPStan/Rules/Methods/data/check-explicit-mixed.php +++ b/tests/PHPStan/Rules/Methods/data/check-explicit-mixed.php @@ -67,3 +67,25 @@ public function doLorem($t): void } } + +class TemplateMixed +{ + + /** + * @template T + * @param T $t + */ + public function doFoo($t): void + { + $this->doBar($t); + } + + /** + * @param mixed $mixed + */ + public function doBar($mixed): void + { + $this->doFoo($mixed); + } + +} From 89d91244a76607aabc89d2223450eeab2ec6833e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 8 Sep 2021 14:13:09 +0200 Subject: [PATCH 0214/1284] Fix --- src/Type/StrictMixedType.php | 10 +--------- tests/PHPStan/Rules/Methods/data/bug-5218.php | 13 +++++++++++++ 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/Type/StrictMixedType.php b/src/Type/StrictMixedType.php index 90fbf8aebf..03846473ed 100644 --- a/src/Type/StrictMixedType.php +++ b/src/Type/StrictMixedType.php @@ -26,15 +26,7 @@ public function getReferencedClasses(): array public function accepts(Type $type, bool $strictTypes): TrinaryLogic { - if ($type instanceof static) { - return TrinaryLogic::createYes(); - } - - if ($type instanceof CompoundType) { - return CompoundTypeHelper::accepts($type, $this, $strictTypes); - } - - return TrinaryLogic::createNo(); + return TrinaryLogic::createYes(); } public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic diff --git a/tests/PHPStan/Rules/Methods/data/bug-5218.php b/tests/PHPStan/Rules/Methods/data/bug-5218.php index 9388a22e07..0c2022ce5c 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-5218.php +++ b/tests/PHPStan/Rules/Methods/data/bug-5218.php @@ -14,3 +14,16 @@ public function getIterator() : \Traversable { return new \ArrayIterator($this->data); } } + +class Foo +{ + + /** + * @return mixed + */ + public function doFoo() + { + return 1; + } + +} From 5e39a94bd25380303c6c3a18343a7c3ba198d4d3 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 8 Sep 2021 14:14:16 +0200 Subject: [PATCH 0215/1284] Fix --- src/Type/Generic/TemplateStrictMixedType.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Type/Generic/TemplateStrictMixedType.php b/src/Type/Generic/TemplateStrictMixedType.php index 0bda962a6f..2159e506bb 100644 --- a/src/Type/Generic/TemplateStrictMixedType.php +++ b/src/Type/Generic/TemplateStrictMixedType.php @@ -42,7 +42,7 @@ public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLog public function traverse(callable $cb): Type { $newBound = $cb($this->getBound()); - if ($this->getBound() !== $newBound && $newBound instanceof MixedType) { + if ($this->getBound() !== $newBound && $newBound instanceof StrictMixedType) { return new self( $this->scope, $this->strategy, From 6986e0bede4e72732623ce6cd1874a9a2beac2ca Mon Sep 17 00:00:00 2001 From: Brandon Olivares Date: Wed, 8 Sep 2021 10:25:34 -0400 Subject: [PATCH 0216/1284] Make SimpleXMLElement stub more specific --- stubs/iterable.stub | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/stubs/iterable.stub b/stubs/iterable.stub index cbcba7b8a5..4e42965e80 100644 --- a/stubs/iterable.stub +++ b/stubs/iterable.stub @@ -81,10 +81,10 @@ class Generator implements Iterator } /** - * @implements Traversable - * @implements ArrayAccess - * @implements Iterator - * @implements RecursiveIterator + * @implements Traversable + * @implements ArrayAccess + * @implements Iterator + * @implements RecursiveIterator */ class SimpleXMLElement implements Traversable, ArrayAccess, Iterator, RecursiveIterator { From 2d6d95f5e665b7dd159df6c32295c16e32afb858 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 8 Sep 2021 19:06:06 +0200 Subject: [PATCH 0217/1284] Test with and without checkExplicitMixed --- .../Rules/Functions/CallCallablesRuleTest.php | 31 +++++++-- .../Rules/Methods/CallMethodsRuleTest.php | 61 ++++++++++++----- .../Rules/Methods/ReturnTypeRuleTest.php | 31 +++++++-- .../Methods/data/check-explicit-mixed.php | 66 ++++++++++++++++++- 4 files changed, 161 insertions(+), 28 deletions(-) diff --git a/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php b/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php index 29bf8cddaa..9ec1a0f40a 100644 --- a/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php @@ -169,15 +169,34 @@ public function testNamedArguments(): void ]); } - public function testBug3566(): void + public function dataBug3566(): array { - $this->checkExplicitMixed = true; - $this->analyse([__DIR__ . '/data/bug-3566.php'], [ + return [ [ - 'Parameter #1 $ of closure expects int, TMemberType given.', - 29, + true, + [ + [ + 'Parameter #1 $ of closure expects int, TMemberType given.', + 29, + ], + ], ], - ]); + [ + false, + [], + ], + ]; + } + + /** + * @dataProvider dataBug3566 + * @param bool $checkExplicitMixed + * @param mixed[] $errors + */ + public function testBug3566(bool $checkExplicitMixed, array $errors): void + { + $this->checkExplicitMixed = $checkExplicitMixed; + $this->analyse([__DIR__ . '/data/bug-3566.php'], $errors); } } diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 842737f589..644a61c0df 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -1431,26 +1431,57 @@ public function testShadowedTraitMethod(): void $this->analyse([__DIR__ . '/data/shadowed-trait-method.php'], []); } - public function testExplicitMixed(): void + public function dataExplicitMixed(): array { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->checkExplicitMixed = true; - $this->analyse([__DIR__ . '/data/check-explicit-mixed.php'], [ - [ - 'Cannot call method foo() on mixed.', - 17, - ], + return [ [ - 'Parameter #1 $i of method CheckExplicitMixedMethodCall\Bar::doBar() expects int, mixed given.', - 43, + true, + [ + [ + 'Cannot call method foo() on mixed.', + 17, + ], + [ + 'Parameter #1 $i of method CheckExplicitMixedMethodCall\Bar::doBar() expects int, mixed given.', + 43, + ], + [ + 'Parameter #1 $i of method CheckExplicitMixedMethodCall\Bar::doBar() expects int, T given.', + 65, + ], + [ + 'Parameter #1 $cb of method CheckExplicitMixedMethodCall\CallableMixed::doFoo() expects callable(mixed): void, Closure(int): void given.', + 133, + ], + [ + 'Parameter #1 $cb of method CheckExplicitMixedMethodCall\CallableMixed::doBar2() expects callable(): int, Closure(): mixed given.', + 152, + ], + ], ], [ - 'Parameter #1 $i of method CheckExplicitMixedMethodCall\Bar::doBar() expects int, T given.', - 65, + false, + [], ], - ]); + ]; + } + + /** + * @dataProvider dataExplicitMixed + * @param bool $checkExplicitMixed + * @param mixed[] $errors + */ + public function testExplicitMixed(bool $checkExplicitMixed, array $errors): void + { + if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->checkExplicitMixed = $checkExplicitMixed; + $this->analyse([__DIR__ . '/data/check-explicit-mixed.php'], $errors); } public function testBug3409(): void diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index 5da2727615..6b1bcde98e 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -530,15 +530,34 @@ public function testTemplateUnion(): void ]); } - public function testBug5218(): void + public function dataBug5218(): array { - $this->checkExplicitMixed = true; - $this->analyse([__DIR__ . '/data/bug-5218.php'], [ + return [ [ - 'Method Bug5218\IA::getIterator() should return Traversable but returns ArrayIterator.', - 14, + true, + [ + [ + 'Method Bug5218\IA::getIterator() should return Traversable but returns ArrayIterator.', + 14, + ], + ], ], - ]); + [ + false, + [], + ], + ]; + } + + /** + * @dataProvider dataBug5218 + * @param bool $checkExplicitMixed + * @param array $errors + */ + public function testBug5218(bool $checkExplicitMixed, array $errors): void + { + $this->checkExplicitMixed = $checkExplicitMixed; + $this->analyse([__DIR__ . '/data/bug-5218.php'], $errors); } } diff --git a/tests/PHPStan/Rules/Methods/data/check-explicit-mixed.php b/tests/PHPStan/Rules/Methods/data/check-explicit-mixed.php index da65ff526b..9cf96eacce 100644 --- a/tests/PHPStan/Rules/Methods/data/check-explicit-mixed.php +++ b/tests/PHPStan/Rules/Methods/data/check-explicit-mixed.php @@ -1,4 +1,4 @@ -= 8.0 namespace CheckExplicitMixedMethodCall; @@ -89,3 +89,67 @@ public function doBar($mixed): void } } + +class CallableMixed +{ + + /** + * @param callable(mixed): void $cb + */ + public function doFoo(callable $cb): void + { + + } + + /** + * @param callable(int): void $cb + */ + public function doBar(callable $cb): void + { + + } + + /** + * @param callable(): mixed $cb + */ + public function doFoo2(callable $cb): void + { + + } + + /** + * @param callable(): int $cb + */ + public function doBar2(callable $cb): void + { + + } + + public function doLorem(int $i, mixed $m): void + { + $acceptsInt = function (int $i): void { + + }; + $this->doFoo($acceptsInt); + $this->doBar($acceptsInt); + + $acceptsMixed = function (mixed $m): void { + + }; + $this->doFoo($acceptsMixed); + $this->doBar($acceptsMixed); + + $returnsInt = function () use ($i): int { + return $i; + }; + $this->doFoo2($returnsInt); + $this->doBar2($returnsInt); + + $returnsMixed = function () use ($m): mixed { + return $m; + }; + $this->doFoo2($returnsMixed); + $this->doBar2($returnsMixed); + } + +} From 1eee0dbb3b82280e7cb34e71141b6ed98adf3962 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 8 Sep 2021 19:30:38 +0200 Subject: [PATCH 0218/1284] Fix --- tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index 6b1bcde98e..5092659b10 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -552,7 +552,7 @@ public function dataBug5218(): array /** * @dataProvider dataBug5218 * @param bool $checkExplicitMixed - * @param array $errors + * @param mixed[] $errors */ public function testBug5218(bool $checkExplicitMixed, array $errors): void { From 1ef74b7ab7c6128166218db4bb10e7dad663d744 Mon Sep 17 00:00:00 2001 From: Dylan T Date: Thu, 9 Sep 2021 16:52:15 +0100 Subject: [PATCH 0219/1284] BaselineNeonErrorFormatter: Sort errors by normalized relative path) --- .../BaselineNeonErrorFormatter.php | 4 ++-- src/File/SimpleRelativePathHelper.php | 4 ++-- .../BaselineNeonErrorFormatterTest.php | 18 ++++++++++++++++++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/Command/ErrorFormatter/BaselineNeonErrorFormatter.php b/src/Command/ErrorFormatter/BaselineNeonErrorFormatter.php index f819dd664f..33a308aa96 100644 --- a/src/Command/ErrorFormatter/BaselineNeonErrorFormatter.php +++ b/src/Command/ErrorFormatter/BaselineNeonErrorFormatter.php @@ -40,7 +40,7 @@ public function formatErrors( if (!$fileSpecificError->canBeIgnored()) { continue; } - $fileErrors[$fileSpecificError->getFilePath()][] = $fileSpecificError->getMessage(); + $fileErrors[$this->relativePathHelper->getRelativePath($fileSpecificError->getFilePath())][] = $fileSpecificError->getMessage(); } ksort($fileErrors, SORT_STRING); @@ -61,7 +61,7 @@ public function formatErrors( $errorsToOutput[] = [ 'message' => Helpers::escape('#^' . preg_quote($message, '#') . '$#'), 'count' => $count, - 'path' => Helpers::escape($this->relativePathHelper->getRelativePath($file)), + 'path' => Helpers::escape($file), ]; } } diff --git a/src/File/SimpleRelativePathHelper.php b/src/File/SimpleRelativePathHelper.php index f71341deeb..a0633a00fa 100644 --- a/src/File/SimpleRelativePathHelper.php +++ b/src/File/SimpleRelativePathHelper.php @@ -15,10 +15,10 @@ public function __construct(string $currentWorkingDirectory) public function getRelativePath(string $filename): string { if ($this->currentWorkingDirectory !== '' && strpos($filename, $this->currentWorkingDirectory) === 0) { - return substr($filename, strlen($this->currentWorkingDirectory) + 1); + return str_replace('\\', '/', substr($filename, strlen($this->currentWorkingDirectory) + 1)); } - return $filename; + return str_replace('\\', '/', $filename); } } diff --git a/tests/PHPStan/Command/ErrorFormatter/BaselineNeonErrorFormatterTest.php b/tests/PHPStan/Command/ErrorFormatter/BaselineNeonErrorFormatterTest.php index d8a7bbe732..49890696cb 100644 --- a/tests/PHPStan/Command/ErrorFormatter/BaselineNeonErrorFormatterTest.php +++ b/tests/PHPStan/Command/ErrorFormatter/BaselineNeonErrorFormatterTest.php @@ -207,6 +207,9 @@ public function outputOrderingProvider(): \Generator new Error('Second error in a different file', 'TestfileB', 4), new Error('Error #1 in a different file', 'TestfileB', 5), new Error('Second error in a different file', 'TestfileB', 6), + new Error('Error with Windows directory separators', 'TestFiles\\TestA', 1), + new Error('Error with Unix directory separators', 'TestFiles/TestA', 1), + new Error('Error without directory separators', 'TestFilesFoo', 1), ]; yield [$errors]; mt_srand(0); @@ -241,6 +244,21 @@ public function testOutputOrdering(array $errors): void trim(Neon::encode([ 'parameters' => [ 'ignoreErrors' => [ + [ + 'message' => '#^Error with Unix directory separators$#', + 'count' => 1, + 'path' => 'TestFiles/TestA', + ], + [ + 'message' => '#^Error with Windows directory separators$#', + 'count' => 1, + 'path' => 'TestFiles/TestA', + ], + [ + 'message' => '#^Error without directory separators$#', + 'count' => 1, + 'path' => 'TestFilesFoo', + ], [ 'message' => '#^A different error \\#1$#', 'count' => 1, From 601460ccecfd72888d96e9c53d21cc3a8b66c719 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 10 Sep 2021 09:38:30 +0200 Subject: [PATCH 0220/1284] Bleeding edge - empty() rule --- conf/config.level4.neon | 5 ++ src/Rules/IssetCheck.php | 43 +++++----- src/Rules/Variables/EmptyRule.php | 78 +++++++++++++++++++ src/Rules/Variables/IssetRule.php | 15 +++- src/Rules/Variables/NullCoalesceRule.php | 19 ++++- .../PHPStan/Rules/Variables/EmptyRuleTest.php | 51 ++++++++++++ .../Rules/Variables/data/empty-rule.php | 42 ++++++++++ 7 files changed, 228 insertions(+), 25 deletions(-) create mode 100644 src/Rules/Variables/EmptyRule.php create mode 100644 tests/PHPStan/Rules/Variables/EmptyRuleTest.php create mode 100644 tests/PHPStan/Rules/Variables/data/empty-rule.php diff --git a/conf/config.level4.neon b/conf/config.level4.neon index 86c6f528c0..9d731fe913 100644 --- a/conf/config.level4.neon +++ b/conf/config.level4.neon @@ -28,6 +28,8 @@ conditionalTags: phpstan.rules.rule: %featureToggles.unusedClassElements% PHPStan\Rules\DeadCode\UnusedPrivatePropertyRule: phpstan.rules.rule: %featureToggles.unusedClassElements% + PHPStan\Rules\Variables\EmptyRule: + phpstan.rules.rule: %featureToggles.nullCoalesce% PHPStan\Rules\Variables\IssetRule: phpstan.rules.rule: %featureToggles.nullCoalesce% PHPStan\Rules\Variables\NullCoalesceRule: @@ -164,6 +166,9 @@ services: tags: - phpstan.rules.rule + - + class: PHPStan\Rules\Variables\EmptyRule + - class: PHPStan\Rules\Variables\IssetRule diff --git a/src/Rules/IssetCheck.php b/src/Rules/IssetCheck.php index 6cfb74c7a9..e737ee9cc7 100644 --- a/src/Rules/IssetCheck.php +++ b/src/Rules/IssetCheck.php @@ -8,7 +8,6 @@ use PHPStan\Rules\Properties\PropertyDescriptor; use PHPStan\Rules\Properties\PropertyReflectionFinder; use PHPStan\Type\MixedType; -use PHPStan\Type\NullType; use PHPStan\Type\Type; use PHPStan\Type\VerbosityLevel; @@ -28,7 +27,10 @@ public function __construct( $this->propertyReflectionFinder = $propertyReflectionFinder; } - public function check(Expr $expr, Scope $scope, string $operatorDescription, ?RuleError $error = null): ?RuleError + /** + * @param callable(Type): ?string $typeMessageCallback + */ + public function check(Expr $expr, Scope $scope, string $operatorDescription, callable $typeMessageCallback, ?RuleError $error = null): ?RuleError { if ($expr instanceof Node\Expr\Variable && is_string($expr->name)) { $hasVariable = $scope->hasVariableType($expr->name); @@ -70,10 +72,10 @@ public function check(Expr $expr, Scope $scope, string $operatorDescription, ?Ru $dimType->describe(VerbosityLevel::value()), $type->describe(VerbosityLevel::value()), $operatorDescription - )); + ), $typeMessageCallback); if ($error !== null) { - return $this->check($expr->var, $scope, $operatorDescription, $error); + return $this->check($expr->var, $scope, $operatorDescription, $typeMessageCallback, $error); } } @@ -104,42 +106,39 @@ public function check(Expr $expr, Scope $scope, string $operatorDescription, ?Ru $error = $error ?? $this->generateError( $propertyReflection->getWritableType(), - sprintf('%s (%s) %s', $propertyDescription, $propertyType->describe(VerbosityLevel::typeOnly()), $operatorDescription) + sprintf('%s (%s) %s', $propertyDescription, $propertyType->describe(VerbosityLevel::typeOnly()), $operatorDescription), + $typeMessageCallback ); if ($error !== null) { if ($expr instanceof Node\Expr\PropertyFetch) { - return $this->check($expr->var, $scope, $operatorDescription, $error); + return $this->check($expr->var, $scope, $operatorDescription, $typeMessageCallback, $error); } if ($expr->class instanceof Expr) { - return $this->check($expr->class, $scope, $operatorDescription, $error); + return $this->check($expr->class, $scope, $operatorDescription, $typeMessageCallback, $error); } } return $error; } - return $error ?? $this->generateError($scope->getType($expr), sprintf('Expression %s', $operatorDescription)); + return $error ?? $this->generateError($scope->getType($expr), sprintf('Expression %s', $operatorDescription), $typeMessageCallback); } - private function generateError(Type $type, string $message): ?RuleError + /** + * @param callable(Type): ?string $typeMessageCallback + */ + private function generateError(Type $type, string $message, callable $typeMessageCallback): ?RuleError { - $nullType = new NullType(); - - if ($type->equals($nullType)) { - return RuleErrorBuilder::message( - sprintf('%s is always null.', $message) - )->build(); - } - - if ($type->isSuperTypeOf($nullType)->no()) { - return RuleErrorBuilder::message( - sprintf('%s is not nullable.', $message) - )->build(); + $typeMessage = $typeMessageCallback($type); + if ($typeMessage === null) { + return null; } - return null; + return RuleErrorBuilder::message( + sprintf('%s %s.', $message, $typeMessage) + )->build(); } } diff --git a/src/Rules/Variables/EmptyRule.php b/src/Rules/Variables/EmptyRule.php new file mode 100644 index 0000000000..ba0911ac44 --- /dev/null +++ b/src/Rules/Variables/EmptyRule.php @@ -0,0 +1,78 @@ + + */ +class EmptyRule implements \PHPStan\Rules\Rule +{ + + private IssetCheck $issetCheck; + + public function __construct(IssetCheck $issetCheck) + { + $this->issetCheck = $issetCheck; + } + + public function getNodeType(): string + { + return Node\Expr\Empty_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $error = $this->issetCheck->check($node->expr, $scope, 'in empty()', static function (Type $type): ?string { + $isNull = (new NullType())->isSuperTypeOf($type); + $isFalsey = (new ConstantBooleanType(false))->isSuperTypeOf($type->toBoolean()); + if ($isNull->maybe()) { + return null; + } + if ($isFalsey->maybe()) { + return null; + } + + if ($isNull->yes()) { + if ($isFalsey->yes()) { + return 'is always falsy'; + } + if ($isFalsey->no()) { + return 'is not falsy'; + } + + return 'is always null'; + } + + if ($isFalsey->yes()) { + return 'is always falsy'; + } + + if ($isFalsey->no()) { + return 'is not falsy'; + } + + return 'is not nullable'; + }); + if ($error === null) { + return []; + } + + $exprType = $scope->getType($node->expr); + $exprBooleanType = $exprType->toBoolean(); + $isFalse = (new ConstantBooleanType(false))->isSuperTypeOf($exprBooleanType); + if (!$exprType instanceof ErrorType && $isFalse->maybe()) { + return []; + } + + return [$error]; + } + +} diff --git a/src/Rules/Variables/IssetRule.php b/src/Rules/Variables/IssetRule.php index 78ab199e97..c9654bd63d 100644 --- a/src/Rules/Variables/IssetRule.php +++ b/src/Rules/Variables/IssetRule.php @@ -5,6 +5,8 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; use PHPStan\Rules\IssetCheck; +use PHPStan\Type\NullType; +use PHPStan\Type\Type; /** * @implements \PHPStan\Rules\Rule @@ -28,7 +30,18 @@ public function processNode(Node $node, Scope $scope): array { $messages = []; foreach ($node->vars as $var) { - $error = $this->issetCheck->check($var, $scope, 'in isset()'); + $error = $this->issetCheck->check($var, $scope, 'in isset()', static function (Type $type): ?string { + $isNull = (new NullType())->isSuperTypeOf($type); + if ($isNull->maybe()) { + return null; + } + + if ($isNull->yes()) { + return 'is always null'; + } + + return 'is not nullable'; + }); if ($error === null) { continue; } diff --git a/src/Rules/Variables/NullCoalesceRule.php b/src/Rules/Variables/NullCoalesceRule.php index 5c6790e3f8..144e07932c 100644 --- a/src/Rules/Variables/NullCoalesceRule.php +++ b/src/Rules/Variables/NullCoalesceRule.php @@ -5,6 +5,8 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; use PHPStan\Rules\IssetCheck; +use PHPStan\Type\NullType; +use PHPStan\Type\Type; /** * @implements \PHPStan\Rules\Rule<\PhpParser\Node\Expr> @@ -26,10 +28,23 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { + $typeMessageCallback = static function (Type $type): ?string { + $isNull = (new NullType())->isSuperTypeOf($type); + if ($isNull->maybe()) { + return null; + } + + if ($isNull->yes()) { + return 'is always null'; + } + + return 'is not nullable'; + }; + if ($node instanceof Node\Expr\BinaryOp\Coalesce) { - $error = $this->issetCheck->check($node->left, $scope, 'on left side of ??'); + $error = $this->issetCheck->check($node->left, $scope, 'on left side of ??', $typeMessageCallback); } elseif ($node instanceof Node\Expr\AssignOp\Coalesce) { - $error = $this->issetCheck->check($node->var, $scope, 'on left side of ??='); + $error = $this->issetCheck->check($node->var, $scope, 'on left side of ??=', $typeMessageCallback); } else { return []; } diff --git a/tests/PHPStan/Rules/Variables/EmptyRuleTest.php b/tests/PHPStan/Rules/Variables/EmptyRuleTest.php new file mode 100644 index 0000000000..af2206ea2d --- /dev/null +++ b/tests/PHPStan/Rules/Variables/EmptyRuleTest.php @@ -0,0 +1,51 @@ + + */ +class EmptyRuleTest extends RuleTestCase +{ + + protected function getRule(): \PHPStan\Rules\Rule + { + return new EmptyRule(new IssetCheck(new PropertyDescriptor(), new PropertyReflectionFinder())); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/empty-rule.php'], [ + [ + 'Offset \'nonexistent\' on array(?0 => bool, ?1 => false, 2 => bool, 3 => false, 4 => true) in empty() does not exist.', + 22, + ], + [ + 'Offset 3 on array(?0 => bool, ?1 => false, 2 => bool, 3 => false, 4 => true) in empty() always exists and is always falsy.', + 24, + ], + [ + 'Offset 4 on array(?0 => bool, ?1 => false, 2 => bool, 3 => false, 4 => true) in empty() always exists and is not falsy.', + 25, + ], + [ + 'Offset 0 on array(\'\', \'0\', \'foo\', \'\'|\'foo\') in empty() always exists and is always falsy.', + 36, + ], + [ + 'Offset 1 on array(\'\', \'0\', \'foo\', \'\'|\'foo\') in empty() always exists and is always falsy.', + 37, + ], + [ + 'Offset 2 on array(\'\', \'0\', \'foo\', \'\'|\'foo\') in empty() always exists and is not falsy.', + 38, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Variables/data/empty-rule.php b/tests/PHPStan/Rules/Variables/data/empty-rule.php new file mode 100644 index 0000000000..26e31985fc --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/empty-rule.php @@ -0,0 +1,42 @@ + Date: Fri, 10 Sep 2021 13:31:42 +0200 Subject: [PATCH 0221/1284] Bleeding edge - teach IssetRule everything what VariableCertaintyInIssetRule does --- conf/config.level1.neon | 12 +- conf/config.neon | 2 + src/Rules/IssetCheck.php | 25 +++- src/Rules/Variables/EmptyRule.php | 9 +- .../VariableCertaintyInIssetRule.php | 11 ++ .../VariableCertaintyNullCoalesceRule.php | 81 ------------- tests/PHPStan/Levels/data/coalesce.php | 12 ++ .../PHPStan/Rules/Variables/EmptyRuleTest.php | 20 +++- .../PHPStan/Rules/Variables/IssetRuleTest.php | 107 +++++++++++++++++- .../Rules/Variables/NullCoalesceRuleTest.php | 76 ++++++++++++- .../VariableCertaintyNullCoalesceRuleTest.php | 66 ----------- .../PHPStan/Rules/Variables/data/bug-970.php | 14 +++ .../Rules/Variables/data/empty-rule.php | 17 +++ 13 files changed, 286 insertions(+), 166 deletions(-) delete mode 100644 src/Rules/Variables/VariableCertaintyNullCoalesceRule.php delete mode 100644 tests/PHPStan/Rules/Variables/VariableCertaintyNullCoalesceRuleTest.php create mode 100644 tests/PHPStan/Rules/Variables/data/bug-970.php diff --git a/conf/config.level1.neon b/conf/config.level1.neon index 2499484180..99006e0133 100644 --- a/conf/config.level1.neon +++ b/conf/config.level1.neon @@ -7,17 +7,15 @@ parameters: reportMagicMethods: true reportMagicProperties: true - -conditionalTags: - PHPStan\Rules\Variables\VariableCertaintyNullCoalesceRule: - phpstan.rules.rule: %featureToggles.nullCoalesce% - rules: - PHPStan\Rules\Classes\UnusedConstructorParametersRule - PHPStan\Rules\Constants\ConstantRule - PHPStan\Rules\Functions\UnusedClosureUsesRule - - PHPStan\Rules\Variables\VariableCertaintyInIssetRule services: - - class: PHPStan\Rules\Variables\VariableCertaintyNullCoalesceRule + class: PHPStan\Rules\Variables\VariableCertaintyInIssetRule + arguments: + bleedingEdge: %featureToggles.bleedingEdge% + tags: + - phpstan.rules.rule diff --git a/conf/config.neon b/conf/config.neon index 3c183f59bf..6236f4d147 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -859,6 +859,8 @@ services: - class: PHPStan\Rules\IssetCheck + arguments: + bleedingEdge: %featureToggles.bleedingEdge% - # checked as part of OverridingMethodRule diff --git a/src/Rules/IssetCheck.php b/src/Rules/IssetCheck.php index e737ee9cc7..47e44e49d3 100644 --- a/src/Rules/IssetCheck.php +++ b/src/Rules/IssetCheck.php @@ -18,13 +18,17 @@ class IssetCheck private \PHPStan\Rules\Properties\PropertyReflectionFinder $propertyReflectionFinder; + private bool $bleedingEdge; + public function __construct( PropertyDescriptor $propertyDescriptor, - PropertyReflectionFinder $propertyReflectionFinder + PropertyReflectionFinder $propertyReflectionFinder, + bool $bleedingEdge = false ) { $this->propertyDescriptor = $propertyDescriptor; $this->propertyReflectionFinder = $propertyReflectionFinder; + $this->bleedingEdge = $bleedingEdge; } /** @@ -38,6 +42,25 @@ public function check(Expr $expr, Scope $scope, string $operatorDescription, cal return null; } + if ( + $error === null + && $this->bleedingEdge + ) { + if ($hasVariable->yes()) { + if ($expr->name === '_SESSION') { + return null; + } + + return $this->generateError( + $scope->getVariableType($expr->name), + sprintf('Variable $%s %s always exists and', $expr->name, $operatorDescription), + $typeMessageCallback + ); + } + + return RuleErrorBuilder::message(sprintf('Variable $%s %s is never defined.', $expr->name, $operatorDescription))->build(); + } + return $error; } elseif ($expr instanceof Node\Expr\ArrayDimFetch && $expr->dim !== null) { diff --git a/src/Rules/Variables/EmptyRule.php b/src/Rules/Variables/EmptyRule.php index ba0911ac44..ab9ce3c38d 100644 --- a/src/Rules/Variables/EmptyRule.php +++ b/src/Rules/Variables/EmptyRule.php @@ -6,7 +6,6 @@ use PHPStan\Analyser\Scope; use PHPStan\Rules\IssetCheck; use PHPStan\Type\Constant\ConstantBooleanType; -use PHPStan\Type\ErrorType; use PHPStan\Type\NullType; use PHPStan\Type\Type; @@ -61,14 +60,8 @@ public function processNode(Node $node, Scope $scope): array return 'is not nullable'; }); - if ($error === null) { - return []; - } - $exprType = $scope->getType($node->expr); - $exprBooleanType = $exprType->toBoolean(); - $isFalse = (new ConstantBooleanType(false))->isSuperTypeOf($exprBooleanType); - if (!$exprType instanceof ErrorType && $isFalse->maybe()) { + if ($error === null) { return []; } diff --git a/src/Rules/Variables/VariableCertaintyInIssetRule.php b/src/Rules/Variables/VariableCertaintyInIssetRule.php index 242951798d..d8226c4a74 100644 --- a/src/Rules/Variables/VariableCertaintyInIssetRule.php +++ b/src/Rules/Variables/VariableCertaintyInIssetRule.php @@ -13,6 +13,13 @@ class VariableCertaintyInIssetRule implements \PHPStan\Rules\Rule { + private bool $bleedingEdge; + + public function __construct(bool $bleedingEdge = false) + { + $this->bleedingEdge = $bleedingEdge; + } + public function getNodeType(): string { return Node\Expr\Isset_::class; @@ -20,6 +27,10 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { + if ($this->bleedingEdge) { + return []; + } + $messages = []; foreach ($node->vars as $var) { $isSubNode = false; diff --git a/src/Rules/Variables/VariableCertaintyNullCoalesceRule.php b/src/Rules/Variables/VariableCertaintyNullCoalesceRule.php deleted file mode 100644 index 5a8b9e1323..0000000000 --- a/src/Rules/Variables/VariableCertaintyNullCoalesceRule.php +++ /dev/null @@ -1,81 +0,0 @@ - - */ -class VariableCertaintyNullCoalesceRule implements \PHPStan\Rules\Rule -{ - - public function getNodeType(): string - { - return Node\Expr::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if ($node instanceof Node\Expr\AssignOp\Coalesce) { - $var = $node->var; - $description = '??='; - } elseif ($node instanceof Node\Expr\BinaryOp\Coalesce) { - $var = $node->left; - $description = '??'; - } else { - return []; - } - - $isSubNode = false; - while ( - $var instanceof Node\Expr\ArrayDimFetch - || $var instanceof Node\Expr\PropertyFetch - || ( - $var instanceof Node\Expr\StaticPropertyFetch - && $var->class instanceof Node\Expr - ) - ) { - if ($var instanceof Node\Expr\StaticPropertyFetch) { - $var = $var->class; - } else { - $var = $var->var; - } - $isSubNode = true; - } - - if (!$var instanceof Node\Expr\Variable || !is_string($var->name)) { - return []; - } - - $certainty = $scope->hasVariableType($var->name); - if ($certainty->no()) { - return [RuleErrorBuilder::message(sprintf( - 'Variable $%s on left side of %s is never defined.', - $var->name, - $description - ))->build()]; - } elseif ($certainty->yes() && !$isSubNode) { - $variableType = $scope->getVariableType($var->name); - if ($variableType->isSuperTypeOf(new NullType())->no()) { - return [RuleErrorBuilder::message(sprintf( - 'Variable $%s on left side of %s always exists and is not nullable.', - $var->name, - $description - ))->build()]; - } elseif ((new NullType())->isSuperTypeOf($variableType)->yes()) { - return [RuleErrorBuilder::message(sprintf( - 'Variable $%s on left side of %s is always null.', - $var->name, - $description - ))->build()]; - } - } - - return []; - } - -} diff --git a/tests/PHPStan/Levels/data/coalesce.php b/tests/PHPStan/Levels/data/coalesce.php index 6010637b0a..fffbf433ca 100644 --- a/tests/PHPStan/Levels/data/coalesce.php +++ b/tests/PHPStan/Levels/data/coalesce.php @@ -10,3 +10,15 @@ function (\ReflectionClass $ref): void { echo $ref->name ?? 'foo'; echo $ref->nonexistent ?? 'bar'; }; + +function (?string $s): void { + echo $a ?? 'foo'; + + echo $s ?? 'bar'; + + if ($s !== null) { + return; + } + + echo $s ?? 'bar'; +}; diff --git a/tests/PHPStan/Rules/Variables/EmptyRuleTest.php b/tests/PHPStan/Rules/Variables/EmptyRuleTest.php index af2206ea2d..bfd6cdefdb 100644 --- a/tests/PHPStan/Rules/Variables/EmptyRuleTest.php +++ b/tests/PHPStan/Rules/Variables/EmptyRuleTest.php @@ -15,7 +15,7 @@ class EmptyRuleTest extends RuleTestCase protected function getRule(): \PHPStan\Rules\Rule { - return new EmptyRule(new IssetCheck(new PropertyDescriptor(), new PropertyReflectionFinder())); + return new EmptyRule(new IssetCheck(new PropertyDescriptor(), new PropertyReflectionFinder(), true)); } public function testRule(): void @@ -45,6 +45,24 @@ public function testRule(): void 'Offset 2 on array(\'\', \'0\', \'foo\', \'\'|\'foo\') in empty() always exists and is not falsy.', 38, ], + [ + 'Variable $a in empty() is never defined.', + 44, + ], + [ + 'Variable $b in empty() always exists and is not falsy.', + 47, + ], + ]); + } + + public function testBug970(): void + { + $this->analyse([__DIR__ . '/data/bug-970.php'], [ + [ + 'aaa', + 10, + ], ]); } diff --git a/tests/PHPStan/Rules/Variables/IssetRuleTest.php b/tests/PHPStan/Rules/Variables/IssetRuleTest.php index 7e4ee5150a..5c20822123 100644 --- a/tests/PHPStan/Rules/Variables/IssetRuleTest.php +++ b/tests/PHPStan/Rules/Variables/IssetRuleTest.php @@ -16,7 +16,7 @@ class IssetRuleTest extends RuleTestCase protected function getRule(): Rule { - return new IssetRule(new IssetCheck(new PropertyDescriptor(), new PropertyReflectionFinder())); + return new IssetRule(new IssetCheck(new PropertyDescriptor(), new PropertyReflectionFinder(), true)); } public function testRule(): void @@ -26,6 +26,10 @@ public function testRule(): void 'Property IssetRule\FooCoalesce::$string (string) in isset() is not nullable.', 32, ], + [ + 'Variable $scalar in isset() always exists and is not nullable.', + 41, + ], [ 'Offset \'string\' on array(1, 2, 3) in isset() does not exist.', 45, @@ -34,6 +38,10 @@ public function testRule(): void 'Offset \'string\' on array(array(1), array(2), array(3)) in isset() does not exist.', 49, ], + [ + 'Variable $doesNotExist in isset() is never defined.', + 51, + ], [ 'Offset \'dim\' on array(\'dim\' => 1, \'dim-null\' => 1|null, \'dim-null-offset\' => array(\'a\' => true|null), \'dim-empty\' => array()) in isset() always exists and is not nullable.', 67, @@ -62,6 +70,10 @@ public function testRule(): void 'Static property IssetRule\FooCoalesce::$staticAlwaysNull (null) in isset() is always null.', 97, ], + [ + 'Variable $a in isset() always exists and is always null.', + 111, + ], [ 'Property IssetRule\FooCoalesce::$string (string) in isset() is not nullable.', 116, @@ -114,4 +126,97 @@ public function testBug4671(): void ]]); } + public function testVariableCertaintyInIsset(): void + { + $this->analyse([__DIR__ . '/data/variable-certainty-isset.php'], [ + [ + 'Variable $alwaysDefinedNotNullable in isset() always exists and is not nullable.', + 14, + ], + [ + 'Variable $neverDefinedVariable in isset() is never defined.', + 22, + ], + [ + 'Variable $anotherNeverDefinedVariable in isset() is never defined.', + 42, + ], + [ + 'Variable $yetAnotherNeverDefinedVariable in isset() is never defined.', + 46, + ], + [ + 'Variable $yetYetAnotherNeverDefinedVariableInIsset in isset() is never defined.', + 56, + ], + [ + 'Variable $anotherVariableInDoWhile in isset() always exists and is not nullable.', + 104, + ], + [ + 'Variable $variableInSecondCase in isset() is never defined.', + 110, + ], + [ + 'Variable $variableInFirstCase in isset() always exists and is not nullable.', + 112, + ], + [ + 'Variable $variableInFirstCase in isset() always exists and is not nullable.', + 116, + ], + [ + 'Variable $variableInSecondCase in isset() always exists and is not nullable.', + 117, + ], + [ + 'Variable $variableAssignedInSecondCase in isset() is never defined.', + 119, + ], + [ + 'Variable $alwaysDefinedForSwitchCondition in isset() always exists and is not nullable.', + 139, + ], + [ + 'Variable $alwaysDefinedForCaseNodeCondition in isset() always exists and is not nullable.', + 140, + ], + [ + 'Variable $alwaysDefinedNotNullable in isset() always exists and is not nullable.', + 152, + ], + [ + 'Variable $neverDefinedVariable in isset() is never defined.', + 152, + ], + [ + 'Variable $a in isset() always exists and is not nullable.', + 214, + ], + [ + 'Variable $null in isset() always exists and is always null.', + 225, + ], + ]); + } + + public function testIssetInGlobalScope(): void + { + $this->analyse([__DIR__ . '/data/isset-global-scope.php'], [ + [ + 'Variable $alwaysDefinedNotNullable in isset() always exists and is not nullable.', + 8, + ], + ]); + } + + public function testNullsafe(): void + { + if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->analyse([__DIR__ . '/data/isset-nullsafe.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php b/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php index 681ee02a24..33636a032c 100644 --- a/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php +++ b/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php @@ -14,7 +14,7 @@ class NullCoalesceRuleTest extends \PHPStan\Testing\RuleTestCase protected function getRule(): \PHPStan\Rules\Rule { - return new NullCoalesceRule(new IssetCheck(new PropertyDescriptor(), new PropertyReflectionFinder())); + return new NullCoalesceRule(new IssetCheck(new PropertyDescriptor(), new PropertyReflectionFinder(), true)); } public function testCoalesceRule(): void @@ -24,6 +24,10 @@ public function testCoalesceRule(): void 'Property CoalesceRule\FooCoalesce::$string (string) on left side of ?? is not nullable.', 32, ], + [ + 'Variable $scalar on left side of ?? always exists and is not nullable.', + 41, + ], [ 'Offset \'string\' on array(1, 2, 3) on left side of ?? does not exist.', 45, @@ -32,6 +36,10 @@ public function testCoalesceRule(): void 'Offset \'string\' on array(array(1), array(2), array(3)) on left side of ?? does not exist.', 49, ], + [ + 'Variable $doesNotExist on left side of ?? is never defined.', + 51, + ], [ 'Offset \'dim\' on array(\'dim\' => 1, \'dim-null\' => 1|null, \'dim-null-offset\' => array(\'a\' => true|null), \'dim-empty\' => array()) on left side of ?? always exists and is not nullable.', 67, @@ -64,6 +72,10 @@ public function testCoalesceRule(): void 'Static property CoalesceRule\FooCoalesce::$staticAlwaysNull (null) on left side of ?? is always null.', 101, ], + [ + 'Variable $a on left side of ?? always exists and is always null.', + 115, + ], [ 'Property CoalesceRule\FooCoalesce::$string (string) on left side of ?? is not nullable.', 120, @@ -106,6 +118,10 @@ public function testCoalesceAssignRule(): void 'Property CoalesceAssignRule\FooCoalesce::$string (string) on left side of ??= is not nullable.', 32, ], + [ + 'Variable $scalar on left side of ??= always exists and is not nullable.', + 41, + ], [ 'Offset \'string\' on array(1, 2, 3) on left side of ??= does not exist.', 45, @@ -114,6 +130,10 @@ public function testCoalesceAssignRule(): void 'Offset \'string\' on array(array(1), array(2), array(3)) on left side of ??= does not exist.', 49, ], + [ + 'Variable $doesNotExist on left side of ??= is never defined.', + 51, + ], [ 'Offset \'dim\' on array(\'dim\' => 1, \'dim-null\' => 1|null, \'dim-null-offset\' => array(\'a\' => true|null), \'dim-empty\' => array()) on left side of ??= always exists and is not nullable.', 67, @@ -142,6 +162,10 @@ public function testCoalesceAssignRule(): void 'Static property CoalesceAssignRule\FooCoalesce::$staticAlwaysNull (null) on left side of ??= is always null.', 101, ], + [ + 'Variable $a on left side of ??= always exists and is always null.', + 115, + ], ]); } @@ -154,4 +178,54 @@ public function testNullsafe(): void $this->analyse([__DIR__ . '/data/null-coalesce-nullsafe.php'], []); } + public function testVariableCertaintyInNullCoalesce(): void + { + $this->analyse([__DIR__ . '/data/variable-certainty-null.php'], [ + [ + 'Variable $scalar on left side of ?? always exists and is not nullable.', + 6, + ], + [ + 'Variable $doesNotExist on left side of ?? is never defined.', + 8, + ], + [ + 'Variable $a on left side of ?? always exists and is always null.', + 13, + ], + ]); + } + + public function testVariableCertaintyInNullCoalesceAssign(): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { + $this->markTestSkipped('Test requires PHP 7.4.'); + } + + $this->analyse([__DIR__ . '/data/variable-certainty-null-assign.php'], [ + [ + 'Variable $scalar on left side of ??= always exists and is not nullable.', + 6, + ], + [ + 'Variable $doesNotExist on left side of ??= is never defined.', + 8, + ], + [ + 'Variable $a on left side of ??= always exists and is always null.', + 13, + ], + ]); + } + + public function testNullCoalesceInGlobalScope(): void + { + $this->analyse([__DIR__ . '/data/null-coalesce-global-scope.php'], [ + [ + 'Variable $bar on left side of ?? always exists and is not nullable.', + 6, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Variables/VariableCertaintyNullCoalesceRuleTest.php b/tests/PHPStan/Rules/Variables/VariableCertaintyNullCoalesceRuleTest.php deleted file mode 100644 index c9c7fe7b22..0000000000 --- a/tests/PHPStan/Rules/Variables/VariableCertaintyNullCoalesceRuleTest.php +++ /dev/null @@ -1,66 +0,0 @@ - - */ -class VariableCertaintyNullCoalesceRuleTest extends \PHPStan\Testing\RuleTestCase -{ - - protected function getRule(): \PHPStan\Rules\Rule - { - return new VariableCertaintyNullCoalesceRule(); - } - - public function testVariableCertaintyInNullCoalesce(): void - { - $this->analyse([__DIR__ . '/data/variable-certainty-null.php'], [ - [ - 'Variable $scalar on left side of ?? always exists and is not nullable.', - 6, - ], - [ - 'Variable $doesNotExist on left side of ?? is never defined.', - 8, - ], - [ - 'Variable $a on left side of ?? is always null.', - 13, - ], - ]); - } - - public function testVariableCertaintyInNullCoalesceAssign(): void - { - if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Test requires PHP 7.4.'); - } - - $this->analyse([__DIR__ . '/data/variable-certainty-null-assign.php'], [ - [ - 'Variable $scalar on left side of ??= always exists and is not nullable.', - 6, - ], - [ - 'Variable $doesNotExist on left side of ??= is never defined.', - 8, - ], - [ - 'Variable $a on left side of ??= is always null.', - 13, - ], - ]); - } - - public function testNullCoalesceInGlobalScope(): void - { - $this->analyse([__DIR__ . '/data/null-coalesce-global-scope.php'], [ - [ - 'Variable $bar on left side of ?? always exists and is not nullable.', - 6, - ], - ]); - } - -} diff --git a/tests/PHPStan/Rules/Variables/data/bug-970.php b/tests/PHPStan/Rules/Variables/data/bug-970.php new file mode 100644 index 0000000000..c1b988f68e --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/bug-970.php @@ -0,0 +1,14 @@ + Date: Fri, 10 Sep 2021 15:20:42 +0200 Subject: [PATCH 0222/1284] empty()/isset()/null-coalesce rules - report variables issues on level 1, the rest on level 4 --- conf/config.level1.neon | 17 +++ conf/config.level4.neon | 18 +-- conf/config.neon | 3 + src/Rules/IssetCheck.php | 121 +++++++++++++++++- tests/PHPStan/Levels/data/coalesce-1.json | 12 +- .../PHPStan/Rules/Variables/EmptyRuleTest.php | 6 +- .../PHPStan/Rules/Variables/IssetRuleTest.php | 8 +- .../Rules/Variables/NullCoalesceRuleTest.php | 18 ++- .../Rules/Variables/data/null-coalesce.php | 6 + .../data/variable-certainty-isset.php | 6 +- 10 files changed, 184 insertions(+), 31 deletions(-) diff --git a/conf/config.level1.neon b/conf/config.level1.neon index 99006e0133..91baf1dc4d 100644 --- a/conf/config.level1.neon +++ b/conf/config.level1.neon @@ -12,6 +12,14 @@ rules: - PHPStan\Rules\Constants\ConstantRule - PHPStan\Rules\Functions\UnusedClosureUsesRule +conditionalTags: + PHPStan\Rules\Variables\EmptyRule: + phpstan.rules.rule: %featureToggles.nullCoalesce% + PHPStan\Rules\Variables\IssetRule: + phpstan.rules.rule: %featureToggles.nullCoalesce% + PHPStan\Rules\Variables\NullCoalesceRule: + phpstan.rules.rule: %featureToggles.nullCoalesce% + services: - class: PHPStan\Rules\Variables\VariableCertaintyInIssetRule @@ -19,3 +27,12 @@ services: bleedingEdge: %featureToggles.bleedingEdge% tags: - phpstan.rules.rule + + - + class: PHPStan\Rules\Variables\EmptyRule + + - + class: PHPStan\Rules\Variables\IssetRule + + - + class: PHPStan\Rules\Variables\NullCoalesceRule diff --git a/conf/config.level4.neon b/conf/config.level4.neon index 9d731fe913..16fdac9f1b 100644 --- a/conf/config.level4.neon +++ b/conf/config.level4.neon @@ -28,12 +28,9 @@ conditionalTags: phpstan.rules.rule: %featureToggles.unusedClassElements% PHPStan\Rules\DeadCode\UnusedPrivatePropertyRule: phpstan.rules.rule: %featureToggles.unusedClassElements% - PHPStan\Rules\Variables\EmptyRule: - phpstan.rules.rule: %featureToggles.nullCoalesce% - PHPStan\Rules\Variables\IssetRule: - phpstan.rules.rule: %featureToggles.nullCoalesce% - PHPStan\Rules\Variables\NullCoalesceRule: - phpstan.rules.rule: %featureToggles.nullCoalesce% + +parameters: + checkAdvancedIsset: true services: - @@ -165,12 +162,3 @@ services: checkProtectedAndPublicMethods: %checkTooWideReturnTypesInProtectedAndPublicMethods% tags: - phpstan.rules.rule - - - - class: PHPStan\Rules\Variables\EmptyRule - - - - class: PHPStan\Rules\Variables\IssetRule - - - - class: PHPStan\Rules\Variables\NullCoalesceRule diff --git a/conf/config.neon b/conf/config.neon index 6236f4d147..46849b6b79 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -54,6 +54,7 @@ parameters: overridingProperty: false fileExtensions: - php + checkAdvancedIsset: false checkAlwaysTrueCheckTypeFunctionCall: false checkAlwaysTrueInstanceof: false checkAlwaysTrueStrictComparison: false @@ -239,6 +240,7 @@ parametersSchema: overridingProperty: bool() ]) fileExtensions: listOf(string()) + checkAdvancedIsset: bool() checkAlwaysTrueCheckTypeFunctionCall: bool() checkAlwaysTrueInstanceof: bool() checkAlwaysTrueStrictComparison: bool() @@ -861,6 +863,7 @@ services: class: PHPStan\Rules\IssetCheck arguments: bleedingEdge: %featureToggles.bleedingEdge% + checkAdvancedIsset: %checkAdvancedIsset% - # checked as part of OverridingMethodRule diff --git a/src/Rules/IssetCheck.php b/src/Rules/IssetCheck.php index 47e44e49d3..354eaba778 100644 --- a/src/Rules/IssetCheck.php +++ b/src/Rules/IssetCheck.php @@ -18,16 +18,20 @@ class IssetCheck private \PHPStan\Rules\Properties\PropertyReflectionFinder $propertyReflectionFinder; + private bool $checkAdvancedIsset; + private bool $bleedingEdge; public function __construct( PropertyDescriptor $propertyDescriptor, PropertyReflectionFinder $propertyReflectionFinder, - bool $bleedingEdge = false + bool $checkAdvancedIsset, + bool $bleedingEdge ) { $this->propertyDescriptor = $propertyDescriptor; $this->propertyReflectionFinder = $propertyReflectionFinder; + $this->checkAdvancedIsset = $checkAdvancedIsset; $this->bleedingEdge = $bleedingEdge; } @@ -68,11 +72,19 @@ public function check(Expr $expr, Scope $scope, string $operatorDescription, cal $dimType = $scope->getType($expr->dim); $hasOffsetValue = $type->hasOffsetValueType($dimType); if (!$type->isOffsetAccessible()->yes()) { - return $error; + return $error ?? $this->checkUndefined($expr->var, $scope, $operatorDescription); } if ($hasOffsetValue->no()) { - return $error ?? RuleErrorBuilder::message( + if ($error !== null) { + return $error; + } + + if (!$this->checkAdvancedIsset) { + return null; + } + + return RuleErrorBuilder::message( sprintf( 'Offset %s on %s %s does not exist.', $dimType->describe(VerbosityLevel::value()), @@ -89,8 +101,15 @@ public function check(Expr $expr, Scope $scope, string $operatorDescription, cal // If offset is cannot be null, store this error message and see if one of the earlier offsets is. // E.g. $array['a']['b']['c'] ?? null; is a valid coalesce if a OR b or C might be null. if ($hasOffsetValue->yes()) { + if ($error !== null) { + return $error; + } - $error = $error ?? $this->generateError($type->getOffsetValueType($dimType), sprintf( + if (!$this->checkAdvancedIsset) { + return null; + } + + $error = $this->generateError($type->getOffsetValueType($dimType), sprintf( 'Offset %s on %s %s always exists and', $dimType->describe(VerbosityLevel::value()), $type->describe(VerbosityLevel::value()), @@ -110,24 +129,62 @@ public function check(Expr $expr, Scope $scope, string $operatorDescription, cal $propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($expr, $scope); if ($propertyReflection === null) { + if ($expr instanceof Node\Expr\PropertyFetch) { + return $this->checkUndefined($expr->var, $scope, $operatorDescription); + } + + if ($expr->class instanceof Expr) { + return $this->checkUndefined($expr->class, $scope, $operatorDescription); + } + return null; } if (!$propertyReflection->isNative()) { + if ($expr instanceof Node\Expr\PropertyFetch) { + return $this->checkUndefined($expr->var, $scope, $operatorDescription); + } + + if ($expr->class instanceof Expr) { + return $this->checkUndefined($expr->class, $scope, $operatorDescription); + } + return null; } $nativeType = $propertyReflection->getNativeType(); if (!$nativeType instanceof MixedType) { if (!$scope->isSpecified($expr)) { + if ($expr instanceof Node\Expr\PropertyFetch) { + return $this->checkUndefined($expr->var, $scope, $operatorDescription); + } + + if ($expr->class instanceof Expr) { + return $this->checkUndefined($expr->class, $scope, $operatorDescription); + } + return null; } } $propertyDescription = $this->propertyDescriptor->describeProperty($propertyReflection, $expr); $propertyType = $propertyReflection->getWritableType(); + if ($error !== null) { + return $error; + } + if (!$this->checkAdvancedIsset) { + if ($expr instanceof Node\Expr\PropertyFetch) { + return $this->checkUndefined($expr->var, $scope, $operatorDescription); + } + + if ($expr->class instanceof Expr) { + return $this->checkUndefined($expr->class, $scope, $operatorDescription); + } - $error = $error ?? $this->generateError( + return null; + } + + $error = $this->generateError( $propertyReflection->getWritableType(), sprintf('%s (%s) %s', $propertyDescription, $propertyType->describe(VerbosityLevel::typeOnly()), $operatorDescription), $typeMessageCallback @@ -146,7 +203,59 @@ public function check(Expr $expr, Scope $scope, string $operatorDescription, cal return $error; } - return $error ?? $this->generateError($scope->getType($expr), sprintf('Expression %s', $operatorDescription), $typeMessageCallback); + if ($error !== null) { + return $error; + } + + if (!$this->checkAdvancedIsset) { + return null; + } + + return $this->generateError($scope->getType($expr), sprintf('Expression %s', $operatorDescription), $typeMessageCallback); + } + + private function checkUndefined(Expr $expr, Scope $scope, string $operatorDescription): ?RuleError + { + if ($expr instanceof Node\Expr\Variable && is_string($expr->name)) { + $hasVariable = $scope->hasVariableType($expr->name); + if (!$hasVariable->no()) { + return null; + } + + return RuleErrorBuilder::message(sprintf('Variable $%s %s is never defined.', $expr->name, $operatorDescription))->build(); + } + + if ($expr instanceof Node\Expr\ArrayDimFetch && $expr->dim !== null) { + $type = $scope->getType($expr->var); + $dimType = $scope->getType($expr->dim); + $hasOffsetValue = $type->hasOffsetValueType($dimType); + if (!$type->isOffsetAccessible()->yes()) { + return $this->checkUndefined($expr->var, $scope, $operatorDescription); + } + + if (!$hasOffsetValue->no()) { + return $this->checkUndefined($expr->var, $scope, $operatorDescription); + } + + return RuleErrorBuilder::message( + sprintf( + 'Offset %s on %s %s does not exist.', + $dimType->describe(VerbosityLevel::value()), + $type->describe(VerbosityLevel::value()), + $operatorDescription + ) + )->build(); + } + + if ($expr instanceof Expr\PropertyFetch) { + return $this->checkUndefined($expr->var, $scope, $operatorDescription); + } + + if ($expr instanceof Expr\StaticPropertyFetch && $expr->class instanceof Expr) { + return $this->checkUndefined($expr->class, $scope, $operatorDescription); + } + + return null; } /** diff --git a/tests/PHPStan/Levels/data/coalesce-1.json b/tests/PHPStan/Levels/data/coalesce-1.json index 6636fbc672..f329d4e78d 100644 --- a/tests/PHPStan/Levels/data/coalesce-1.json +++ b/tests/PHPStan/Levels/data/coalesce-1.json @@ -8,5 +8,15 @@ "message": "Variable $bar on left side of ?? is never defined.", "line": 6, "ignorable": true + }, + { + "message": "Variable $a on left side of ?? is never defined.", + "line": 15, + "ignorable": true + }, + { + "message": "Variable $s on left side of ?? always exists and is always null.", + "line": 23, + "ignorable": true } -] \ No newline at end of file +] diff --git a/tests/PHPStan/Rules/Variables/EmptyRuleTest.php b/tests/PHPStan/Rules/Variables/EmptyRuleTest.php index bfd6cdefdb..0ed215f228 100644 --- a/tests/PHPStan/Rules/Variables/EmptyRuleTest.php +++ b/tests/PHPStan/Rules/Variables/EmptyRuleTest.php @@ -15,7 +15,7 @@ class EmptyRuleTest extends RuleTestCase protected function getRule(): \PHPStan\Rules\Rule { - return new EmptyRule(new IssetCheck(new PropertyDescriptor(), new PropertyReflectionFinder(), true)); + return new EmptyRule(new IssetCheck(new PropertyDescriptor(), new PropertyReflectionFinder(), true, true)); } public function testRule(): void @@ -60,8 +60,8 @@ public function testBug970(): void { $this->analyse([__DIR__ . '/data/bug-970.php'], [ [ - 'aaa', - 10, + 'Variable $ar in empty() is never defined.', + 9, ], ]); } diff --git a/tests/PHPStan/Rules/Variables/IssetRuleTest.php b/tests/PHPStan/Rules/Variables/IssetRuleTest.php index 5c20822123..e495f80661 100644 --- a/tests/PHPStan/Rules/Variables/IssetRuleTest.php +++ b/tests/PHPStan/Rules/Variables/IssetRuleTest.php @@ -16,7 +16,7 @@ class IssetRuleTest extends RuleTestCase protected function getRule(): Rule { - return new IssetRule(new IssetCheck(new PropertyDescriptor(), new PropertyReflectionFinder(), true)); + return new IssetRule(new IssetCheck(new PropertyDescriptor(), new PropertyReflectionFinder(), true, true)); } public function testRule(): void @@ -46,6 +46,10 @@ public function testRule(): void 'Offset \'dim\' on array(\'dim\' => 1, \'dim-null\' => 1|null, \'dim-null-offset\' => array(\'a\' => true|null), \'dim-empty\' => array()) in isset() always exists and is not nullable.', 67, ], + [ + 'Offset \'dim-null-not-set\' on array(\'dim\' => 1, \'dim-null\' => 1|null, \'dim-null-offset\' => array(\'a\' => true|null), \'dim-empty\' => array()) in isset() does not exist.', + 73, + ], [ 'Offset \'b\' on array() in isset() does not exist.', 79, @@ -166,7 +170,7 @@ public function testVariableCertaintyInIsset(): void 116, ], [ - 'Variable $variableInSecondCase in isset() always exists and is not nullable.', + 'Variable $variableInSecondCase in isset() always exists and is always null.', 117, ], [ diff --git a/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php b/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php index 33636a032c..3ed88c5604 100644 --- a/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php +++ b/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php @@ -14,7 +14,7 @@ class NullCoalesceRuleTest extends \PHPStan\Testing\RuleTestCase protected function getRule(): \PHPStan\Rules\Rule { - return new NullCoalesceRule(new IssetCheck(new PropertyDescriptor(), new PropertyReflectionFinder(), true)); + return new NullCoalesceRule(new IssetCheck(new PropertyDescriptor(), new PropertyReflectionFinder(), true, true)); } public function testCoalesceRule(): void @@ -44,6 +44,10 @@ public function testCoalesceRule(): void 'Offset \'dim\' on array(\'dim\' => 1, \'dim-null\' => 1|null, \'dim-null-offset\' => array(\'a\' => true|null), \'dim-empty\' => array()) on left side of ?? always exists and is not nullable.', 67, ], + [ + 'Offset \'dim-null-not-set\' on array(\'dim\' => 1, \'dim-null\' => 1|null, \'dim-null-offset\' => array(\'a\' => true|null), \'dim-empty\' => array()) on left side of ?? does not exist.', + 73, + ], [ 'Offset \'b\' on array() on left side of ?? does not exist.', 79, @@ -104,6 +108,14 @@ public function testCoalesceRule(): void 'Property ReflectionClass::$name (class-string) on left side of ?? is not nullable.', 136, ], + [ + 'Variable $foo on left side of ?? is never defined.', + 141, + ], + [ + 'Variable $bar on left side of ?? is never defined.', + 143, + ], ]); } @@ -138,6 +150,10 @@ public function testCoalesceAssignRule(): void 'Offset \'dim\' on array(\'dim\' => 1, \'dim-null\' => 1|null, \'dim-null-offset\' => array(\'a\' => true|null), \'dim-empty\' => array()) on left side of ??= always exists and is not nullable.', 67, ], + [ + 'Offset \'dim-null-not-set\' on array(\'dim\' => 1, \'dim-null\' => 0|1, \'dim-null-offset\' => array(\'a\' => true|null), \'dim-empty\' => array()) on left side of ??= does not exist.', + 73, + ], [ 'Offset \'b\' on array() on left side of ??= does not exist.', 79, diff --git a/tests/PHPStan/Rules/Variables/data/null-coalesce.php b/tests/PHPStan/Rules/Variables/data/null-coalesce.php index a694fa67aa..24d71e8ea0 100644 --- a/tests/PHPStan/Rules/Variables/data/null-coalesce.php +++ b/tests/PHPStan/Rules/Variables/data/null-coalesce.php @@ -136,3 +136,9 @@ function (\ReflectionClass $ref): void { echo $ref->name ?? 'foo'; echo $ref->nonexistent ?? 'bar'; }; + +function (): void { + echo $foo ?? 'foo'; + + echo $bar->bar ?? 'foo'; +}; diff --git a/tests/PHPStan/Rules/Variables/data/variable-certainty-isset.php b/tests/PHPStan/Rules/Variables/data/variable-certainty-isset.php index 866b7d33e4..044a79b5c7 100644 --- a/tests/PHPStan/Rules/Variables/data/variable-certainty-isset.php +++ b/tests/PHPStan/Rules/Variables/data/variable-certainty-isset.php @@ -26,15 +26,15 @@ function foo() } - /** @var string|null $anotherAlwaysDefinedNullable */ + /** @var array|null $anotherAlwaysDefinedNullable */ $anotherAlwaysDefinedNullable = doFoo(); if (isset($anotherAlwaysDefinedNullable['test']['test'])) { // fine, checking for nullability } - $anotherAlwaysDefinedNotNullable = 'string'; - + /** @var array $anotherAlwaysDefinedNotNullable */ + $anotherAlwaysDefinedNotNullable = doFoo(); if (isset($anotherAlwaysDefinedNotNullable['test']['test'])) { // fine, variable always exists, but what about the array index? } From 165504cf9c4ae6e2dcfc2c3570a631441a411615 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 11 Sep 2021 22:33:11 +0200 Subject: [PATCH 0223/1284] Bleeding edge - teach CatchWithUnthrownExceptionRule everything what DeadCatchRule does --- conf/config.level4.neon | 8 +++++++- src/Analyser/NodeScopeResolver.php | 2 +- src/Node/CatchWithUnthrownExceptionNode.php | 10 +++++++++- .../Exceptions/CatchWithUnthrownExceptionRule.php | 9 +++++++++ src/Rules/Exceptions/DeadCatchRule.php | 11 +++++++++++ .../Exceptions/CatchWithUnthrownExceptionRuleTest.php | 10 ++++++++++ tests/PHPStan/Rules/Exceptions/data/dead-catch.php | 4 ++-- 7 files changed, 49 insertions(+), 5 deletions(-) diff --git a/conf/config.level4.neon b/conf/config.level4.neon index 16fdac9f1b..cb7540c5eb 100644 --- a/conf/config.level4.neon +++ b/conf/config.level4.neon @@ -6,7 +6,6 @@ rules: - PHPStan\Rules\Comparison\NumberComparisonOperatorsConstantConditionRule - PHPStan\Rules\DeadCode\NoopRule - PHPStan\Rules\DeadCode\UnreachableStatementRule - - PHPStan\Rules\Exceptions\DeadCatchRule - PHPStan\Rules\Functions\CallToFunctionStamentWithoutSideEffectsRule - PHPStan\Rules\Methods\CallToConstructorStatementWithoutSideEffectsRule - PHPStan\Rules\Methods\CallToMethodStamentWithoutSideEffectsRule @@ -153,6 +152,13 @@ services: - class: PHPStan\Rules\Exceptions\CatchWithUnthrownExceptionRule + - + class: PHPStan\Rules\Exceptions\DeadCatchRule + arguments: + bleedingEdge: %featureToggles.bleedingEdge% + tags: + - phpstan.rules.rule + - class: PHPStan\Rules\Exceptions\OverwrittenExitPointByFinallyRule diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 70d9306458..05765a86f8 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -1233,7 +1233,7 @@ private function processStmtNode( } if (count($throwableThrowPoints) === 0) { - $nodeCallback(new CatchWithUnthrownExceptionNode($catchNode, $catchType), $scope); + $nodeCallback(new CatchWithUnthrownExceptionNode($catchNode, $catchType, $originalCatchType), $scope); continue; } diff --git a/src/Node/CatchWithUnthrownExceptionNode.php b/src/Node/CatchWithUnthrownExceptionNode.php index 01ad10629b..6c367e32d4 100644 --- a/src/Node/CatchWithUnthrownExceptionNode.php +++ b/src/Node/CatchWithUnthrownExceptionNode.php @@ -14,11 +14,14 @@ class CatchWithUnthrownExceptionNode extends NodeAbstract implements VirtualNode private Type $caughtType; - public function __construct(Catch_ $originalNode, Type $caughtType) + private Type $originalCaughtType; + + public function __construct(Catch_ $originalNode, Type $caughtType, Type $originalCaughtType) { parent::__construct($originalNode->getAttributes()); $this->originalNode = $originalNode; $this->caughtType = $caughtType; + $this->originalCaughtType = $originalCaughtType; } public function getOriginalNode(): Catch_ @@ -31,6 +34,11 @@ public function getCaughtType(): Type return $this->caughtType; } + public function getOriginalCaughtType(): Type + { + return $this->originalCaughtType; + } + public function getType(): string { return 'PHPStan_Node_CatchWithUnthrownExceptionNode'; diff --git a/src/Rules/Exceptions/CatchWithUnthrownExceptionRule.php b/src/Rules/Exceptions/CatchWithUnthrownExceptionRule.php index 025e87690d..5e9040e761 100644 --- a/src/Rules/Exceptions/CatchWithUnthrownExceptionRule.php +++ b/src/Rules/Exceptions/CatchWithUnthrownExceptionRule.php @@ -7,6 +7,7 @@ use PHPStan\Node\CatchWithUnthrownExceptionNode; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; +use PHPStan\Type\NeverType; use PHPStan\Type\VerbosityLevel; /** @@ -22,6 +23,14 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { + if ($node->getCaughtType() instanceof NeverType) { + return [ + RuleErrorBuilder::message( + sprintf('Dead catch - %s is already caught above.', $node->getOriginalCaughtType()->describe(VerbosityLevel::typeOnly())) + )->line($node->getLine())->build(), + ]; + } + return [ RuleErrorBuilder::message( sprintf('Dead catch - %s is never thrown in the try block.', $node->getCaughtType()->describe(VerbosityLevel::typeOnly())) diff --git a/src/Rules/Exceptions/DeadCatchRule.php b/src/Rules/Exceptions/DeadCatchRule.php index d05a634792..1761bbb637 100644 --- a/src/Rules/Exceptions/DeadCatchRule.php +++ b/src/Rules/Exceptions/DeadCatchRule.php @@ -17,6 +17,13 @@ class DeadCatchRule implements Rule { + private bool $bleedingEdge; + + public function __construct(bool $bleedingEdge = false) + { + $this->bleedingEdge = $bleedingEdge; + } + public function getNodeType(): string { return Node\Stmt\TryCatch::class; @@ -24,6 +31,10 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { + if ($this->bleedingEdge) { + return []; + } + $catchTypes = array_map(static function (Node\Stmt\Catch_ $catch): Type { return TypeCombinator::union(...array_map(static function (Node\Name $className): ObjectType { return new ObjectType($className->toString()); diff --git a/tests/PHPStan/Rules/Exceptions/CatchWithUnthrownExceptionRuleTest.php b/tests/PHPStan/Rules/Exceptions/CatchWithUnthrownExceptionRuleTest.php index 1c1ddb0a44..15c4930e07 100644 --- a/tests/PHPStan/Rules/Exceptions/CatchWithUnthrownExceptionRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/CatchWithUnthrownExceptionRuleTest.php @@ -171,4 +171,14 @@ public function testThrowExpression(): void ]); } + public function testDeadCatch(): void + { + $this->analyse([__DIR__ . '/data/dead-catch.php'], [ + [ + 'Dead catch - TypeError is already caught above.', + 27, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Exceptions/data/dead-catch.php b/tests/PHPStan/Rules/Exceptions/data/dead-catch.php index a2765c41d9..788cb88584 100644 --- a/tests/PHPStan/Rules/Exceptions/data/dead-catch.php +++ b/tests/PHPStan/Rules/Exceptions/data/dead-catch.php @@ -8,7 +8,7 @@ class Foo public function doFoo() { try { - + doFoo(); } catch (\Exception $e) { } catch (\TypeError $e) { @@ -21,7 +21,7 @@ public function doFoo() public function doBar() { try { - + doFoo(); } catch (\Throwable $e) { } catch (\TypeError $e) { From 7ddfa174a60a15e584c7e6f4fec82237ccd70da8 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 11 Sep 2021 22:47:56 +0200 Subject: [PATCH 0224/1284] Enter assignment of property fetch's var when in null coalesce operator --- src/Analyser/NodeScopeResolver.php | 10 ++++++++-- .../Rules/Variables/DefinedVariableRuleTest.php | 10 ++++++++++ tests/PHPStan/Rules/Variables/data/bug-3283.php | 15 +++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Rules/Variables/data/bug-3283.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 05765a86f8..14621c624f 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -2326,8 +2326,14 @@ static function () use ($expr, $rightResult): MutatingScope { } elseif ($expr instanceof Coalesce) { $nonNullabilityResult = $this->ensureNonNullability($scope, $expr->left, false); - if ($expr->left instanceof PropertyFetch || $expr->left instanceof StaticPropertyFetch || $expr->left instanceof Expr\NullsafePropertyFetch) { - $scope = $nonNullabilityResult->getScope(); + if ($expr->left instanceof PropertyFetch || $expr->left instanceof Expr\NullsafePropertyFetch) { + $scope = $this->lookForEnterVariableAssign($nonNullabilityResult->getScope(), $expr->left->var); + } elseif ($expr->left instanceof StaticPropertyFetch) { + if ($expr->left->class instanceof Expr) { + $scope = $this->lookForEnterVariableAssign($nonNullabilityResult->getScope(), $expr->left->class); + } else { + $scope = $nonNullabilityResult->getScope(); + } } else { $scope = $this->lookForEnterVariableAssign($nonNullabilityResult->getScope(), $expr->left); } diff --git a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php index f09b906189..3beb38ad2b 100644 --- a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php +++ b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php @@ -840,4 +840,14 @@ public function testBug4412(): void ]); } + public function testBug3283(): void + { + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = false; + $this->polluteCatchScopeWithTryAssignments = false; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + $this->analyse([__DIR__ . '/data/bug-3283.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Variables/data/bug-3283.php b/tests/PHPStan/Rules/Variables/data/bug-3283.php new file mode 100644 index 0000000000..6fc95b5d2d --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/bug-3283.php @@ -0,0 +1,15 @@ + 5) { + $user = new \stdClass; + $user->name = 'Thibaud'; + } + + echo $user->name ?? 'Default'; +}; From f306a8b94f1ec1b6b9ab159317c7d0b88ce0d8b1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 11 Sep 2021 23:02:56 +0200 Subject: [PATCH 0225/1284] Fix --- src/Analyser/NodeScopeResolver.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 14621c624f..88766bd69c 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -2343,7 +2343,13 @@ static function () use ($expr, $rightResult): MutatingScope { $scope = $result->getScope(); $scope = $this->revertNonNullability($scope, $nonNullabilityResult->getSpecifiedExpressions()); - if (!$expr->left instanceof PropertyFetch) { + if ($expr->left instanceof PropertyFetch || $expr->left instanceof Expr\NullsafePropertyFetch) { + $scope = $this->lookForExitVariableAssign($scope, $expr->left->var); + } elseif ($expr->left instanceof StaticPropertyFetch) { + if ($expr->left->class instanceof Expr) { + $scope = $this->lookForExitVariableAssign($scope, $expr->left->class); + } + } else { $scope = $this->lookForExitVariableAssign($scope, $expr->left); } $result = $this->processExprNode($expr->right, $scope, $nodeCallback, $context->enterDeep()); From 9ecefd56d8b1d5cf0100c50d6e620285d5312291 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 11 Sep 2021 23:17:30 +0200 Subject: [PATCH 0226/1284] Bleeding edge - MissingReturnRule - make the error non-ignorable for native typehints --- conf/config.level0.neon | 1 + src/Rules/Missing/MissingReturnRule.php | 26 ++++++++++++++++++++----- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/conf/config.level0.neon b/conf/config.level0.neon index 6f1bbf757e..a57e69384f 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -172,6 +172,7 @@ services: arguments: checkExplicitMixedMissingReturn: %checkExplicitMixedMissingReturn% checkPhpDocMissingReturn: %checkPhpDocMissingReturn% + bleedingEdge: %featureToggles.bleedingEdge% tags: - phpstan.rules.rule diff --git a/src/Rules/Missing/MissingReturnRule.php b/src/Rules/Missing/MissingReturnRule.php index 641352b3e6..39de55fac0 100644 --- a/src/Rules/Missing/MissingReturnRule.php +++ b/src/Rules/Missing/MissingReturnRule.php @@ -28,13 +28,17 @@ class MissingReturnRule implements Rule private bool $checkPhpDocMissingReturn; + private bool $bleedingEdge; + public function __construct( bool $checkExplicitMixedMissingReturn, - bool $checkPhpDocMissingReturn + bool $checkPhpDocMissingReturn, + bool $bleedingEdge = false ) { $this->checkExplicitMixedMissingReturn = $checkExplicitMixedMissingReturn; $this->checkPhpDocMissingReturn = $checkPhpDocMissingReturn; + $this->bleedingEdge = $bleedingEdge; } public function getNodeType(): string @@ -106,8 +110,14 @@ public function processNode(Node $node, Scope $scope): array } if ($returnType instanceof NeverType && $returnType->isExplicit()) { + $errorBuilder = RuleErrorBuilder::message(sprintf('%s should always throw an exception or terminate script execution but doesn\'t do that.', $description))->line($node->getNode()->getStartLine()); + + if ($this->bleedingEdge && $node->hasNativeReturnTypehint()) { + $errorBuilder->nonIgnorable(); + } + return [ - RuleErrorBuilder::message(sprintf('%s should always throw an exception or terminate script execution but doesn\'t do that.', $description))->line($node->getNode()->getStartLine())->build(), + $errorBuilder->build(), ]; } @@ -123,10 +133,16 @@ public function processNode(Node $node, Scope $scope): array return []; } + $errorBuilder = RuleErrorBuilder::message( + sprintf('%s should return %s but return statement is missing.', $description, $returnType->describe(VerbosityLevel::typeOnly())) + )->line($node->getNode()->getStartLine()); + + if ($this->bleedingEdge && $node->hasNativeReturnTypehint()) { + $errorBuilder->nonIgnorable(); + } + return [ - RuleErrorBuilder::message( - sprintf('%s should return %s but return statement is missing.', $description, $returnType->describe(VerbosityLevel::typeOnly())) - )->line($node->getNode()->getStartLine())->build(), + $errorBuilder->build(), ]; } From 1b48f4319fe242a0523bdda9ec3423f1034b351a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 11 Sep 2021 23:31:08 +0200 Subject: [PATCH 0227/1284] Fix --- tests/PHPStan/Levels/data/coalesce-0.json | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 tests/PHPStan/Levels/data/coalesce-0.json diff --git a/tests/PHPStan/Levels/data/coalesce-0.json b/tests/PHPStan/Levels/data/coalesce-0.json deleted file mode 100644 index d4d84323dd..0000000000 --- a/tests/PHPStan/Levels/data/coalesce-0.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - { - "message": "Undefined variable: $bar", - "line": 6, - "ignorable": true - } -] \ No newline at end of file From fff85f3c08a38110bca6a13d341778bbd79a2108 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 11 Sep 2021 23:38:04 +0200 Subject: [PATCH 0228/1284] CompoundTypeHelper is deprecated --- src/Type/Accessory/AccessoryLiteralStringType.php | 3 +-- src/Type/Accessory/AccessoryNonEmptyStringType.php | 3 +-- src/Type/Accessory/AccessoryNumericStringType.php | 3 +-- src/Type/Accessory/HasOffsetType.php | 3 +-- src/Type/Accessory/NonEmptyArrayType.php | 3 +-- src/Type/ArrayType.php | 2 +- src/Type/CallableType.php | 2 +- src/Type/ClosureType.php | 2 +- src/Type/CompoundTypeHelper.php | 3 +++ src/Type/Generic/TemplateTypeParameterStrategy.php | 3 +-- src/Type/IntegerRangeType.php | 2 +- src/Type/IterableType.php | 2 +- src/Type/JustNullableTypeTrait.php | 2 +- src/Type/NullType.php | 2 +- src/Type/ObjectType.php | 2 +- src/Type/ObjectWithoutClassType.php | 2 +- src/Type/StaticType.php | 2 +- src/Type/StringType.php | 2 +- src/Type/Traits/ConstantScalarTypeTrait.php | 3 +-- src/Type/UnionType.php | 2 +- 20 files changed, 22 insertions(+), 26 deletions(-) diff --git a/src/Type/Accessory/AccessoryLiteralStringType.php b/src/Type/Accessory/AccessoryLiteralStringType.php index 0208a69b33..2cbf3134f7 100644 --- a/src/Type/Accessory/AccessoryLiteralStringType.php +++ b/src/Type/Accessory/AccessoryLiteralStringType.php @@ -5,7 +5,6 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\BooleanType; use PHPStan\Type\CompoundType; -use PHPStan\Type\CompoundTypeHelper; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\ErrorType; @@ -47,7 +46,7 @@ public function accepts(Type $type, bool $strictTypes): TrinaryLogic return TrinaryLogic::createNo(); } if ($type instanceof CompoundType) { - return CompoundTypeHelper::accepts($type, $this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } return $type->isLiteralString(); diff --git a/src/Type/Accessory/AccessoryNonEmptyStringType.php b/src/Type/Accessory/AccessoryNonEmptyStringType.php index 72f2f4a19e..2e3f599e7e 100644 --- a/src/Type/Accessory/AccessoryNonEmptyStringType.php +++ b/src/Type/Accessory/AccessoryNonEmptyStringType.php @@ -4,7 +4,6 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\CompoundType; -use PHPStan\Type\CompoundTypeHelper; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\ErrorType; @@ -44,7 +43,7 @@ public function getReferencedClasses(): array public function accepts(Type $type, bool $strictTypes): TrinaryLogic { if ($type instanceof CompoundType) { - return CompoundTypeHelper::accepts($type, $this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } return $type->isNonEmptyString(); diff --git a/src/Type/Accessory/AccessoryNumericStringType.php b/src/Type/Accessory/AccessoryNumericStringType.php index da0df04efc..3463615c91 100644 --- a/src/Type/Accessory/AccessoryNumericStringType.php +++ b/src/Type/Accessory/AccessoryNumericStringType.php @@ -4,7 +4,6 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\CompoundType; -use PHPStan\Type\CompoundTypeHelper; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\ErrorType; @@ -44,7 +43,7 @@ public function getReferencedClasses(): array public function accepts(Type $type, bool $strictTypes): TrinaryLogic { if ($type instanceof CompoundType) { - return CompoundTypeHelper::accepts($type, $this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } return $type->isNumericString(); diff --git a/src/Type/Accessory/HasOffsetType.php b/src/Type/Accessory/HasOffsetType.php index 4ecdc13cf7..f2f5c41714 100644 --- a/src/Type/Accessory/HasOffsetType.php +++ b/src/Type/Accessory/HasOffsetType.php @@ -4,7 +4,6 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\CompoundType; -use PHPStan\Type\CompoundTypeHelper; use PHPStan\Type\ConstantScalarType; use PHPStan\Type\ErrorType; use PHPStan\Type\IntersectionType; @@ -49,7 +48,7 @@ public function getReferencedClasses(): array public function accepts(Type $type, bool $strictTypes): TrinaryLogic { if ($type instanceof CompoundType) { - return CompoundTypeHelper::accepts($type, $this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } return $type->isOffsetAccessible() diff --git a/src/Type/Accessory/NonEmptyArrayType.php b/src/Type/Accessory/NonEmptyArrayType.php index 3e85adcc3c..0e255f70f9 100644 --- a/src/Type/Accessory/NonEmptyArrayType.php +++ b/src/Type/Accessory/NonEmptyArrayType.php @@ -4,7 +4,6 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\CompoundType; -use PHPStan\Type\CompoundTypeHelper; use PHPStan\Type\Constant\ConstantFloatType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\ErrorType; @@ -40,7 +39,7 @@ public function getReferencedClasses(): array public function accepts(Type $type, bool $strictTypes): TrinaryLogic { if ($type instanceof CompoundType) { - return CompoundTypeHelper::accepts($type, $this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } return $type->isArray() diff --git a/src/Type/ArrayType.php b/src/Type/ArrayType.php index 73334e6c34..34080be9d9 100644 --- a/src/Type/ArrayType.php +++ b/src/Type/ArrayType.php @@ -66,7 +66,7 @@ public function getReferencedClasses(): array public function accepts(Type $type, bool $strictTypes): TrinaryLogic { if ($type instanceof CompoundType) { - return CompoundTypeHelper::accepts($type, $this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } if ($type instanceof ConstantArrayType) { diff --git a/src/Type/CallableType.php b/src/Type/CallableType.php index 6a8a1458ff..397c8c6027 100644 --- a/src/Type/CallableType.php +++ b/src/Type/CallableType.php @@ -71,7 +71,7 @@ public function getReferencedClasses(): array public function accepts(Type $type, bool $strictTypes): TrinaryLogic { if ($type instanceof CompoundType && !$type instanceof self) { - return CompoundTypeHelper::accepts($type, $this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } return $this->isSuperTypeOfInternal($type, true); diff --git a/src/Type/ClosureType.php b/src/Type/ClosureType.php index c16a2845fd..6fdf33fd2a 100644 --- a/src/Type/ClosureType.php +++ b/src/Type/ClosureType.php @@ -89,7 +89,7 @@ public function getReferencedClasses(): array public function accepts(Type $type, bool $strictTypes): TrinaryLogic { if ($type instanceof CompoundType) { - return CompoundTypeHelper::accepts($type, $this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } if (!$type instanceof ClosureType) { diff --git a/src/Type/CompoundTypeHelper.php b/src/Type/CompoundTypeHelper.php index a86e36eb5e..01686a722d 100644 --- a/src/Type/CompoundTypeHelper.php +++ b/src/Type/CompoundTypeHelper.php @@ -4,6 +4,9 @@ use PHPStan\TrinaryLogic; +/** + * @deprecated + */ class CompoundTypeHelper { diff --git a/src/Type/Generic/TemplateTypeParameterStrategy.php b/src/Type/Generic/TemplateTypeParameterStrategy.php index 89e69d6051..5d9a8256d6 100644 --- a/src/Type/Generic/TemplateTypeParameterStrategy.php +++ b/src/Type/Generic/TemplateTypeParameterStrategy.php @@ -4,7 +4,6 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\CompoundType; -use PHPStan\Type\CompoundTypeHelper; use PHPStan\Type\Type; /** @@ -16,7 +15,7 @@ class TemplateTypeParameterStrategy implements TemplateTypeStrategy public function accepts(TemplateType $left, Type $right, bool $strictTypes): TrinaryLogic { if ($right instanceof CompoundType) { - return CompoundTypeHelper::accepts($right, $left, $strictTypes); + return $right->isAcceptedBy($left, $strictTypes); } return $left->getBound()->accepts($right, $strictTypes); diff --git a/src/Type/IntegerRangeType.php b/src/Type/IntegerRangeType.php index fc686d5e94..f06302e833 100644 --- a/src/Type/IntegerRangeType.php +++ b/src/Type/IntegerRangeType.php @@ -202,7 +202,7 @@ public function accepts(Type $type, bool $strictTypes): TrinaryLogic } if ($type instanceof CompoundType) { - return CompoundTypeHelper::accepts($type, $this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } return TrinaryLogic::createNo(); diff --git a/src/Type/IterableType.php b/src/Type/IterableType.php index b69dfb2f89..46c21dad4b 100644 --- a/src/Type/IterableType.php +++ b/src/Type/IterableType.php @@ -70,7 +70,7 @@ public function accepts(Type $type, bool $strictTypes): TrinaryLogic } if ($type instanceof CompoundType) { - return CompoundTypeHelper::accepts($type, $this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } return TrinaryLogic::createNo(); diff --git a/src/Type/JustNullableTypeTrait.php b/src/Type/JustNullableTypeTrait.php index 1bd0f92005..703c5b4c66 100644 --- a/src/Type/JustNullableTypeTrait.php +++ b/src/Type/JustNullableTypeTrait.php @@ -22,7 +22,7 @@ public function accepts(Type $type, bool $strictTypes): TrinaryLogic } if ($type instanceof CompoundType) { - return CompoundTypeHelper::accepts($type, $this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } return TrinaryLogic::createNo(); diff --git a/src/Type/NullType.php b/src/Type/NullType.php index b027a1d93f..a4ca1e92c7 100644 --- a/src/Type/NullType.php +++ b/src/Type/NullType.php @@ -57,7 +57,7 @@ public function accepts(Type $type, bool $strictTypes): TrinaryLogic } if ($type instanceof CompoundType) { - return CompoundTypeHelper::accepts($type, $this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } return TrinaryLogic::createNo(); diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 545048e693..81baf4d731 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -204,7 +204,7 @@ public function accepts(Type $type, bool $strictTypes): TrinaryLogic } if ($type instanceof CompoundType) { - return CompoundTypeHelper::accepts($type, $this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } if ($type instanceof ClosureType) { diff --git a/src/Type/ObjectWithoutClassType.php b/src/Type/ObjectWithoutClassType.php index bca31f1660..23984955a3 100644 --- a/src/Type/ObjectWithoutClassType.php +++ b/src/Type/ObjectWithoutClassType.php @@ -40,7 +40,7 @@ public function getReferencedClasses(): array public function accepts(Type $type, bool $strictTypes): TrinaryLogic { if ($type instanceof CompoundType) { - return CompoundTypeHelper::accepts($type, $this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } return TrinaryLogic::createFromBoolean( diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php index 6d628a36bf..8326b3a79e 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -110,7 +110,7 @@ public function getBaseClass(): string public function accepts(Type $type, bool $strictTypes): TrinaryLogic { if ($type instanceof CompoundType) { - return CompoundTypeHelper::accepts($type, $this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } if (!$type instanceof static) { diff --git a/src/Type/StringType.php b/src/Type/StringType.php index 50240f5da6..f94ca86351 100644 --- a/src/Type/StringType.php +++ b/src/Type/StringType.php @@ -79,7 +79,7 @@ public function accepts(Type $type, bool $strictTypes): TrinaryLogic } if ($type instanceof CompoundType) { - return CompoundTypeHelper::accepts($type, $this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } if ($type instanceof TypeWithClassName && !$strictTypes) { diff --git a/src/Type/Traits/ConstantScalarTypeTrait.php b/src/Type/Traits/ConstantScalarTypeTrait.php index 0ac64800cd..d21d4882cc 100644 --- a/src/Type/Traits/ConstantScalarTypeTrait.php +++ b/src/Type/Traits/ConstantScalarTypeTrait.php @@ -4,7 +4,6 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\CompoundType; -use PHPStan\Type\CompoundTypeHelper; use PHPStan\Type\ConstantScalarType; use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\Type; @@ -19,7 +18,7 @@ public function accepts(Type $type, bool $strictTypes): TrinaryLogic } if ($type instanceof CompoundType) { - return CompoundTypeHelper::accepts($type, $this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } return TrinaryLogic::createNo(); diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index d24baf3bbc..08d347c9d9 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -85,7 +85,7 @@ public function accepts(Type $type, bool $strictTypes): TrinaryLogic } if ($type instanceof CompoundType && !$type instanceof CallableType) { - return CompoundTypeHelper::accepts($type, $this, $strictTypes); + return $type->isAcceptedBy($this, $strictTypes); } $results = []; From 0249e6de7dfbc1056bd54cfe11d905f48844be31 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 11 Sep 2021 23:42:19 +0200 Subject: [PATCH 0229/1284] Fix --- tests/PHPStan/Levels/data/missingReturn-0.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/PHPStan/Levels/data/missingReturn-0.json b/tests/PHPStan/Levels/data/missingReturn-0.json index 222473943d..98754e893f 100644 --- a/tests/PHPStan/Levels/data/missingReturn-0.json +++ b/tests/PHPStan/Levels/data/missingReturn-0.json @@ -2,11 +2,11 @@ { "message": "Method Levels\\MissingReturn\\Foo::doFoo() should return int but return statement is missing.", "line": 8, - "ignorable": true + "ignorable": false }, { "message": "Method Levels\\MissingReturn\\Foo::doBar() should return int but return statement is missing.", "line": 16, "ignorable": true } -] \ No newline at end of file +] From 109bf999b8791f512f2fe82b026ccd67d1f152a2 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 12 Sep 2021 10:20:56 +0200 Subject: [PATCH 0230/1284] Pass-by-ref argument type passed to callable should be mixed after calling the callable --- src/Analyser/NodeScopeResolver.php | 8 +++++ .../Analyser/NodeScopeResolverTest.php | 1 + tests/PHPStan/Analyser/data/bug-5615.php | 29 +++++++++++++++++++ 3 files changed, 38 insertions(+) create mode 100644 tests/PHPStan/Analyser/data/bug-5615.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 88766bd69c..fcc8275ecc 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -1828,6 +1828,14 @@ function (MutatingScope $scope) use ($expr, $nodeCallback, $context): Expression $functionReflection = null; $throwPoints = []; if ($expr->name instanceof Expr) { + $nameType = $scope->getType($expr->name); + if ($nameType->isCallable()->yes()) { + $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( + $scope, + $expr->args, + $nameType->getCallableParametersAcceptors($scope) + ); + } $nameResult = $this->processExprNode($expr->name, $scope, $nodeCallback, $context->enterDeep()); $throwPoints = $nameResult->getThrowPoints(); $scope = $nameResult->getScope(); diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 87d5a2ff20..5b93dd30c6 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -501,6 +501,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-1870.php'); yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/bug-5562.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5615.php'); } /** diff --git a/tests/PHPStan/Analyser/data/bug-5615.php b/tests/PHPStan/Analyser/data/bug-5615.php new file mode 100644 index 0000000000..5c8f8027fb --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-5615.php @@ -0,0 +1,29 @@ + Date: Sun, 12 Sep 2021 11:06:16 +0200 Subject: [PATCH 0231/1284] StubSourceLocatorFactory - always use PHP 8 parser for PhpStorm stubs --- conf/config.stubValidator.neon | 2 +- src/PhpDoc/StubSourceLocatorFactory.php | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/conf/config.stubValidator.neon b/conf/config.stubValidator.neon index 86e25998d0..2f8d462dec 100644 --- a/conf/config.stubValidator.neon +++ b/conf/config.stubValidator.neon @@ -10,7 +10,7 @@ services: - class: PHPStan\PhpDoc\StubSourceLocatorFactory arguments: - parser: @phpParserDecorator + php8Parser: @php8PhpParser stubFiles: %stubFiles% nodeScopeResolverClassReflector: diff --git a/src/PhpDoc/StubSourceLocatorFactory.php b/src/PhpDoc/StubSourceLocatorFactory.php index a3a1215c34..4b461f7537 100644 --- a/src/PhpDoc/StubSourceLocatorFactory.php +++ b/src/PhpDoc/StubSourceLocatorFactory.php @@ -15,7 +15,7 @@ class StubSourceLocatorFactory { - private \PhpParser\Parser $parser; + private \PhpParser\Parser $php8Parser; private PhpStormStubsSourceStubber $phpStormStubsSourceStubber; @@ -30,14 +30,14 @@ class StubSourceLocatorFactory * @param string[] $stubFiles */ public function __construct( - \PhpParser\Parser $parser, + \PhpParser\Parser $php8Parser, PhpStormStubsSourceStubber $phpStormStubsSourceStubber, OptimizedSingleFileSourceLocatorRepository $optimizedSingleFileSourceLocatorRepository, Container $container, array $stubFiles ) { - $this->parser = $parser; + $this->php8Parser = $php8Parser; $this->phpStormStubsSourceStubber = $phpStormStubsSourceStubber; $this->optimizedSingleFileSourceLocatorRepository = $optimizedSingleFileSourceLocatorRepository; $this->container = $container; @@ -47,14 +47,14 @@ public function __construct( public function create(): SourceLocator { $locators = []; - $astLocator = new Locator($this->parser, function (): FunctionReflector { + $astPhp8Locator = new Locator($this->php8Parser, function (): FunctionReflector { return $this->container->getService('stubFunctionReflector'); }); foreach ($this->stubFiles as $stubFile) { $locators[] = $this->optimizedSingleFileSourceLocatorRepository->getOrCreate($stubFile); } - $locators[] = new PhpInternalSourceLocator($astLocator, $this->phpStormStubsSourceStubber); + $locators[] = new PhpInternalSourceLocator($astPhp8Locator, $this->phpStormStubsSourceStubber); return new MemoizingSourceLocator(new AggregateSourceLocator($locators)); } From 6fa12857c88dccfcf58492fc20aaa1f8aaa60535 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 12 Sep 2021 11:07:51 +0200 Subject: [PATCH 0232/1284] Update phpstorm-stubs --- composer.json | 2 +- composer.lock | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index a0acc34938..466404310a 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,7 @@ "hoa/exception": "^1.0", "hoa/regex": "1.17.01.13", "jean85/pretty-package-versions": "^1.0.3", - "jetbrains/phpstorm-stubs": "dev-master#2000119caf61d226e7facad68d7a260d8616a925", + "jetbrains/phpstorm-stubs": "dev-master#82595d7a426c4b3d1e3a7d604ad3f99534784599", "nette/bootstrap": "^3.0", "nette/di": "^3.0.5", "nette/finder": "^2.5", diff --git a/composer.lock b/composer.lock index 28e4ffa134..2dd8777f5a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5da3ab556c01220b08f037130f2e2062", + "content-hash": "09336621e0dad025e2d6fffd6301ba27", "packages": [ { "name": "clue/block-react", @@ -1335,17 +1335,17 @@ "source": { "type": "git", "url": "https://github.com/JetBrains/phpstorm-stubs.git", - "reference": "2000119caf61d226e7facad68d7a260d8616a925" + "reference": "82595d7a426c4b3d1e3a7d604ad3f99534784599" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/2000119caf61d226e7facad68d7a260d8616a925", - "reference": "2000119caf61d226e7facad68d7a260d8616a925", + "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/82595d7a426c4b3d1e3a7d604ad3f99534784599", + "reference": "82595d7a426c4b3d1e3a7d604ad3f99534784599", "shasum": "" }, "require-dev": { "friendsofphp/php-cs-fixer": "@stable", - "nikic/php-parser": "dev-master", + "nikic/php-parser": "@stable", "php": "^8.0", "phpdocumentor/reflection-docblock": "@stable", "phpunit/phpunit": "@stable" @@ -1376,7 +1376,7 @@ "support": { "source": "https://github.com/JetBrains/phpstorm-stubs/tree/master" }, - "time": "2021-07-21T11:04:10+00:00" + "time": "2021-09-09T16:57:58+00:00" }, { "name": "nette/bootstrap", From 1513b3b8e21301c2f394a6cb64aa837626617aba Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 12 Sep 2021 11:13:34 +0200 Subject: [PATCH 0233/1284] Regression test Closes https://github.com/phpstan/phpstan/issues/5589 --- .../Rules/Methods/OverridingMethodRuleTest.php | 6 ++++++ tests/PHPStan/Rules/Methods/data/bug-5589.php | 15 +++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 tests/PHPStan/Rules/Methods/data/bug-5589.php diff --git a/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php b/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php index 06083ccdf0..53fd094fd6 100644 --- a/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php +++ b/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php @@ -510,4 +510,10 @@ public function testParameterTypeWidening(int $phpVersionId, array $errors): voi $this->analyse([__DIR__ . '/data/parameter-type-widening.php'], $errors); } + public function testBug5589(): void + { + $this->phpVersionId = PHP_VERSION_ID; + $this->analyse([__DIR__ . '/data/bug-5589.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-5589.php b/tests/PHPStan/Rules/Methods/data/bug-5589.php new file mode 100644 index 0000000000..64a388ecab --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-5589.php @@ -0,0 +1,15 @@ + + */ +class HelloWorld extends \ReflectionClass +{ + public static function export($argument, $return = false) + { + return ''; + } +} From 1c6bb6bc2ca60297ec0f1e0cd260dbe07820ffd3 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sun, 5 Sep 2021 13:05:14 +0200 Subject: [PATCH 0234/1284] Add support for multiple wildcards in const type annotations, fixes phpstan/phpstan#5534 --- composer.json | 2 +- composer.lock | 14 +++++++------- src/PhpDoc/TypeNodeResolver.php | 7 ++++--- .../Analyser/data/const-expr-phpdoc-type.php | 16 ++++++++++++++-- 4 files changed, 26 insertions(+), 13 deletions(-) diff --git a/composer.json b/composer.json index 466404310a..c8e60e7f66 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "4.3.67", "phpstan/php-8-stubs": "^0.1.23", - "phpstan/phpdoc-parser": "^0.5.6", + "phpstan/phpdoc-parser": "^0.5.7", "react/child-process": "^0.6.1", "react/event-loop": "^1.1", "react/http": "^1.1", diff --git a/composer.lock b/composer.lock index 2dd8777f5a..c4a85ef6d4 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "09336621e0dad025e2d6fffd6301ba27", + "content-hash": "5f96f805a466ae4dbe320e30ca8f2f7e", "packages": [ { "name": "clue/block-react", @@ -2177,16 +2177,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "0.5.6", + "version": "0.5.7", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "fac86158ffc7392e49636f77e63684c026df43b8" + "reference": "816e826ce0b7fb32098d8cb6de62511ce6021cea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/fac86158ffc7392e49636f77e63684c026df43b8", - "reference": "fac86158ffc7392e49636f77e63684c026df43b8", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/816e826ce0b7fb32098d8cb6de62511ce6021cea", + "reference": "816e826ce0b7fb32098d8cb6de62511ce6021cea", "shasum": "" }, "require": { @@ -2220,9 +2220,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/0.5.6" + "source": "https://github.com/phpstan/phpdoc-parser/tree/0.5.7" }, - "time": "2021-08-31T08:08:22+00:00" + "time": "2021-09-12T11:52:00+00:00" }, { "name": "psr/container", diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index a4e4102155..83d362090b 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -702,11 +702,12 @@ private function resolveConstTypeNode(ConstTypeNode $typeNode, NameScope $nameSc $classReflection = $this->getReflectionProvider()->getClass($className); $constantName = $constExpr->name; - if (Strings::endsWith($constantName, '*')) { - $constantNameStartsWith = Strings::substring($constantName, 0, Strings::length($constantName) - 1); + if (Strings::contains($constantName, '*')) { + // convert * into .*? and escape everything else so the constants can be matched against the pattern + $pattern = '{^' . str_replace('\\*', '.*?', preg_quote($constantName)) . '$}D'; $constantTypes = []; foreach ($classReflection->getNativeReflection()->getConstants() as $classConstantName => $constantValue) { - if (!Strings::startsWith($classConstantName, $constantNameStartsWith)) { + if (Strings::match($classConstantName, $pattern) === null) { continue; } diff --git a/tests/PHPStan/Analyser/data/const-expr-phpdoc-type.php b/tests/PHPStan/Analyser/data/const-expr-phpdoc-type.php index 317c91c7b9..ab79fb6a46 100644 --- a/tests/PHPStan/Analyser/data/const-expr-phpdoc-type.php +++ b/tests/PHPStan/Analyser/data/const-expr-phpdoc-type.php @@ -10,6 +10,9 @@ class Foo public const SOME_CONSTANT = 1; public const SOME_OTHER_CONSTANT = 2; + public const YET_ONE_CONST = 3; + public const YET_ANOTHER_CONST = 4; + public const DUMMY_SOME_CONSTANT = 99; // should only match the * /** * @param 'foo'|'bar' $one @@ -21,6 +24,9 @@ class Foo * @param 234 $seven * @param self::SOME_OTHER_* $eight * @param self::* $nine + * @param self::*_CONST $ten + * @param self::YET_*_CONST $eleven + * @param self::*OTHER* $twelve */ public function doFoo( $one, @@ -31,7 +37,10 @@ public function doFoo( $six, $seven, $eight, - $nine + $nine, + $ten, + $eleven, + $twelve ) { assertType("'bar'|'foo'", $one); @@ -42,7 +51,10 @@ public function doFoo( assertType('1.0', $six); assertType('234', $seven); assertType('2', $eight); - assertType('1|2', $nine); + assertType('1|2|3|4|99', $nine); + assertType('3|4', $ten); + assertType('3|4', $eleven); + assertType('2|4', $twelve); } } From 05c5ad3c89b089ef3f931d0720be7df5b2370791 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 12 Sep 2021 14:01:08 +0200 Subject: [PATCH 0235/1284] A little bit more RAM for phpstan-static-reflection --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 0bfa9d747e..9d85ecb989 100644 --- a/Makefile +++ b/Makefile @@ -53,7 +53,7 @@ phpstan: php bin/phpstan clear-result-cache -q && php -d memory_limit=768M bin/phpstan phpstan-static-reflection: - php bin/phpstan clear-result-cache -q && php -d memory_limit=768M bin/phpstan analyse -c phpstan-static-reflection.neon + php bin/phpstan clear-result-cache -q && php -d memory_limit=800M bin/phpstan analyse -c phpstan-static-reflection.neon phpstan-result-cache: php -d memory_limit=768M bin/phpstan From 33af274dc34fa44d2c85d689cfacdbff1a7480cf Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 12 Sep 2021 14:24:33 +0200 Subject: [PATCH 0236/1284] support unary-minus on IntegerRangeType --- src/Analyser/MutatingScope.php | 10 ++++++++++ .../Analyser/data/integer-range-types.php | 17 +++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 0adf29cc8d..dcdf9c4ce7 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -1006,6 +1006,7 @@ private function resolveType(Expr $node): Type if ($node instanceof Node\Expr\UnaryMinus) { $type = $this->getType($node->expr)->toNumber(); $scalarValues = TypeUtils::getConstantScalars($type); + if (count($scalarValues) > 0) { $newTypes = []; foreach ($scalarValues as $scalarValue) { @@ -1019,6 +1020,15 @@ private function resolveType(Expr $node): Type return TypeCombinator::union(...$newTypes); } + if ($type instanceof IntegerRangeType) { + $negativeRange = $this->resolveType(new Node\Expr\BinaryOp\Mul($node->expr, new LNumber(-1))); + + if ( $negativeRange instanceof IntegerRangeType && ($negativeRange->getMin() === null || $negativeRange->getMax() === null)) { + return IntegerRangeType::fromInterval($negativeRange->getMax(), $negativeRange->getMin()); + } + return $negativeRange; + } + return $type; } diff --git a/tests/PHPStan/Analyser/data/integer-range-types.php b/tests/PHPStan/Analyser/data/integer-range-types.php index 45e3c55717..a65885182e 100644 --- a/tests/PHPStan/Analyser/data/integer-range-types.php +++ b/tests/PHPStan/Analyser/data/integer-range-types.php @@ -278,4 +278,21 @@ public function math($i, $j, $z, $pi, $r1, $r2, $rMin, $rMax, $x, $y) { assertType('5|10|15|20|30', $x / $y); } + + /** + * @param int<1, 10> $r1 + * @param int<-5, 10> $r2 + * @param int $rMin + * @param int<5, max> $rMax + * @param int<0, 50> $rZero + */ + public function unaryMinus($r1, $r2, $rMin, $rMax, $rZero) { + + assertType('int<-10, -1>', -$r1); + assertType('int<-10, 5>', -$r2); + assertType('int<-5, max>', -$rMin); + assertType('int', -$rMax); + assertType('int<-50, 0>', -$rZero); + } + } From bc97698b388c2cefd725ba6f8ffe5e576e0ada6f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 12 Sep 2021 14:54:26 +0200 Subject: [PATCH 0237/1284] ClosureFromCallableDynamicReturnTypeExtension - small fix --- .../ClosureFromCallableDynamicReturnTypeExtension.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Type/Php/ClosureFromCallableDynamicReturnTypeExtension.php b/src/Type/Php/ClosureFromCallableDynamicReturnTypeExtension.php index 8f3cf04574..39e048a883 100644 --- a/src/Type/Php/ClosureFromCallableDynamicReturnTypeExtension.php +++ b/src/Type/Php/ClosureFromCallableDynamicReturnTypeExtension.php @@ -5,6 +5,7 @@ use PhpParser\Node\Expr\StaticCall; use PHPStan\Analyser\Scope; use PHPStan\Reflection\MethodReflection; +use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Type\ClosureType; use PHPStan\Type\ErrorType; use PHPStan\Type\Type; @@ -25,6 +26,14 @@ public function isStaticMethodSupported(MethodReflection $methodReflection): boo public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): Type { + if (!isset($methodCall->args[0])) { + return ParametersAcceptorSelector::selectFromArgs( + $scope, + $methodCall->args, + $methodReflection->getVariants() + )->getReturnType(); + } + $callableType = $scope->getType($methodCall->args[0]->value); if ($callableType->isCallable()->no()) { return new ErrorType(); From ca7deb31a7a0060385a7dc0c35f3ced553407475 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 12 Sep 2021 14:08:09 +0200 Subject: [PATCH 0238/1284] Hardcode array_filter/array_map handling in ParametersAcceptorSelector, not NodeScopeResolver + Scope --- src/Analyser/NodeScopeResolver.php | 44 ---------- src/Reflection/ParametersAcceptorSelector.php | 86 +++++++++++++++++++ .../ArrayMapFunctionReturnTypeExtension.php | 12 +-- tests/PHPStan/Analyser/data/bug-4097.php | 14 ++- .../CallToFunctionParametersRuleTest.php | 11 ++- .../PHPStan/Rules/Functions/data/bug-5609.php | 18 ++++ 6 files changed, 133 insertions(+), 52 deletions(-) create mode 100644 tests/PHPStan/Rules/Functions/data/bug-5609.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index fcc8275ecc..06ee0c3069 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -90,14 +90,11 @@ use PHPStan\Reflection\Native\NativeMethodReflection; use PHPStan\Reflection\ParametersAcceptor; use PHPStan\Reflection\ParametersAcceptorSelector; -use PHPStan\Reflection\PassedByReference; -use PHPStan\Reflection\Php\DummyParameter; use PHPStan\Reflection\Php\PhpMethodReflection; use PHPStan\Reflection\ReflectionProvider; use PHPStan\TrinaryLogic; use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\ArrayType; -use PHPStan\Type\CallableType; use PHPStan\Type\ClosureType; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantArrayTypeBuilder; @@ -3156,47 +3153,6 @@ private function processArgs( $scope = $scope->assignVariable($argValue->name, new MixedType()); } } - - if ($calleeReflection instanceof FunctionReflection) { - if ( - $i === 0 - && $calleeReflection->getName() === 'array_map' - && isset($args[1]) - ) { - $parameterType = new CallableType([ - new DummyParameter('item', $scope->getType($args[1]->value)->getIterableValueType(), false, PassedByReference::createNo(), false, null), - ], new MixedType(), false); - } - - if ( - $i === 1 - && $calleeReflection->getName() === 'array_filter' - && isset($args[0]) - ) { - if (isset($args[2])) { - $mode = $scope->getType($args[2]->value); - if ($mode instanceof ConstantIntegerType) { - if ($mode->getValue() === ARRAY_FILTER_USE_KEY) { - $arrayFilterParameters = [ - new DummyParameter('key', $scope->getType($args[0]->value)->getIterableKeyType(), false, PassedByReference::createNo(), false, null), - ]; - } elseif ($mode->getValue() === ARRAY_FILTER_USE_BOTH) { - $arrayFilterParameters = [ - new DummyParameter('item', $scope->getType($args[0]->value)->getIterableValueType(), false, PassedByReference::createNo(), false, null), - new DummyParameter('key', $scope->getType($args[0]->value)->getIterableKeyType(), false, PassedByReference::createNo(), false, null), - ]; - } - } - } - $parameterType = new CallableType( - $arrayFilterParameters ?? [ - new DummyParameter('item', $scope->getType($args[0]->value)->getIterableValueType(), false, PassedByReference::createNo(), false, null), - ], - new MixedType(), - false - ); - } - } } $originalScope = $scope; diff --git a/src/Reflection/ParametersAcceptorSelector.php b/src/Reflection/ParametersAcceptorSelector.php index a809100402..b97fe62cc9 100644 --- a/src/Reflection/ParametersAcceptorSelector.php +++ b/src/Reflection/ParametersAcceptorSelector.php @@ -2,9 +2,14 @@ namespace PHPStan\Reflection; +use PhpParser\Node\Expr\FuncCall; +use PhpParser\Node\Name; use PHPStan\Analyser\Scope; use PHPStan\Reflection\Native\NativeParameterReflection; +use PHPStan\Reflection\Php\DummyParameter; use PHPStan\TrinaryLogic; +use PHPStan\Type\CallableType; +use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Generic\TemplateTypeMap; use PHPStan\Type\MixedType; use PHPStan\Type\TypeCombinator; @@ -43,6 +48,87 @@ public static function selectFromArgs( { $types = []; $unpack = false; + if (count($args) > 0 && count($parametersAcceptors) > 0) { + $functionName = null; + $argParent = $args[0]->getAttribute('parent'); + if ($argParent instanceof FuncCall && $argParent->name instanceof Name) { + $functionName = $argParent->name->toLowerString(); + } + if ( + $functionName === 'array_map' + && isset($args[1]) + ) { + $acceptor = $parametersAcceptors[0]; + $parameters = $acceptor->getParameters(); + $parameters[0] = new NativeParameterReflection( + $parameters[0]->getName(), + $parameters[0]->isOptional(), + new CallableType([ + new DummyParameter('item', $scope->getType($args[1]->value)->getIterableValueType(), false, PassedByReference::createNo(), false, null), + ], new MixedType(), false), + $parameters[0]->passedByReference(), + $parameters[0]->isVariadic(), + $parameters[0]->getDefaultValue() + ); + $parametersAcceptors = [ + new FunctionVariant( + $acceptor->getTemplateTypeMap(), + $acceptor->getResolvedTemplateTypeMap(), + $parameters, + $acceptor->isVariadic(), + $acceptor->getReturnType() + ), + ]; + } + + if ( + $functionName === 'array_filter' + && isset($args[0]) + ) { + if (isset($args[2])) { + $mode = $scope->getType($args[2]->value); + if ($mode instanceof ConstantIntegerType) { + if ($mode->getValue() === ARRAY_FILTER_USE_KEY) { + $arrayFilterParameters = [ + new DummyParameter('key', $scope->getType($args[0]->value)->getIterableKeyType(), false, PassedByReference::createNo(), false, null), + ]; + } elseif ($mode->getValue() === ARRAY_FILTER_USE_BOTH) { + $arrayFilterParameters = [ + new DummyParameter('item', $scope->getType($args[0]->value)->getIterableValueType(), false, PassedByReference::createNo(), false, null), + new DummyParameter('key', $scope->getType($args[0]->value)->getIterableKeyType(), false, PassedByReference::createNo(), false, null), + ]; + } + } + } + + $acceptor = $parametersAcceptors[0]; + $parameters = $acceptor->getParameters(); + $parameters[1] = new NativeParameterReflection( + $parameters[1]->getName(), + $parameters[1]->isOptional(), + new CallableType( + $arrayFilterParameters ?? [ + new DummyParameter('item', $scope->getType($args[0]->value)->getIterableValueType(), false, PassedByReference::createNo(), false, null), + ], + new MixedType(), + false + ), + $parameters[1]->passedByReference(), + $parameters[1]->isVariadic(), + $parameters[1]->getDefaultValue() + ); + $parametersAcceptors = [ + new FunctionVariant( + $acceptor->getTemplateTypeMap(), + $acceptor->getResolvedTemplateTypeMap(), + $parameters, + $acceptor->isVariadic(), + $acceptor->getReturnType() + ), + ]; + } + } + foreach ($args as $arg) { $type = $scope->getType($arg->value); if ($arg->unpack) { diff --git a/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php b/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php index 38db53df30..3b4869eff6 100644 --- a/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php @@ -10,6 +10,7 @@ use PHPStan\Type\ArrayType; use PHPStan\Type\Constant\ConstantArrayTypeBuilder; use PHPStan\Type\MixedType; +use PHPStan\Type\NeverType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use PHPStan\Type\TypeUtils; @@ -30,12 +31,11 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $valueType = new MixedType(); $callableType = $scope->getType($functionCall->args[0]->value); - if (!$callableType->isCallable()->no()) { - $valueType = ParametersAcceptorSelector::selectFromArgs( - $scope, - $functionCall->args, - $callableType->getCallableParametersAcceptors($scope) - )->getReturnType(); + if ($callableType->isCallable()->yes()) { + $valueType = new NeverType(); + foreach ($callableType->getCallableParametersAcceptors($scope) as $parametersAcceptor) { + $valueType = TypeCombinator::union($valueType, $parametersAcceptor->getReturnType()); + } } $mappedArrayType = new ArrayType( diff --git a/tests/PHPStan/Analyser/data/bug-4097.php b/tests/PHPStan/Analyser/data/bug-4097.php index cd27b2f0f7..c18ad8ec14 100644 --- a/tests/PHPStan/Analyser/data/bug-4097.php +++ b/tests/PHPStan/Analyser/data/bug-4097.php @@ -11,6 +11,18 @@ class Bar {} */ class SnapshotRepository { + + /** @var T[] */ + private $entities; + + /** + * @param T[] $entities + */ + public function __construct(array $entities) + { + $this->entities = $entities; + } + /** * @return \Traversable */ @@ -18,7 +30,7 @@ public function findAllSnapshots(): \Traversable { yield from \array_map( \Closure::fromCallable([$this, 'buildSnapshot']), - [] + $this->entities ); } diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 2285c6d271..eb36c79d30 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -15,12 +15,15 @@ class CallToFunctionParametersRuleTest extends \PHPStan\Testing\RuleTestCase { + /** @var bool */ + private $checkExplicitMixed = false; + protected function getRule(): \PHPStan\Rules\Rule { $broker = $this->createReflectionProvider(); return new CallToFunctionParametersRule( $broker, - new FunctionCallParametersCheck(new RuleLevelHelper($broker, true, false, true, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(true), true, true, true, true, true) + new FunctionCallParametersCheck(new RuleLevelHelper($broker, true, false, true, $this->checkExplicitMixed), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(true), true, true, true, true, true) ); } @@ -811,4 +814,10 @@ public function testProcOpen(): void ]); } + public function testBug5609(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/bug-5609.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/bug-5609.php b/tests/PHPStan/Rules/Functions/data/bug-5609.php new file mode 100644 index 0000000000..503f4fe4fb --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-5609.php @@ -0,0 +1,18 @@ +entities, function (\stdClass $std): bool { + return true; + }); + } + +} From 49c9a89c852116fc0b18ba75355eeb7e4345cd2f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 12 Sep 2021 15:15:02 +0200 Subject: [PATCH 0239/1284] NodeScopeResolver - return some code --- src/Analyser/NodeScopeResolver.php | 44 ++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 06ee0c3069..fcc8275ecc 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -90,11 +90,14 @@ use PHPStan\Reflection\Native\NativeMethodReflection; use PHPStan\Reflection\ParametersAcceptor; use PHPStan\Reflection\ParametersAcceptorSelector; +use PHPStan\Reflection\PassedByReference; +use PHPStan\Reflection\Php\DummyParameter; use PHPStan\Reflection\Php\PhpMethodReflection; use PHPStan\Reflection\ReflectionProvider; use PHPStan\TrinaryLogic; use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\ArrayType; +use PHPStan\Type\CallableType; use PHPStan\Type\ClosureType; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantArrayTypeBuilder; @@ -3153,6 +3156,47 @@ private function processArgs( $scope = $scope->assignVariable($argValue->name, new MixedType()); } } + + if ($calleeReflection instanceof FunctionReflection) { + if ( + $i === 0 + && $calleeReflection->getName() === 'array_map' + && isset($args[1]) + ) { + $parameterType = new CallableType([ + new DummyParameter('item', $scope->getType($args[1]->value)->getIterableValueType(), false, PassedByReference::createNo(), false, null), + ], new MixedType(), false); + } + + if ( + $i === 1 + && $calleeReflection->getName() === 'array_filter' + && isset($args[0]) + ) { + if (isset($args[2])) { + $mode = $scope->getType($args[2]->value); + if ($mode instanceof ConstantIntegerType) { + if ($mode->getValue() === ARRAY_FILTER_USE_KEY) { + $arrayFilterParameters = [ + new DummyParameter('key', $scope->getType($args[0]->value)->getIterableKeyType(), false, PassedByReference::createNo(), false, null), + ]; + } elseif ($mode->getValue() === ARRAY_FILTER_USE_BOTH) { + $arrayFilterParameters = [ + new DummyParameter('item', $scope->getType($args[0]->value)->getIterableValueType(), false, PassedByReference::createNo(), false, null), + new DummyParameter('key', $scope->getType($args[0]->value)->getIterableKeyType(), false, PassedByReference::createNo(), false, null), + ]; + } + } + } + $parameterType = new CallableType( + $arrayFilterParameters ?? [ + new DummyParameter('item', $scope->getType($args[0]->value)->getIterableValueType(), false, PassedByReference::createNo(), false, null), + ], + new MixedType(), + false + ); + } + } } $originalScope = $scope; From 9c3b76539a10453fffc5c30e213ffab9dd03c737 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 12 Sep 2021 15:15:57 +0200 Subject: [PATCH 0240/1284] ParametersAcceptorSelector - advanced array_map/array_filter inference only for bleeding edge --- src/Reflection/ParametersAcceptorSelector.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Reflection/ParametersAcceptorSelector.php b/src/Reflection/ParametersAcceptorSelector.php index b97fe62cc9..c3411dc660 100644 --- a/src/Reflection/ParametersAcceptorSelector.php +++ b/src/Reflection/ParametersAcceptorSelector.php @@ -5,6 +5,7 @@ use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Name; use PHPStan\Analyser\Scope; +use PHPStan\DependencyInjection\BleedingEdgeToggle; use PHPStan\Reflection\Native\NativeParameterReflection; use PHPStan\Reflection\Php\DummyParameter; use PHPStan\TrinaryLogic; @@ -48,7 +49,11 @@ public static function selectFromArgs( { $types = []; $unpack = false; - if (count($args) > 0 && count($parametersAcceptors) > 0) { + if ( + BleedingEdgeToggle::isBleedingEdge() + && count($args) > 0 + && count($parametersAcceptors) > 0 + ) { $functionName = null; $argParent = $args[0]->getAttribute('parent'); if ($argParent instanceof FuncCall && $argParent->name instanceof Name) { From 3e0ecec8fc2482e412f67f6a62bdb6542be0fe5d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 12 Sep 2021 16:37:14 +0200 Subject: [PATCH 0241/1284] array_map - understand call with multiple arrays --- src/Analyser/MutatingScope.php | 17 ++++- src/Analyser/NodeScopeResolver.php | 3 +- src/Reflection/ParametersAcceptorSelector.php | 18 +++++- .../ArrayMapFunctionReturnTypeExtension.php | 37 ++++++----- .../Analyser/NodeScopeResolverTest.php | 1 + .../Analyser/data/array_map_multiple.php | 21 +++++++ .../CallToFunctionParametersRuleTest.php | 23 +++++++ .../Functions/data/array_map_multiple.php | 63 +++++++++++++++++++ 8 files changed, 162 insertions(+), 21 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/array_map_multiple.php create mode 100644 tests/PHPStan/Rules/Functions/data/array_map_multiple.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index dcdf9c4ce7..6bf6fb1cbc 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -1517,9 +1517,20 @@ private function resolveType(Expr $node): Type && $argOrder === 0 && isset($funcCall->args[1]) ) { - $callableParameters = [ - new DummyParameter('item', $this->getType($funcCall->args[1]->value)->getIterableValueType(), false, PassedByReference::createNo(), false, null), - ]; + if (!isset($funcCall->args[2])) { + $callableParameters = [ + new DummyParameter('item', $this->getType($funcCall->args[1]->value)->getIterableValueType(), false, PassedByReference::createNo(), false, null), + ]; + } else { + $callableParameters = []; + foreach ($funcCall->args as $i => $funcCallArg) { + if ($i === 0) { + continue; + } + + $callableParameters[] = new DummyParameter('item', $this->getType($funcCallArg->value)->getIterableValueType(), false, PassedByReference::createNo(), false, null); + } + } } } } diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index fcc8275ecc..7cfbdc6ecc 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -50,6 +50,7 @@ use PHPStan\BetterReflection\Reflector\ClassReflector; use PHPStan\BetterReflection\SourceLocator\Ast\Strategy\NodeToReflection; use PHPStan\BetterReflection\SourceLocator\Located\LocatedSource; +use PHPStan\DependencyInjection\BleedingEdgeToggle; use PHPStan\DependencyInjection\Reflection\ClassReflectionExtensionRegistryProvider; use PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider; use PHPStan\File\FileHelper; @@ -3157,7 +3158,7 @@ private function processArgs( } } - if ($calleeReflection instanceof FunctionReflection) { + if (!BleedingEdgeToggle::isBleedingEdge() && $calleeReflection instanceof FunctionReflection) { if ( $i === 0 && $calleeReflection->getName() === 'array_map' diff --git a/src/Reflection/ParametersAcceptorSelector.php b/src/Reflection/ParametersAcceptorSelector.php index c3411dc660..61faa63397 100644 --- a/src/Reflection/ParametersAcceptorSelector.php +++ b/src/Reflection/ParametersAcceptorSelector.php @@ -65,12 +65,24 @@ public static function selectFromArgs( ) { $acceptor = $parametersAcceptors[0]; $parameters = $acceptor->getParameters(); + if (!isset($args[2])) { + $callbackParameters = [ + new DummyParameter('item', $scope->getType($args[1]->value)->getIterableValueType(), false, PassedByReference::createNo(), false, null), + ]; + } else { + $callbackParameters = []; + foreach ($args as $i => $arg) { + if ($i === 0) { + continue; + } + + $callbackParameters[] = new DummyParameter('item', $scope->getType($arg->value)->getIterableValueType(), false, PassedByReference::createNo(), false, null); + } + } $parameters[0] = new NativeParameterReflection( $parameters[0]->getName(), $parameters[0]->isOptional(), - new CallableType([ - new DummyParameter('item', $scope->getType($args[1]->value)->getIterableValueType(), false, PassedByReference::createNo(), false, null), - ], new MixedType(), false), + new CallableType($callbackParameters, new MixedType(), false), $parameters[0]->passedByReference(), $parameters[0]->isVariadic(), $parameters[0]->getDefaultValue() diff --git a/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php b/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php index 3b4869eff6..7e2634ba50 100644 --- a/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php @@ -9,6 +9,7 @@ use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\ArrayType; use PHPStan\Type\Constant\ConstantArrayTypeBuilder; +use PHPStan\Type\IntegerType; use PHPStan\Type\MixedType; use PHPStan\Type\NeverType; use PHPStan\Type\Type; @@ -44,23 +45,31 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, ); $arrayType = $scope->getType($functionCall->args[1]->value); $constantArrays = TypeUtils::getConstantArrays($arrayType); - if (count($constantArrays) > 0) { - $arrayTypes = []; - foreach ($constantArrays as $constantArray) { - $returnedArrayBuilder = ConstantArrayTypeBuilder::createEmpty(); - foreach ($constantArray->getKeyTypes() as $keyType) { - $returnedArrayBuilder->setOffsetValueType( - $keyType, - $valueType - ); + + if (!isset($functionCall->args[2])) { + if (count($constantArrays) > 0) { + $arrayTypes = []; + foreach ($constantArrays as $constantArray) { + $returnedArrayBuilder = ConstantArrayTypeBuilder::createEmpty(); + foreach ($constantArray->getKeyTypes() as $keyType) { + $returnedArrayBuilder->setOffsetValueType( + $keyType, + $valueType + ); + } + $arrayTypes[] = $returnedArrayBuilder->getArray(); } - $arrayTypes[] = $returnedArrayBuilder->getArray(); - } - $mappedArrayType = TypeCombinator::union(...$arrayTypes); - } elseif ($arrayType->isArray()->yes()) { + $mappedArrayType = TypeCombinator::union(...$arrayTypes); + } elseif ($arrayType->isArray()->yes()) { + $mappedArrayType = TypeCombinator::intersect(new ArrayType( + $arrayType->getIterableKeyType(), + $valueType + ), ...TypeUtils::getAccessoryTypes($arrayType)); + } + } else { $mappedArrayType = TypeCombinator::intersect(new ArrayType( - $arrayType->getIterableKeyType(), + new IntegerType(), $valueType ), ...TypeUtils::getAccessoryTypes($arrayType)); } diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 5b93dd30c6..937998df7e 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -502,6 +502,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-1870.php'); yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/bug-5562.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5615.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/array_map_multiple.php'); } /** diff --git a/tests/PHPStan/Analyser/data/array_map_multiple.php b/tests/PHPStan/Analyser/data/array_map_multiple.php new file mode 100644 index 0000000000..651ffde7f7 --- /dev/null +++ b/tests/PHPStan/Analyser/data/array_map_multiple.php @@ -0,0 +1,21 @@ + $i], ['bar' => $s]); + assertType('array&nonEmpty', $result); + } + +} diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index eb36c79d30..85eb9416dd 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -820,4 +820,27 @@ public function testBug5609(): void $this->analyse([__DIR__ . '/data/bug-5609.php'], []); } + public function dataArrayMapMultiple(): array + { + return [ + [true], + [false], + ]; + } + + /** + * @dataProvider dataArrayMapMultiple + * @param bool $checkExplicitMixed + */ + public function testArrayMapMultiple(bool $checkExplicitMixed): void + { + $this->checkExplicitMixed = $checkExplicitMixed; + $this->analyse([__DIR__ . '/data/array_map_multiple.php'], [ + [ + 'Parameter #1 $callback of function array_map expects callable(1|2, \'bar\'|\'foo\'): mixed, Closure(int, int): void given.', + 58, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/array_map_multiple.php b/tests/PHPStan/Rules/Functions/data/array_map_multiple.php new file mode 100644 index 0000000000..06d872a41f --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/array_map_multiple.php @@ -0,0 +1,63 @@ + + */ + protected $items = []; + + /** + * @param array $items + * @return void + */ + public function __construct($items) + { + $this->items = $items; + } + + /** + * @template TMapValue + * + * @param callable(TValue, TKey): TMapValue $callback + * @return self + */ + public function map(callable $callback) + { + $keys = array_keys($this->items); + + $items = array_map($callback, $this->items, $keys); + + return new self(array_combine($keys, $items)); + } + + /** + * @return array + */ + public function all() + { + return $this->items; + } +} + +class Foo +{ + + public function doFoo(): void + { + array_map(function (int $a, string $b) { + + }, [1, 2], ['foo', 'bar']); + + array_map(function (int $a, int $b) { + + }, [1, 2], ['foo', 'bar']); + } + +} From 8596663270b7b45d227e2184264a1fed7f5e8a57 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 12 Sep 2021 17:12:27 +0200 Subject: [PATCH 0242/1284] Regression tests Closes https://github.com/phpstan/phpstan/issues/5356 Closes https://github.com/phpstan/phpstan/issues/1954 --- .../CallToFunctionParametersRuleTest.php | 28 +++++++++++++++++++ .../PHPStan/Rules/Functions/data/bug-1954.php | 8 ++++++ .../PHPStan/Rules/Functions/data/bug-5356.php | 24 ++++++++++++++++ 3 files changed, 60 insertions(+) create mode 100644 tests/PHPStan/Rules/Functions/data/bug-1954.php create mode 100644 tests/PHPStan/Rules/Functions/data/bug-5356.php diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 85eb9416dd..62a0835771 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -843,4 +843,32 @@ public function testArrayMapMultiple(bool $checkExplicitMixed): void ]); } + public function testBug5356(): void + { + if (PHP_VERSION_ID < 70400 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 7.4.'); + } + + $this->analyse([__DIR__ . '/data/bug-5356.php'], [ + [ + 'Parameter #1 $callback of function array_map expects callable(string): mixed, Closure(array): \'a\' given.', + 13, + ], + [ + 'Parameter #1 $callback of function array_map expects callable(string): mixed, Closure(array): \'a\' given.', + 21, + ], + ]); + } + + public function testBug1954(): void + { + $this->analyse([__DIR__ . '/data/bug-1954.php'], [ + [ + 'Parameter #1 $callback of function array_map expects callable(1|stdClass): mixed, Closure(string): string given.', + 7, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/bug-1954.php b/tests/PHPStan/Rules/Functions/data/bug-1954.php new file mode 100644 index 0000000000..38e4354cbc --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-1954.php @@ -0,0 +1,8 @@ += 7.4 + +namespace Bug5356; + +class Foo +{ + + public function doFoo(): void + { + /** @var array{name: string, collectors: string[]} $array */ + $array = []; + + array_map(static fn(array $_): string => 'a', $array['collectors']); + } + + public function doBar(): void + { + /** @var array{name: string, collectors: string[]} $array */ + $array = []; + + array_map(static function(array $_): string { return 'a'; }, $array['collectors']); + } + +} From 3624e666676614ad18c02608098de96d8319d7be Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 12 Sep 2021 17:45:33 +0200 Subject: [PATCH 0243/1284] Revert Regression test This reverts commit 1513b3b8e21301c2f394a6cb64aa837626617aba. --- .../Rules/Methods/OverridingMethodRuleTest.php | 6 ------ tests/PHPStan/Rules/Methods/data/bug-5589.php | 15 --------------- 2 files changed, 21 deletions(-) delete mode 100644 tests/PHPStan/Rules/Methods/data/bug-5589.php diff --git a/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php b/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php index 53fd094fd6..06083ccdf0 100644 --- a/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php +++ b/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php @@ -510,10 +510,4 @@ public function testParameterTypeWidening(int $phpVersionId, array $errors): voi $this->analyse([__DIR__ . '/data/parameter-type-widening.php'], $errors); } - public function testBug5589(): void - { - $this->phpVersionId = PHP_VERSION_ID; - $this->analyse([__DIR__ . '/data/bug-5589.php'], []); - } - } diff --git a/tests/PHPStan/Rules/Methods/data/bug-5589.php b/tests/PHPStan/Rules/Methods/data/bug-5589.php deleted file mode 100644 index 64a388ecab..0000000000 --- a/tests/PHPStan/Rules/Methods/data/bug-5589.php +++ /dev/null @@ -1,15 +0,0 @@ - - */ -class HelloWorld extends \ReflectionClass -{ - public static function export($argument, $return = false) - { - return ''; - } -} From 8b3382aceac801b9801fccda253033d8e8e5f655 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 12 Sep 2021 17:20:07 +0200 Subject: [PATCH 0244/1284] Bleeding edge - check that function with `@throws void` does not have an explicit throw point --- conf/bleedingEdge.neon | 1 + conf/config.level3.neon | 16 ++++ conf/config.neon | 4 +- ...VoidFunctionWithExplicitThrowPointRule.php | 81 ++++++++++++++++++ ...wsVoidMethodWithExplicitThrowPointRule.php | 82 +++++++++++++++++++ ...FunctionWithExplicitThrowPointRuleTest.php | 70 ++++++++++++++++ ...idMethodWithExplicitThrowPointRuleTest.php | 70 ++++++++++++++++ .../Exceptions/data/throws-void-function.php | 16 ++++ .../Exceptions/data/throws-void-method.php | 22 +++++ 9 files changed, 361 insertions(+), 1 deletion(-) create mode 100644 src/Rules/Exceptions/ThrowsVoidFunctionWithExplicitThrowPointRule.php create mode 100644 src/Rules/Exceptions/ThrowsVoidMethodWithExplicitThrowPointRule.php create mode 100644 tests/PHPStan/Rules/Exceptions/ThrowsVoidFunctionWithExplicitThrowPointRuleTest.php create mode 100644 tests/PHPStan/Rules/Exceptions/ThrowsVoidMethodWithExplicitThrowPointRuleTest.php create mode 100644 tests/PHPStan/Rules/Exceptions/data/throws-void-function.php create mode 100644 tests/PHPStan/Rules/Exceptions/data/throws-void-method.php diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index bfdd57db17..245fb055b1 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -27,5 +27,6 @@ parameters: classConstants: true privateStaticCall: true overridingProperty: true + throwsVoid: true stubFiles: - ../stubs/arrayFunctions.stub diff --git a/conf/config.level3.neon b/conf/config.level3.neon index 45c6d129a0..f30bea1c29 100644 --- a/conf/config.level3.neon +++ b/conf/config.level3.neon @@ -20,6 +20,10 @@ rules: conditionalTags: PHPStan\Rules\Arrays\ArrayDestructuringRule: phpstan.rules.rule: %featureToggles.arrayDestructuring% + PHPStan\Rules\Exceptions\ThrowsVoidFunctionWithExplicitThrowPointRule: + phpstan.rules.rule: %featureToggles.throwsVoid% + PHPStan\Rules\Exceptions\ThrowsVoidMethodWithExplicitThrowPointRule: + phpstan.rules.rule: %featureToggles.throwsVoid% parameters: checkPhpDocMethodSignatures: true @@ -56,6 +60,18 @@ services: tags: - phpstan.rules.rule + - + class: PHPStan\Rules\Exceptions\ThrowsVoidFunctionWithExplicitThrowPointRule + arguments: + exceptionTypeResolver: @exceptionTypeResolver + missingCheckedExceptionInThrows: %exceptions.check.missingCheckedExceptionInThrows% + + - + class: PHPStan\Rules\Exceptions\ThrowsVoidMethodWithExplicitThrowPointRule + arguments: + exceptionTypeResolver: @exceptionTypeResolver + missingCheckedExceptionInThrows: %exceptions.check.missingCheckedExceptionInThrows% + - class: PHPStan\Rules\Functions\ReturnTypeRule arguments: diff --git a/conf/config.neon b/conf/config.neon index 46849b6b79..0fec097710 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -52,6 +52,7 @@ parameters: classConstants: false privateStaticCall: false overridingProperty: false + throwsVoid: false fileExtensions: - php checkAdvancedIsset: false @@ -237,7 +238,8 @@ parametersSchema: finalByPhpDocTag: bool(), classConstants: bool(), privateStaticCall: bool(), - overridingProperty: bool() + overridingProperty: bool(), + throwsVoid: bool() ]) fileExtensions: listOf(string()) checkAdvancedIsset: bool() diff --git a/src/Rules/Exceptions/ThrowsVoidFunctionWithExplicitThrowPointRule.php b/src/Rules/Exceptions/ThrowsVoidFunctionWithExplicitThrowPointRule.php new file mode 100644 index 0000000000..932f85555c --- /dev/null +++ b/src/Rules/Exceptions/ThrowsVoidFunctionWithExplicitThrowPointRule.php @@ -0,0 +1,81 @@ + + */ +class ThrowsVoidFunctionWithExplicitThrowPointRule implements Rule +{ + + private ExceptionTypeResolver $exceptionTypeResolver; + + private bool $missingCheckedExceptionInThrows; + + public function __construct( + ExceptionTypeResolver $exceptionTypeResolver, + bool $missingCheckedExceptionInThrows + ) + { + $this->exceptionTypeResolver = $exceptionTypeResolver; + $this->missingCheckedExceptionInThrows = $missingCheckedExceptionInThrows; + } + + public function getNodeType(): string + { + return FunctionReturnStatementsNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($this->missingCheckedExceptionInThrows) { + return []; + } + + $statementResult = $node->getStatementResult(); + $functionReflection = $scope->getFunction(); + if (!$functionReflection instanceof FunctionReflection) { + throw new \PHPStan\ShouldNotHappenException(); + } + + if (!$functionReflection->getThrowType() instanceof VoidType) { + return []; + } + + $errors = []; + foreach ($statementResult->getThrowPoints() as $throwPoint) { + if (!$throwPoint->isExplicit()) { + continue; + } + + foreach (TypeUtils::flattenTypes($throwPoint->getType()) as $throwPointType) { + if ( + $throwPointType instanceof TypeWithClassName + && $this->exceptionTypeResolver->isCheckedException($throwPointType->getClassName(), $throwPoint->getScope()) + ) { + continue; + } + + $errors[] = RuleErrorBuilder::message(sprintf( + 'Function %s() throws exception %s but the PHPDoc contains @throws void.', + $functionReflection->getName(), + $throwPointType->describe(VerbosityLevel::typeOnly()) + ))->line($throwPoint->getNode()->getLine())->build(); + } + } + + return $errors; + } + +} diff --git a/src/Rules/Exceptions/ThrowsVoidMethodWithExplicitThrowPointRule.php b/src/Rules/Exceptions/ThrowsVoidMethodWithExplicitThrowPointRule.php new file mode 100644 index 0000000000..6354f7c529 --- /dev/null +++ b/src/Rules/Exceptions/ThrowsVoidMethodWithExplicitThrowPointRule.php @@ -0,0 +1,82 @@ + + */ +class ThrowsVoidMethodWithExplicitThrowPointRule implements Rule +{ + + private ExceptionTypeResolver $exceptionTypeResolver; + + private bool $missingCheckedExceptionInThrows; + + public function __construct( + ExceptionTypeResolver $exceptionTypeResolver, + bool $missingCheckedExceptionInThrows + ) + { + $this->exceptionTypeResolver = $exceptionTypeResolver; + $this->missingCheckedExceptionInThrows = $missingCheckedExceptionInThrows; + } + + public function getNodeType(): string + { + return MethodReturnStatementsNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($this->missingCheckedExceptionInThrows) { + return []; + } + + $statementResult = $node->getStatementResult(); + $methodReflection = $scope->getFunction(); + if (!$methodReflection instanceof MethodReflection) { + throw new \PHPStan\ShouldNotHappenException(); + } + + if (!$methodReflection->getThrowType() instanceof VoidType) { + return []; + } + + $errors = []; + foreach ($statementResult->getThrowPoints() as $throwPoint) { + if (!$throwPoint->isExplicit()) { + continue; + } + + foreach (TypeUtils::flattenTypes($throwPoint->getType()) as $throwPointType) { + if ( + $throwPointType instanceof TypeWithClassName + && $this->exceptionTypeResolver->isCheckedException($throwPointType->getClassName(), $throwPoint->getScope()) + ) { + continue; + } + + $errors[] = RuleErrorBuilder::message(sprintf( + 'Method %s::%s() throws exception %s but the PHPDoc contains @throws void.', + $methodReflection->getDeclaringClass()->getDisplayName(), + $methodReflection->getName(), + $throwPointType->describe(VerbosityLevel::typeOnly()) + ))->line($throwPoint->getNode()->getLine())->build(); + } + } + + return $errors; + } + +} diff --git a/tests/PHPStan/Rules/Exceptions/ThrowsVoidFunctionWithExplicitThrowPointRuleTest.php b/tests/PHPStan/Rules/Exceptions/ThrowsVoidFunctionWithExplicitThrowPointRuleTest.php new file mode 100644 index 0000000000..61f1f3de96 --- /dev/null +++ b/tests/PHPStan/Rules/Exceptions/ThrowsVoidFunctionWithExplicitThrowPointRuleTest.php @@ -0,0 +1,70 @@ + + */ +class ThrowsVoidFunctionWithExplicitThrowPointRuleTest extends RuleTestCase +{ + + /** @var bool */ + private $missingCheckedExceptionInThrows; + + /** @var string[] */ + private $checkedExceptionClasses; + + protected function getRule(): Rule + { + return new ThrowsVoidFunctionWithExplicitThrowPointRule(new DefaultExceptionTypeResolver( + $this->createReflectionProvider(), + [], + [], + [], + $this->checkedExceptionClasses + ), $this->missingCheckedExceptionInThrows); + } + + public function dataRule(): array + { + return [ + [ + true, + [], + [], + ], + [ + false, + ['DifferentException'], + [ + [ + 'Function ThrowsVoidFunction\foo() throws exception ThrowsVoidFunction\MyException but the PHPDoc contains @throws void.', + 15, + ], + ], + ], + [ + false, + [\ThrowsVoidFunction\MyException::class], + [], + ], + ]; + } + + /** + * @dataProvider dataRule + * @param bool $missingCheckedExceptionInThrows + * @param string[] $checkedExceptionClasses + * @param mixed[] $errors + */ + public function testRule(bool $missingCheckedExceptionInThrows, array $checkedExceptionClasses, array $errors): void + { + $this->missingCheckedExceptionInThrows = $missingCheckedExceptionInThrows; + $this->checkedExceptionClasses = $checkedExceptionClasses; + $this->analyse([__DIR__ . '/data/throws-void-function.php'], $errors); + } + +} diff --git a/tests/PHPStan/Rules/Exceptions/ThrowsVoidMethodWithExplicitThrowPointRuleTest.php b/tests/PHPStan/Rules/Exceptions/ThrowsVoidMethodWithExplicitThrowPointRuleTest.php new file mode 100644 index 0000000000..df99360201 --- /dev/null +++ b/tests/PHPStan/Rules/Exceptions/ThrowsVoidMethodWithExplicitThrowPointRuleTest.php @@ -0,0 +1,70 @@ + + */ +class ThrowsVoidMethodWithExplicitThrowPointRuleTest extends RuleTestCase +{ + + /** @var bool */ + private $missingCheckedExceptionInThrows; + + /** @var string[] */ + private $checkedExceptionClasses; + + protected function getRule(): Rule + { + return new ThrowsVoidMethodWithExplicitThrowPointRule(new DefaultExceptionTypeResolver( + $this->createReflectionProvider(), + [], + [], + [], + $this->checkedExceptionClasses + ), $this->missingCheckedExceptionInThrows); + } + + public function dataRule(): array + { + return [ + [ + true, + [], + [], + ], + [ + false, + ['DifferentException'], + [ + [ + 'Method ThrowsVoidMethod\Foo::doFoo() throws exception ThrowsVoidMethod\MyException but the PHPDoc contains @throws void.', + 18, + ], + ], + ], + [ + false, + [\ThrowsVoidMethod\MyException::class], + [], + ], + ]; + } + + /** + * @dataProvider dataRule + * @param bool $missingCheckedExceptionInThrows + * @param string[] $checkedExceptionClasses + * @param mixed[] $errors + */ + public function testRule(bool $missingCheckedExceptionInThrows, array $checkedExceptionClasses, array $errors): void + { + $this->missingCheckedExceptionInThrows = $missingCheckedExceptionInThrows; + $this->checkedExceptionClasses = $checkedExceptionClasses; + $this->analyse([__DIR__ . '/data/throws-void-method.php'], $errors); + } + +} diff --git a/tests/PHPStan/Rules/Exceptions/data/throws-void-function.php b/tests/PHPStan/Rules/Exceptions/data/throws-void-function.php new file mode 100644 index 0000000000..d5a407ec13 --- /dev/null +++ b/tests/PHPStan/Rules/Exceptions/data/throws-void-function.php @@ -0,0 +1,16 @@ + Date: Sun, 12 Sep 2021 19:02:22 +0200 Subject: [PATCH 0245/1284] Fix --- patches/SessionHandler.patch | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 patches/SessionHandler.patch diff --git a/patches/SessionHandler.patch b/patches/SessionHandler.patch new file mode 100644 index 0000000000..5b05a02df5 --- /dev/null +++ b/patches/SessionHandler.patch @@ -0,0 +1,23 @@ +@package jetbrains/phpstorm-stubs +@version dev-master + +--- session/SessionHandler.php 2021-09-09 18:57:58.000000000 +0200 ++++ session/SessionHandler.php 2021-09-12 19:00:13.000000000 +0200 +@@ -137,7 +137,7 @@ + * Note this value is returned internally to PHP for processing. + *

+ */ +- public function validateId(string $id); ++ public function validateId($id); + + /** + * Update timestamp of a session +@@ -152,7 +152,7 @@ + *

+ * @return bool + */ +- public function updateTimestamp(string $id, string $data); ++ public function updateTimestamp($id, $data); + } + + /** From 3b6f0bf739c77e1fb8fb5116ae89cd70a2ac96c2 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 12 Sep 2021 19:12:31 +0200 Subject: [PATCH 0246/1284] range() of numeric-strings can produce array of float|int --- .../Php/RangeFunctionReturnTypeExtension.php | 3 ++- .../Analyser/NodeScopeResolverTest.php | 1 + .../Analyser/data/range-numeric-string.php | 22 +++++++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Analyser/data/range-numeric-string.php diff --git a/src/Type/Php/RangeFunctionReturnTypeExtension.php b/src/Type/Php/RangeFunctionReturnTypeExtension.php index 101cb8d09f..bc6bcee9fd 100644 --- a/src/Type/Php/RangeFunctionReturnTypeExtension.php +++ b/src/Type/Php/RangeFunctionReturnTypeExtension.php @@ -105,7 +105,8 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $numberType = new UnionType([new IntegerType(), new FloatType()]); $isNumber = $numberType->isSuperTypeOf($argType)->yes(); - if ($isNumber) { + $isNumericString = $argType->isNumericString()->yes(); + if ($isNumber || $isNumericString) { return new ArrayType(new IntegerType(), $numberType); } diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 937998df7e..f33db67112 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -503,6 +503,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/bug-5562.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5615.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/array_map_multiple.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/range-numeric-string.php'); } /** diff --git a/tests/PHPStan/Analyser/data/range-numeric-string.php b/tests/PHPStan/Analyser/data/range-numeric-string.php new file mode 100644 index 0000000000..faddec206b --- /dev/null +++ b/tests/PHPStan/Analyser/data/range-numeric-string.php @@ -0,0 +1,22 @@ +', range($a, $b)); + } + +} From 76800bfb578426817fabcedba5ba7ff695bdc74a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 12 Sep 2021 21:52:35 +0200 Subject: [PATCH 0247/1284] Fix --- ...VoidFunctionWithExplicitThrowPointRule.php | 5 +-- ...wsVoidMethodWithExplicitThrowPointRule.php | 5 +-- ...FunctionWithExplicitThrowPointRuleTest.php | 32 ++++++++++++++++++- ...idMethodWithExplicitThrowPointRuleTest.php | 32 ++++++++++++++++++- 4 files changed, 64 insertions(+), 10 deletions(-) diff --git a/src/Rules/Exceptions/ThrowsVoidFunctionWithExplicitThrowPointRule.php b/src/Rules/Exceptions/ThrowsVoidFunctionWithExplicitThrowPointRule.php index 932f85555c..b8c3a26e0e 100644 --- a/src/Rules/Exceptions/ThrowsVoidFunctionWithExplicitThrowPointRule.php +++ b/src/Rules/Exceptions/ThrowsVoidFunctionWithExplicitThrowPointRule.php @@ -39,10 +39,6 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { - if ($this->missingCheckedExceptionInThrows) { - return []; - } - $statementResult = $node->getStatementResult(); $functionReflection = $scope->getFunction(); if (!$functionReflection instanceof FunctionReflection) { @@ -63,6 +59,7 @@ public function processNode(Node $node, Scope $scope): array if ( $throwPointType instanceof TypeWithClassName && $this->exceptionTypeResolver->isCheckedException($throwPointType->getClassName(), $throwPoint->getScope()) + && $this->missingCheckedExceptionInThrows ) { continue; } diff --git a/src/Rules/Exceptions/ThrowsVoidMethodWithExplicitThrowPointRule.php b/src/Rules/Exceptions/ThrowsVoidMethodWithExplicitThrowPointRule.php index 6354f7c529..77f0a1d257 100644 --- a/src/Rules/Exceptions/ThrowsVoidMethodWithExplicitThrowPointRule.php +++ b/src/Rules/Exceptions/ThrowsVoidMethodWithExplicitThrowPointRule.php @@ -39,10 +39,6 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { - if ($this->missingCheckedExceptionInThrows) { - return []; - } - $statementResult = $node->getStatementResult(); $methodReflection = $scope->getFunction(); if (!$methodReflection instanceof MethodReflection) { @@ -63,6 +59,7 @@ public function processNode(Node $node, Scope $scope): array if ( $throwPointType instanceof TypeWithClassName && $this->exceptionTypeResolver->isCheckedException($throwPointType->getClassName(), $throwPoint->getScope()) + && $this->missingCheckedExceptionInThrows ) { continue; } diff --git a/tests/PHPStan/Rules/Exceptions/ThrowsVoidFunctionWithExplicitThrowPointRuleTest.php b/tests/PHPStan/Rules/Exceptions/ThrowsVoidFunctionWithExplicitThrowPointRuleTest.php index 61f1f3de96..710e90e77f 100644 --- a/tests/PHPStan/Rules/Exceptions/ThrowsVoidFunctionWithExplicitThrowPointRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/ThrowsVoidFunctionWithExplicitThrowPointRuleTest.php @@ -47,10 +47,40 @@ public function dataRule(): array ], ], [ - false, + true, [\ThrowsVoidFunction\MyException::class], [], ], + [ + true, + ['DifferentException'], + [ + [ + 'Function ThrowsVoidFunction\foo() throws exception ThrowsVoidFunction\MyException but the PHPDoc contains @throws void.', + 15, + ], + ], + ], + [ + false, + [], + [ + [ + 'Function ThrowsVoidFunction\foo() throws exception ThrowsVoidFunction\MyException but the PHPDoc contains @throws void.', + 15, + ], + ], + ], + [ + false, + [\ThrowsVoidFunction\MyException::class], + [ + [ + 'Function ThrowsVoidFunction\foo() throws exception ThrowsVoidFunction\MyException but the PHPDoc contains @throws void.', + 15, + ], + ], + ], ]; } diff --git a/tests/PHPStan/Rules/Exceptions/ThrowsVoidMethodWithExplicitThrowPointRuleTest.php b/tests/PHPStan/Rules/Exceptions/ThrowsVoidMethodWithExplicitThrowPointRuleTest.php index df99360201..15ba07e2ce 100644 --- a/tests/PHPStan/Rules/Exceptions/ThrowsVoidMethodWithExplicitThrowPointRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/ThrowsVoidMethodWithExplicitThrowPointRuleTest.php @@ -47,10 +47,40 @@ public function dataRule(): array ], ], [ - false, + true, [\ThrowsVoidMethod\MyException::class], [], ], + [ + true, + ['DifferentException'], + [ + [ + 'Method ThrowsVoidMethod\Foo::doFoo() throws exception ThrowsVoidMethod\MyException but the PHPDoc contains @throws void.', + 18, + ], + ], + ], + [ + false, + [], + [ + [ + 'Method ThrowsVoidMethod\Foo::doFoo() throws exception ThrowsVoidMethod\MyException but the PHPDoc contains @throws void.', + 18, + ], + ], + ], + [ + false, + [\ThrowsVoidMethod\MyException::class], + [ + [ + 'Method ThrowsVoidMethod\Foo::doFoo() throws exception ThrowsVoidMethod\MyException but the PHPDoc contains @throws void.', + 18, + ], + ], + ], ]; } From 6a8aa4e070e121f31358ade5eba7e5d1ff9fd674 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 12 Sep 2021 22:21:57 +0200 Subject: [PATCH 0248/1284] Open 1.0-dev --- .github/workflows/backward-compatibility.yml | 2 +- .github/workflows/compiler-tests.yml | 2 +- .github/workflows/e2e-tests.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/static-analysis.yml | 2 +- .github/workflows/tests.yml | 2 +- composer.json | 14 +- composer.lock | 134 +++++++++---------- 8 files changed, 75 insertions(+), 85 deletions(-) diff --git a/.github/workflows/backward-compatibility.yml b/.github/workflows/backward-compatibility.yml index 25ad88fe2f..fd0c7f37a4 100644 --- a/.github/workflows/backward-compatibility.yml +++ b/.github/workflows/backward-compatibility.yml @@ -9,7 +9,7 @@ on: - "master" env: - COMPOSER_ROOT_VERSION: "0.12.x-dev" + COMPOSER_ROOT_VERSION: "1.0.x-dev" jobs: backward-compatibility: diff --git a/.github/workflows/compiler-tests.yml b/.github/workflows/compiler-tests.yml index 2771ea134c..ca0ffc885a 100644 --- a/.github/workflows/compiler-tests.yml +++ b/.github/workflows/compiler-tests.yml @@ -9,7 +9,7 @@ on: - "master" env: - COMPOSER_ROOT_VERSION: "0.12.x-dev" + COMPOSER_ROOT_VERSION: "1.0.x-dev" jobs: compiler-tests: diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 45f8c5e34d..c7217ff0c5 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -9,7 +9,7 @@ on: - "master" env: - COMPOSER_ROOT_VERSION: "0.12.x-dev" + COMPOSER_ROOT_VERSION: "1.0.x-dev" jobs: result-cache-e2e-tests: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index ff8c179983..01976ec12f 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -9,7 +9,7 @@ on: - "master" env: - COMPOSER_ROOT_VERSION: "0.12.x-dev" + COMPOSER_ROOT_VERSION: "1.0.x-dev" jobs: lint: diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 8512f5a8e4..be5b2663c8 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -9,7 +9,7 @@ on: - "master" env: - COMPOSER_ROOT_VERSION: "0.12.x-dev" + COMPOSER_ROOT_VERSION: "1.0.x-dev" jobs: static-analysis: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1c03934271..d2a1ecd903 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -9,7 +9,7 @@ on: - "master" env: - COMPOSER_ROOT_VERSION: "0.12.x-dev" + COMPOSER_ROOT_VERSION: "1.0.x-dev" jobs: tests: diff --git a/composer.json b/composer.json index c8e60e7f66..5afd495efe 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "4.3.67", "phpstan/php-8-stubs": "^0.1.23", - "phpstan/phpdoc-parser": "^0.5.7", + "phpstan/phpdoc-parser": "^1.0", "react/child-process": "^0.6.1", "react/event-loop": "^1.1", "react/http": "^1.1", @@ -43,11 +43,11 @@ "brianium/paratest": "^6.2.0", "nategood/httpful": "^0.2.20", "php-parallel-lint/php-parallel-lint": "^1.2.0", - "phpstan/phpstan-deprecation-rules": "^0.12.3", - "phpstan/phpstan-nette": "^0.12.18", - "phpstan/phpstan-php-parser": "^0.12", - "phpstan/phpstan-phpunit": "^0.12.19", - "phpstan/phpstan-strict-rules": "^0.12", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-nette": "^1.0", + "phpstan/phpstan-php-parser": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "phpstan/phpstan-strict-rules": "^1.0", "phpunit/phpunit": "^9.5.4", "vaimo/composer-patches": "^4.22" }, @@ -60,7 +60,7 @@ }, "extra": { "branch-alias": { - "dev-master": "0.12-dev" + "dev-master": "1.0-dev" }, "patcher": { "search": "patches" diff --git a/composer.lock b/composer.lock index c4a85ef6d4..32cdd037ce 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5f96f805a466ae4dbe320e30ca8f2f7e", + "content-hash": "42d609d884ab7fa716f14e7e006e29a0", "packages": [ { "name": "clue/block-react", @@ -2177,16 +2177,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "0.5.7", + "version": "1.0.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "816e826ce0b7fb32098d8cb6de62511ce6021cea" + "reference": "9e8e6e79f86c2e4fdddb189e569276ce693b6dd7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/816e826ce0b7fb32098d8cb6de62511ce6021cea", - "reference": "816e826ce0b7fb32098d8cb6de62511ce6021cea", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/9e8e6e79f86c2e4fdddb189e569276ce693b6dd7", + "reference": "9e8e6e79f86c2e4fdddb189e569276ce693b6dd7", "shasum": "" }, "require": { @@ -2203,7 +2203,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "0.5-dev" + "dev-master": "1.0-dev" } }, "autoload": { @@ -2220,9 +2220,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/0.5.7" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.0.0" }, - "time": "2021-09-12T11:52:00+00:00" + "time": "2021-09-12T20:00:31+00:00" }, { "name": "psr/container", @@ -4247,36 +4247,32 @@ }, { "name": "phpstan/phpstan-deprecation-rules", - "version": "0.12.5", + "version": "dev-master", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-deprecation-rules.git", - "reference": "bfabc6a1b4617fbcbff43f03a4c04eae9bafae21" + "reference": "a6a1ed55749e2e39ad15b1976d523ba0af57f9c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/bfabc6a1b4617fbcbff43f03a4c04eae9bafae21", - "reference": "bfabc6a1b4617fbcbff43f03a4c04eae9bafae21", + "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/a6a1ed55749e2e39ad15b1976d523ba0af57f9c3", + "reference": "a6a1ed55749e2e39ad15b1976d523ba0af57f9c3", "shasum": "" }, "require": { "php": "^7.1 || ^8.0", - "phpstan/phpstan": "^0.12.26" + "phpstan/phpstan": "^1.0" }, "require-dev": { - "consistence/coding-standard": "^3.0.1", - "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", - "ergebnis/composer-normalize": "^2.0.2", - "jakub-onderka/php-parallel-lint": "^1.0", - "phing/phing": "^2.16.0", - "phpstan/phpstan-phpunit": "^0.12", - "phpunit/phpunit": "^7.0", - "slevomat/coding-standard": "^4.5.2" + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" }, + "default-branch": true, "type": "phpstan-extension", "extra": { "branch-alias": { - "dev-master": "0.12-dev" + "dev-master": "1.0-dev" }, "phpstan": { "includes": [ @@ -4296,9 +4292,9 @@ "description": "PHPStan rules for detecting usage of deprecated classes, methods, properties, constants and traits.", "support": { "issues": "https://github.com/phpstan/phpstan-deprecation-rules/issues", - "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/0.12.5" + "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/master" }, - "time": "2020-07-21T14:52:30+00:00" + "time": "2021-09-12T20:22:56+00:00" }, { "name": "phpstan/phpstan-nette", @@ -4306,17 +4302,17 @@ "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-nette.git", - "reference": "9feb3cafff0f1d3bba38f0e8680085089deceb62" + "reference": "b9fc63948d9e2f2e1c00681fd0734c8df7e40627" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-nette/zipball/9feb3cafff0f1d3bba38f0e8680085089deceb62", - "reference": "9feb3cafff0f1d3bba38f0e8680085089deceb62", + "url": "https://api.github.com/repos/phpstan/phpstan-nette/zipball/b9fc63948d9e2f2e1c00681fd0734c8df7e40627", + "reference": "b9fc63948d9e2f2e1c00681fd0734c8df7e40627", "shasum": "" }, "require": { "php": "^7.1 || ^8.0", - "phpstan/phpstan": "^0.12.86" + "phpstan/phpstan": "^1.0" }, "conflict": { "nette/application": "<2.3.0", @@ -4329,18 +4325,17 @@ "require-dev": { "nette/forms": "^3.0", "nette/utils": "^2.3.0 || ^3.0.0", - "phing/phing": "^2.16.3", "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-php-parser": "^0.12.2", - "phpstan/phpstan-phpunit": "^0.12.16", - "phpstan/phpstan-strict-rules": "^0.12.5", - "phpunit/phpunit": "^7.5.20" + "phpstan/phpstan-php-parser": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5" }, "default-branch": true, "type": "phpstan-extension", "extra": { "branch-alias": { - "dev-master": "0.12-dev" + "dev-master": "1.0-dev" }, "phpstan": { "includes": [ @@ -4363,41 +4358,37 @@ "issues": "https://github.com/phpstan/phpstan-nette/issues", "source": "https://github.com/phpstan/phpstan-nette/tree/master" }, - "time": "2021-04-30T11:16:08+00:00" + "time": "2021-09-12T20:23:43+00:00" }, { "name": "phpstan/phpstan-php-parser", - "version": "0.12.2", + "version": "dev-master", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-php-parser.git", - "reference": "0b27eec1e92d48fa82199844dec119b1b22baba0" + "reference": "fd0b1ddaed99074bbd986b8493fd6391d59a86b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-php-parser/zipball/0b27eec1e92d48fa82199844dec119b1b22baba0", - "reference": "0b27eec1e92d48fa82199844dec119b1b22baba0", + "url": "https://api.github.com/repos/phpstan/phpstan-php-parser/zipball/fd0b1ddaed99074bbd986b8493fd6391d59a86b2", + "reference": "fd0b1ddaed99074bbd986b8493fd6391d59a86b2", "shasum": "" }, "require": { "php": "^7.1 || ^8.0", - "phpstan/phpstan": "^0.12" + "phpstan/phpstan": "^1.0" }, "require-dev": { - "consistence/coding-standard": "^3.0.1", - "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", - "ergebnis/composer-normalize": "^2.0.2", - "jakub-onderka/php-parallel-lint": "^1.0", - "phing/phing": "^2.16.0", - "phpstan/phpstan-phpunit": "^0.12", - "phpstan/phpstan-strict-rules": "^0.12", - "phpunit/phpunit": "^7.0", - "slevomat/coding-standard": "^4.5.2" + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5" }, + "default-branch": true, "type": "phpstan-extension", "extra": { "branch-alias": { - "dev-master": "0.12-dev" + "dev-master": "1.0-dev" }, "phpstan": { "includes": [ @@ -4417,9 +4408,9 @@ "description": "PHP-Parser extensions for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-php-parser/issues", - "source": "https://github.com/phpstan/phpstan-php-parser/tree/0.12.2" + "source": "https://github.com/phpstan/phpstan-php-parser/tree/master" }, - "time": "2020-07-21T14:50:29+00:00" + "time": "2021-09-12T20:24:59+00:00" }, { "name": "phpstan/phpstan-phpunit", @@ -4427,32 +4418,31 @@ "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-phpunit.git", - "reference": "52f7072ddc5f81492f9d2de65a24813a48c90b18" + "reference": "6c0e48e98f082e94be11bca4db64489194c66b06" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/52f7072ddc5f81492f9d2de65a24813a48c90b18", - "reference": "52f7072ddc5f81492f9d2de65a24813a48c90b18", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/6c0e48e98f082e94be11bca4db64489194c66b06", + "reference": "6c0e48e98f082e94be11bca4db64489194c66b06", "shasum": "" }, "require": { "php": "^7.1 || ^8.0", - "phpstan/phpstan": "^0.12.86" + "phpstan/phpstan": "^1.0" }, "conflict": { "phpunit/phpunit": "<7.0" }, "require-dev": { - "phing/phing": "^2.16.3", "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-strict-rules": "^0.12.6", - "phpunit/phpunit": "^7.5.20" + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5" }, "default-branch": true, "type": "phpstan-extension", "extra": { "branch-alias": { - "dev-master": "0.12-dev" + "dev-master": "1.0-dev" }, "phpstan": { "includes": [ @@ -4475,36 +4465,36 @@ "issues": "https://github.com/phpstan/phpstan-phpunit/issues", "source": "https://github.com/phpstan/phpstan-phpunit/tree/master" }, - "time": "2021-04-30T11:10:37+00:00" + "time": "2021-09-12T20:25:39+00:00" }, { "name": "phpstan/phpstan-strict-rules", - "version": "0.12.9", + "version": "dev-master", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "0705fefc7c9168529fd130e341428f5f10f4f01d" + "reference": "6a7b2714e4e05d9db2cacad9ec2c245d63565607" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/0705fefc7c9168529fd130e341428f5f10f4f01d", - "reference": "0705fefc7c9168529fd130e341428f5f10f4f01d", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/6a7b2714e4e05d9db2cacad9ec2c245d63565607", + "reference": "6a7b2714e4e05d9db2cacad9ec2c245d63565607", "shasum": "" }, "require": { "php": "^7.1 || ^8.0", - "phpstan/phpstan": "^0.12.66" + "phpstan/phpstan": "^1.0" }, "require-dev": { - "phing/phing": "^2.16.3", "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-phpunit": "^0.12.16", - "phpunit/phpunit": "^7.5.20" + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" }, + "default-branch": true, "type": "phpstan-extension", "extra": { "branch-alias": { - "dev-master": "0.12-dev" + "dev-master": "1.0-dev" }, "phpstan": { "includes": [ @@ -4524,9 +4514,9 @@ "description": "Extra strict and opinionated rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", - "source": "https://github.com/phpstan/phpstan-strict-rules/tree/0.12.9" + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/master" }, - "time": "2021-01-13T08:50:28+00:00" + "time": "2021-09-12T20:26:32+00:00" }, { "name": "phpunit/php-code-coverage", From 85f22a1009481d48424587f578b76195a5dffb06 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 12 Sep 2021 22:33:12 +0200 Subject: [PATCH 0249/1284] PHAR compiler - require PHPStan 1.0 --- compiler/composer.json | 6 ++++-- compiler/composer.lock | 40 +++++++++++++++++++++------------------- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/compiler/composer.json b/compiler/composer.json index fb3b8e47c5..109608810b 100644 --- a/compiler/composer.json +++ b/compiler/composer.json @@ -23,7 +23,7 @@ }, "require-dev": { "phpunit/phpunit": "^9.5.1", - "phpstan/phpstan-phpunit": "^0.12.8" + "phpstan/phpstan-phpunit": "^1.0" }, "config": { "platform": { @@ -31,5 +31,7 @@ }, "platform-check": false, "sort-packages": true - } + }, + "minimum-stability": "dev", + "prefer-stable": true } diff --git a/compiler/composer.lock b/compiler/composer.lock index 05d5c0ecac..4517aaff62 100644 --- a/compiler/composer.lock +++ b/compiler/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "3d8d846053f4a058c0f88025d0edead6", + "content-hash": "5e13c6da184e97394b42dff8a20ba685", "packages": [ { "name": "nette/neon", @@ -1640,16 +1640,16 @@ }, { "name": "phpstan/phpstan", - "version": "0.12.90", + "version": "dev-master", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "f0e4b56630fc3d4eb5be86606d07212ac212ede4" + "reference": "ad96e5efd921513113c96245e0e3ed369c5544d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/f0e4b56630fc3d4eb5be86606d07212ac212ede4", - "reference": "f0e4b56630fc3d4eb5be86606d07212ac212ede4", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/ad96e5efd921513113c96245e0e3ed369c5544d4", + "reference": "ad96e5efd921513113c96245e0e3ed369c5544d4", "shasum": "" }, "require": { @@ -1658,6 +1658,7 @@ "conflict": { "phpstan/phpstan-shim": "*" }, + "default-branch": true, "bin": [ "phpstan", "phpstan.phar" @@ -1665,7 +1666,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "0.12-dev" + "dev-master": "1.0-dev" } }, "autoload": { @@ -1680,7 +1681,7 @@ "description": "PHPStan - PHP Static Analysis Tool", "support": { "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/0.12.90" + "source": "https://github.com/phpstan/phpstan/tree/master" }, "funding": [ { @@ -1700,38 +1701,39 @@ "type": "tidelift" } ], - "time": "2021-06-18T07:15:38+00:00" + "time": "2021-09-12T20:30:21+00:00" }, { "name": "phpstan/phpstan-phpunit", - "version": "0.12.20", + "version": "dev-master", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-phpunit.git", - "reference": "efc009981af383eb3303f0ca9868c29acad7ce74" + "reference": "6c0e48e98f082e94be11bca4db64489194c66b06" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/efc009981af383eb3303f0ca9868c29acad7ce74", - "reference": "efc009981af383eb3303f0ca9868c29acad7ce74", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/6c0e48e98f082e94be11bca4db64489194c66b06", + "reference": "6c0e48e98f082e94be11bca4db64489194c66b06", "shasum": "" }, "require": { "php": "^7.1 || ^8.0", - "phpstan/phpstan": "^0.12.86" + "phpstan/phpstan": "^1.0" }, "conflict": { "phpunit/phpunit": "<7.0" }, "require-dev": { "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-strict-rules": "^0.12.6", + "phpstan/phpstan-strict-rules": "^1.0", "phpunit/phpunit": "^9.5" }, + "default-branch": true, "type": "phpstan-extension", "extra": { "branch-alias": { - "dev-master": "0.12-dev" + "dev-master": "1.0-dev" }, "phpstan": { "includes": [ @@ -1752,9 +1754,9 @@ "description": "PHPUnit extensions and rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-phpunit/issues", - "source": "https://github.com/phpstan/phpstan-phpunit/tree/0.12.20" + "source": "https://github.com/phpstan/phpstan-phpunit/tree/master" }, - "time": "2021-06-17T08:28:30+00:00" + "time": "2021-09-12T20:25:39+00:00" }, { "name": "phpunit/php-code-coverage", @@ -3251,9 +3253,9 @@ } ], "aliases": [], - "minimum-stability": "stable", + "minimum-stability": "dev", "stability-flags": [], - "prefer-stable": false, + "prefer-stable": true, "prefer-lowest": false, "platform": { "php": "^7.3" From 4dba60bacbf3ac0efe8627fa32177ee21cdd3eca Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 12 Sep 2021 22:50:17 +0200 Subject: [PATCH 0250/1284] [BCB] Removed DeadCatchRule, replaced by CatchWithUnthrownExceptionRule --- conf/config.level4.neon | 7 -- src/Rules/Exceptions/DeadCatchRule.php | 71 ------------------- .../Rules/Exceptions/DeadCatchRuleTest.php | 29 -------- 3 files changed, 107 deletions(-) delete mode 100644 src/Rules/Exceptions/DeadCatchRule.php delete mode 100644 tests/PHPStan/Rules/Exceptions/DeadCatchRuleTest.php diff --git a/conf/config.level4.neon b/conf/config.level4.neon index cb7540c5eb..dc7cb46a1d 100644 --- a/conf/config.level4.neon +++ b/conf/config.level4.neon @@ -152,13 +152,6 @@ services: - class: PHPStan\Rules\Exceptions\CatchWithUnthrownExceptionRule - - - class: PHPStan\Rules\Exceptions\DeadCatchRule - arguments: - bleedingEdge: %featureToggles.bleedingEdge% - tags: - - phpstan.rules.rule - - class: PHPStan\Rules\Exceptions\OverwrittenExitPointByFinallyRule diff --git a/src/Rules/Exceptions/DeadCatchRule.php b/src/Rules/Exceptions/DeadCatchRule.php deleted file mode 100644 index 1761bbb637..0000000000 --- a/src/Rules/Exceptions/DeadCatchRule.php +++ /dev/null @@ -1,71 +0,0 @@ - - */ -class DeadCatchRule implements Rule -{ - - private bool $bleedingEdge; - - public function __construct(bool $bleedingEdge = false) - { - $this->bleedingEdge = $bleedingEdge; - } - - public function getNodeType(): string - { - return Node\Stmt\TryCatch::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if ($this->bleedingEdge) { - return []; - } - - $catchTypes = array_map(static function (Node\Stmt\Catch_ $catch): Type { - return TypeCombinator::union(...array_map(static function (Node\Name $className): ObjectType { - return new ObjectType($className->toString()); - }, $catch->types)); - }, $node->catches); - $catchesCount = count($catchTypes); - $errors = []; - for ($i = 0; $i < $catchesCount - 1; $i++) { - $firstType = $catchTypes[$i]; - for ($j = $i + 1; $j < $catchesCount; $j++) { - $secondType = $catchTypes[$j]; - if (!$firstType->isSuperTypeOf($secondType)->yes()) { - continue; - } - - $errors[] = RuleErrorBuilder::message(sprintf( - 'Dead catch - %s is already caught by %s above.', - $secondType->describe(VerbosityLevel::typeOnly()), - $firstType->describe(VerbosityLevel::typeOnly()) - ))->line($node->catches[$j]->getLine()) - ->identifier('deadCode.unreachableCatch') - ->metadata([ - 'tryLine' => $node->getLine(), - 'firstCatchOrder' => $i, - 'deadCatchOrder' => $j, - ]) - ->build(); - } - } - - return $errors; - } - -} diff --git a/tests/PHPStan/Rules/Exceptions/DeadCatchRuleTest.php b/tests/PHPStan/Rules/Exceptions/DeadCatchRuleTest.php deleted file mode 100644 index 828cab35ce..0000000000 --- a/tests/PHPStan/Rules/Exceptions/DeadCatchRuleTest.php +++ /dev/null @@ -1,29 +0,0 @@ - - */ -class DeadCatchRuleTest extends RuleTestCase -{ - - protected function getRule(): Rule - { - return new DeadCatchRule(); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/dead-catch.php'], [ - [ - 'Dead catch - TypeError is already caught by Throwable above.', - 27, - ], - ]); - } - -} From 2e858dee389d4843e4d1a7dc1bc565c264be247f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 12 Sep 2021 22:51:17 +0200 Subject: [PATCH 0251/1284] [BCB] Removed VariableCertaintyInIssetRule, replaced by IssetRule --- conf/config.level1.neon | 7 -- .../VariableCertaintyInIssetRule.php | 82 ------------- .../VariableCertaintyInIssetRuleTest.php | 109 ------------------ 3 files changed, 198 deletions(-) delete mode 100644 src/Rules/Variables/VariableCertaintyInIssetRule.php delete mode 100644 tests/PHPStan/Rules/Variables/VariableCertaintyInIssetRuleTest.php diff --git a/conf/config.level1.neon b/conf/config.level1.neon index 91baf1dc4d..05cb8715b9 100644 --- a/conf/config.level1.neon +++ b/conf/config.level1.neon @@ -21,13 +21,6 @@ conditionalTags: phpstan.rules.rule: %featureToggles.nullCoalesce% services: - - - class: PHPStan\Rules\Variables\VariableCertaintyInIssetRule - arguments: - bleedingEdge: %featureToggles.bleedingEdge% - tags: - - phpstan.rules.rule - - class: PHPStan\Rules\Variables\EmptyRule diff --git a/src/Rules/Variables/VariableCertaintyInIssetRule.php b/src/Rules/Variables/VariableCertaintyInIssetRule.php deleted file mode 100644 index d8226c4a74..0000000000 --- a/src/Rules/Variables/VariableCertaintyInIssetRule.php +++ /dev/null @@ -1,82 +0,0 @@ - - */ -class VariableCertaintyInIssetRule implements \PHPStan\Rules\Rule -{ - - private bool $bleedingEdge; - - public function __construct(bool $bleedingEdge = false) - { - $this->bleedingEdge = $bleedingEdge; - } - - public function getNodeType(): string - { - return Node\Expr\Isset_::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if ($this->bleedingEdge) { - return []; - } - - $messages = []; - foreach ($node->vars as $var) { - $isSubNode = false; - while ( - $var instanceof Node\Expr\ArrayDimFetch - || $var instanceof Node\Expr\PropertyFetch - || ( - $var instanceof Node\Expr\StaticPropertyFetch - && $var->class instanceof Node\Expr - ) - ) { - if ($var instanceof Node\Expr\StaticPropertyFetch) { - $var = $var->class; - } else { - $var = $var->var; - } - $isSubNode = true; - } - - if (!$var instanceof Node\Expr\Variable || !is_string($var->name) || $var->name === '_SESSION') { - continue; - } - - $certainty = $scope->hasVariableType($var->name); - if ($certainty->no()) { - $messages[] = RuleErrorBuilder::message(sprintf( - 'Variable $%s in isset() is never defined.', - $var->name - ))->build(); - } elseif ($certainty->yes() && !$isSubNode) { - $variableType = $scope->getVariableType($var->name); - if ($variableType->isSuperTypeOf(new NullType())->no()) { - $messages[] = RuleErrorBuilder::message(sprintf( - 'Variable $%s in isset() always exists and is not nullable.', - $var->name - ))->build(); - } elseif ((new NullType())->isSuperTypeOf($variableType)->yes()) { - $messages[] = RuleErrorBuilder::message(sprintf( - 'Variable $%s in isset() is always null.', - $var->name - ))->build(); - } - } - } - - return $messages; - } - -} diff --git a/tests/PHPStan/Rules/Variables/VariableCertaintyInIssetRuleTest.php b/tests/PHPStan/Rules/Variables/VariableCertaintyInIssetRuleTest.php deleted file mode 100644 index 6d588de94a..0000000000 --- a/tests/PHPStan/Rules/Variables/VariableCertaintyInIssetRuleTest.php +++ /dev/null @@ -1,109 +0,0 @@ - - */ -class VariableCertaintyInIssetRuleTest extends \PHPStan\Testing\RuleTestCase -{ - - protected function getRule(): \PHPStan\Rules\Rule - { - return new VariableCertaintyInIssetRule(); - } - - public function testVariableCertaintyInIsset(): void - { - $this->analyse([__DIR__ . '/data/variable-certainty-isset.php'], [ - [ - 'Variable $alwaysDefinedNotNullable in isset() always exists and is not nullable.', - 14, - ], - [ - 'Variable $neverDefinedVariable in isset() is never defined.', - 22, - ], - [ - 'Variable $anotherNeverDefinedVariable in isset() is never defined.', - 42, - ], - [ - 'Variable $yetAnotherNeverDefinedVariable in isset() is never defined.', - 46, - ], - [ - 'Variable $yetYetAnotherNeverDefinedVariableInIsset in isset() is never defined.', - 56, - ], - [ - 'Variable $anotherVariableInDoWhile in isset() always exists and is not nullable.', - 104, - ], - [ - 'Variable $variableInSecondCase in isset() is never defined.', - 110, - ], - [ - 'Variable $variableInFirstCase in isset() always exists and is not nullable.', - 112, - ], - [ - 'Variable $variableInFirstCase in isset() always exists and is not nullable.', - 116, - ], - [ - 'Variable $variableInSecondCase in isset() always exists and is not nullable.', - 117, - ], - [ - 'Variable $variableAssignedInSecondCase in isset() is never defined.', - 119, - ], - [ - 'Variable $alwaysDefinedForSwitchCondition in isset() always exists and is not nullable.', - 139, - ], - [ - 'Variable $alwaysDefinedForCaseNodeCondition in isset() always exists and is not nullable.', - 140, - ], - [ - 'Variable $alwaysDefinedNotNullable in isset() always exists and is not nullable.', - 152, - ], - [ - 'Variable $neverDefinedVariable in isset() is never defined.', - 152, - ], - [ - 'Variable $a in isset() always exists and is not nullable.', - 214, - ], - [ - 'Variable $null in isset() is always null.', - 225, - ], - ]); - } - - public function testIssetInGlobalScope(): void - { - $this->analyse([__DIR__ . '/data/isset-global-scope.php'], [ - [ - 'Variable $alwaysDefinedNotNullable in isset() always exists and is not nullable.', - 8, - ], - ]); - } - - public function testNullsafe(): void - { - if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires PHP 8.0.'); - } - - $this->analyse([__DIR__ . '/data/isset-nullsafe.php'], []); - } - -} From 81bebddd77aae419454c5c2376132a562d2ecc10 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 12 Sep 2021 22:53:17 +0200 Subject: [PATCH 0252/1284] IssetCheck - remove bleeding edge condition so that undefined variables are always reported --- conf/config.neon | 1 - src/Rules/IssetCheck.php | 11 ++--------- tests/PHPStan/Rules/Variables/EmptyRuleTest.php | 2 +- tests/PHPStan/Rules/Variables/IssetRuleTest.php | 2 +- .../PHPStan/Rules/Variables/NullCoalesceRuleTest.php | 2 +- 5 files changed, 5 insertions(+), 13 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index 0fec097710..1bcf0120bd 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -864,7 +864,6 @@ services: - class: PHPStan\Rules\IssetCheck arguments: - bleedingEdge: %featureToggles.bleedingEdge% checkAdvancedIsset: %checkAdvancedIsset% - diff --git a/src/Rules/IssetCheck.php b/src/Rules/IssetCheck.php index 354eaba778..cb2dd02022 100644 --- a/src/Rules/IssetCheck.php +++ b/src/Rules/IssetCheck.php @@ -20,19 +20,15 @@ class IssetCheck private bool $checkAdvancedIsset; - private bool $bleedingEdge; - public function __construct( PropertyDescriptor $propertyDescriptor, PropertyReflectionFinder $propertyReflectionFinder, - bool $checkAdvancedIsset, - bool $bleedingEdge + bool $checkAdvancedIsset ) { $this->propertyDescriptor = $propertyDescriptor; $this->propertyReflectionFinder = $propertyReflectionFinder; $this->checkAdvancedIsset = $checkAdvancedIsset; - $this->bleedingEdge = $bleedingEdge; } /** @@ -46,10 +42,7 @@ public function check(Expr $expr, Scope $scope, string $operatorDescription, cal return null; } - if ( - $error === null - && $this->bleedingEdge - ) { + if ($error === null) { if ($hasVariable->yes()) { if ($expr->name === '_SESSION') { return null; diff --git a/tests/PHPStan/Rules/Variables/EmptyRuleTest.php b/tests/PHPStan/Rules/Variables/EmptyRuleTest.php index 0ed215f228..95ca45d34b 100644 --- a/tests/PHPStan/Rules/Variables/EmptyRuleTest.php +++ b/tests/PHPStan/Rules/Variables/EmptyRuleTest.php @@ -15,7 +15,7 @@ class EmptyRuleTest extends RuleTestCase protected function getRule(): \PHPStan\Rules\Rule { - return new EmptyRule(new IssetCheck(new PropertyDescriptor(), new PropertyReflectionFinder(), true, true)); + return new EmptyRule(new IssetCheck(new PropertyDescriptor(), new PropertyReflectionFinder(), true)); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Variables/IssetRuleTest.php b/tests/PHPStan/Rules/Variables/IssetRuleTest.php index e495f80661..2f2c7b1c11 100644 --- a/tests/PHPStan/Rules/Variables/IssetRuleTest.php +++ b/tests/PHPStan/Rules/Variables/IssetRuleTest.php @@ -16,7 +16,7 @@ class IssetRuleTest extends RuleTestCase protected function getRule(): Rule { - return new IssetRule(new IssetCheck(new PropertyDescriptor(), new PropertyReflectionFinder(), true, true)); + return new IssetRule(new IssetCheck(new PropertyDescriptor(), new PropertyReflectionFinder(), true)); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php b/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php index 3ed88c5604..243c36b3dd 100644 --- a/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php +++ b/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php @@ -14,7 +14,7 @@ class NullCoalesceRuleTest extends \PHPStan\Testing\RuleTestCase protected function getRule(): \PHPStan\Rules\Rule { - return new NullCoalesceRule(new IssetCheck(new PropertyDescriptor(), new PropertyReflectionFinder(), true, true)); + return new NullCoalesceRule(new IssetCheck(new PropertyDescriptor(), new PropertyReflectionFinder(), true)); } public function testCoalesceRule(): void From db4db5b107b64d3853b2cd8d56e496132c7c15e4 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 12 Sep 2021 22:54:41 +0200 Subject: [PATCH 0253/1284] MissingReturnRule - make missing return with native return type non-ignorable --- conf/config.level0.neon | 1 - src/Rules/Missing/MissingReturnRule.php | 10 +++------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/conf/config.level0.neon b/conf/config.level0.neon index a57e69384f..6f1bbf757e 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -172,7 +172,6 @@ services: arguments: checkExplicitMixedMissingReturn: %checkExplicitMixedMissingReturn% checkPhpDocMissingReturn: %checkPhpDocMissingReturn% - bleedingEdge: %featureToggles.bleedingEdge% tags: - phpstan.rules.rule diff --git a/src/Rules/Missing/MissingReturnRule.php b/src/Rules/Missing/MissingReturnRule.php index 39de55fac0..ed69a725b0 100644 --- a/src/Rules/Missing/MissingReturnRule.php +++ b/src/Rules/Missing/MissingReturnRule.php @@ -28,17 +28,13 @@ class MissingReturnRule implements Rule private bool $checkPhpDocMissingReturn; - private bool $bleedingEdge; - public function __construct( bool $checkExplicitMixedMissingReturn, - bool $checkPhpDocMissingReturn, - bool $bleedingEdge = false + bool $checkPhpDocMissingReturn ) { $this->checkExplicitMixedMissingReturn = $checkExplicitMixedMissingReturn; $this->checkPhpDocMissingReturn = $checkPhpDocMissingReturn; - $this->bleedingEdge = $bleedingEdge; } public function getNodeType(): string @@ -112,7 +108,7 @@ public function processNode(Node $node, Scope $scope): array if ($returnType instanceof NeverType && $returnType->isExplicit()) { $errorBuilder = RuleErrorBuilder::message(sprintf('%s should always throw an exception or terminate script execution but doesn\'t do that.', $description))->line($node->getNode()->getStartLine()); - if ($this->bleedingEdge && $node->hasNativeReturnTypehint()) { + if ($node->hasNativeReturnTypehint()) { $errorBuilder->nonIgnorable(); } @@ -137,7 +133,7 @@ public function processNode(Node $node, Scope $scope): array sprintf('%s should return %s but return statement is missing.', $description, $returnType->describe(VerbosityLevel::typeOnly())) )->line($node->getNode()->getStartLine()); - if ($this->bleedingEdge && $node->hasNativeReturnTypehint()) { + if ($node->hasNativeReturnTypehint()) { $errorBuilder->nonIgnorable(); } From 3e015ef5a5c3be1fe30946e3d6b13d3c300eb162 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 12 Sep 2021 22:56:39 +0200 Subject: [PATCH 0254/1284] Check callable parameter type for array_map() and array_filter() --- src/Analyser/NodeScopeResolver.php | 45 ------------------- src/Reflection/ParametersAcceptorSelector.php | 4 +- 2 files changed, 1 insertion(+), 48 deletions(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 7cfbdc6ecc..06ee0c3069 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -50,7 +50,6 @@ use PHPStan\BetterReflection\Reflector\ClassReflector; use PHPStan\BetterReflection\SourceLocator\Ast\Strategy\NodeToReflection; use PHPStan\BetterReflection\SourceLocator\Located\LocatedSource; -use PHPStan\DependencyInjection\BleedingEdgeToggle; use PHPStan\DependencyInjection\Reflection\ClassReflectionExtensionRegistryProvider; use PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider; use PHPStan\File\FileHelper; @@ -91,14 +90,11 @@ use PHPStan\Reflection\Native\NativeMethodReflection; use PHPStan\Reflection\ParametersAcceptor; use PHPStan\Reflection\ParametersAcceptorSelector; -use PHPStan\Reflection\PassedByReference; -use PHPStan\Reflection\Php\DummyParameter; use PHPStan\Reflection\Php\PhpMethodReflection; use PHPStan\Reflection\ReflectionProvider; use PHPStan\TrinaryLogic; use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\ArrayType; -use PHPStan\Type\CallableType; use PHPStan\Type\ClosureType; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantArrayTypeBuilder; @@ -3157,47 +3153,6 @@ private function processArgs( $scope = $scope->assignVariable($argValue->name, new MixedType()); } } - - if (!BleedingEdgeToggle::isBleedingEdge() && $calleeReflection instanceof FunctionReflection) { - if ( - $i === 0 - && $calleeReflection->getName() === 'array_map' - && isset($args[1]) - ) { - $parameterType = new CallableType([ - new DummyParameter('item', $scope->getType($args[1]->value)->getIterableValueType(), false, PassedByReference::createNo(), false, null), - ], new MixedType(), false); - } - - if ( - $i === 1 - && $calleeReflection->getName() === 'array_filter' - && isset($args[0]) - ) { - if (isset($args[2])) { - $mode = $scope->getType($args[2]->value); - if ($mode instanceof ConstantIntegerType) { - if ($mode->getValue() === ARRAY_FILTER_USE_KEY) { - $arrayFilterParameters = [ - new DummyParameter('key', $scope->getType($args[0]->value)->getIterableKeyType(), false, PassedByReference::createNo(), false, null), - ]; - } elseif ($mode->getValue() === ARRAY_FILTER_USE_BOTH) { - $arrayFilterParameters = [ - new DummyParameter('item', $scope->getType($args[0]->value)->getIterableValueType(), false, PassedByReference::createNo(), false, null), - new DummyParameter('key', $scope->getType($args[0]->value)->getIterableKeyType(), false, PassedByReference::createNo(), false, null), - ]; - } - } - } - $parameterType = new CallableType( - $arrayFilterParameters ?? [ - new DummyParameter('item', $scope->getType($args[0]->value)->getIterableValueType(), false, PassedByReference::createNo(), false, null), - ], - new MixedType(), - false - ); - } - } } $originalScope = $scope; diff --git a/src/Reflection/ParametersAcceptorSelector.php b/src/Reflection/ParametersAcceptorSelector.php index 61faa63397..3b3db43e26 100644 --- a/src/Reflection/ParametersAcceptorSelector.php +++ b/src/Reflection/ParametersAcceptorSelector.php @@ -5,7 +5,6 @@ use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Name; use PHPStan\Analyser\Scope; -use PHPStan\DependencyInjection\BleedingEdgeToggle; use PHPStan\Reflection\Native\NativeParameterReflection; use PHPStan\Reflection\Php\DummyParameter; use PHPStan\TrinaryLogic; @@ -50,8 +49,7 @@ public static function selectFromArgs( $types = []; $unpack = false; if ( - BleedingEdgeToggle::isBleedingEdge() - && count($args) > 0 + count($args) > 0 && count($parametersAcceptors) > 0 ) { $functionName = null; From 145c4e3af4045b074b1d6e697022a835433cd5f2 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 12 Sep 2021 23:00:37 +0200 Subject: [PATCH 0255/1284] [BCB] Removed CompoundTypeHelper --- src/Type/CompoundTypeHelper.php | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 src/Type/CompoundTypeHelper.php diff --git a/src/Type/CompoundTypeHelper.php b/src/Type/CompoundTypeHelper.php deleted file mode 100644 index 01686a722d..0000000000 --- a/src/Type/CompoundTypeHelper.php +++ /dev/null @@ -1,18 +0,0 @@ -isAcceptedBy($otherType, $strictTypes); - } - -} From ebad6f61b9a65b68c200f7ac3a444ce198149396 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 12 Sep 2021 23:00:48 +0200 Subject: [PATCH 0256/1284] [BCB] Removed CommentHelper --- src/Type/CommentHelper.php | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 src/Type/CommentHelper.php diff --git a/src/Type/CommentHelper.php b/src/Type/CommentHelper.php deleted file mode 100644 index 3f9e3501ae..0000000000 --- a/src/Type/CommentHelper.php +++ /dev/null @@ -1,21 +0,0 @@ -getDocComment(); - if ($phpDoc !== null) { - return $phpDoc->getText(); - } - - return null; - } - -} From 1cc6c8175a6d92cf717cd553506c9da8a2dd72cc Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 12 Sep 2021 23:01:37 +0200 Subject: [PATCH 0257/1284] [BCB] Removed PHPStan\Reflection\Generic\ResolvedFunctionVariant, replaced by PHPStan\Reflection\ResolvedFunctionVariant --- .../Generic/ResolvedFunctionVariant.php | 89 ------------------- 1 file changed, 89 deletions(-) delete mode 100644 src/Reflection/Generic/ResolvedFunctionVariant.php diff --git a/src/Reflection/Generic/ResolvedFunctionVariant.php b/src/Reflection/Generic/ResolvedFunctionVariant.php deleted file mode 100644 index 81c76bc77f..0000000000 --- a/src/Reflection/Generic/ResolvedFunctionVariant.php +++ /dev/null @@ -1,89 +0,0 @@ -parametersAcceptor = $parametersAcceptor; - $this->resolvedTemplateTypeMap = $resolvedTemplateTypeMap; - } - - public function getTemplateTypeMap(): TemplateTypeMap - { - return $this->parametersAcceptor->getTemplateTypeMap(); - } - - public function getResolvedTemplateTypeMap(): TemplateTypeMap - { - return $this->resolvedTemplateTypeMap; - } - - public function getParameters(): array - { - $parameters = $this->parameters; - - if ($parameters === null) { - $parameters = array_map(function (ParameterReflection $param): ParameterReflection { - return new DummyParameter( - $param->getName(), - TemplateTypeHelper::resolveTemplateTypes($param->getType(), $this->resolvedTemplateTypeMap), - $param->isOptional(), - $param->passedByReference(), - $param->isVariadic(), - $param->getDefaultValue() - ); - }, $this->parametersAcceptor->getParameters()); - - $this->parameters = $parameters; - } - - return $parameters; - } - - public function isVariadic(): bool - { - return $this->parametersAcceptor->isVariadic(); - } - - public function getReturnType(): Type - { - $type = $this->returnType; - - if ($type === null) { - $type = TemplateTypeHelper::resolveTemplateTypes( - $this->parametersAcceptor->getReturnType(), - $this->resolvedTemplateTypeMap - ); - - $this->returnType = $type; - } - - return $type; - } - -} From d2c1446b3b0f581502019c522ccb521b37e7dc15 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 12 Sep 2021 23:02:29 +0200 Subject: [PATCH 0258/1284] [BCB] Removed ClassReflection::getNativeMethods(), use getNativeReflection() instead --- src/Reflection/ClassReflection.php | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index 2801fc3a13..eee291e8a7 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -434,20 +434,6 @@ public function getNativeMethod(string $methodName): MethodReflection return $this->getPhpExtension()->getNativeMethod($this, $methodName); } - /** - * @deprecated Use ClassReflection::getNativeReflection() instead. - * @return MethodReflection[] - */ - public function getNativeMethods(): array - { - $methods = []; - foreach ($this->reflection->getMethods() as $method) { - $methods[] = $this->getNativeMethod($method->getName()); - } - - return $methods; - } - public function hasConstructor(): bool { return $this->findConstructor() !== null; From bedd5be99107254f42efb7728c9ce3a7b958e33e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 12 Sep 2021 23:03:02 +0200 Subject: [PATCH 0259/1284] [BCB] Removed PhpPropertyReflection::hasPhpDoc(), replaced by hasPhpDocType() --- src/Reflection/Php/PhpPropertyReflection.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Reflection/Php/PhpPropertyReflection.php b/src/Reflection/Php/PhpPropertyReflection.php index 077c1e58bd..87ff0fbdb8 100644 --- a/src/Reflection/Php/PhpPropertyReflection.php +++ b/src/Reflection/Php/PhpPropertyReflection.php @@ -138,12 +138,6 @@ public function isPromoted(): bool return $this->reflection->isPromoted(); } - /** @deprecated Use hasPhpDocType() */ - public function hasPhpDoc(): bool - { - return $this->hasPhpDocType(); - } - public function hasPhpDocType(): bool { return $this->phpDocType !== null; From 9c7017c697c29668f8a7c766236e597737f99f91 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 12 Sep 2021 23:04:37 +0200 Subject: [PATCH 0260/1284] [BCB] Removed dump-deps command, DependencyDumper --- bin/phpstan | 2 - conf/config.neon | 5 - src/Command/DumpDependenciesCommand.php | 130 ---------------- src/Dependency/DependencyDumper.php | 97 ------------ .../Dependency/DependencyDumperTest.php | 145 ------------------ tests/PHPStan/Dependency/data/Child.php | 8 - tests/PHPStan/Dependency/data/GrandChild.php | 17 -- tests/PHPStan/Dependency/data/Parent.php | 8 - 8 files changed, 412 deletions(-) delete mode 100644 src/Command/DumpDependenciesCommand.php delete mode 100644 src/Dependency/DependencyDumper.php delete mode 100644 tests/PHPStan/Dependency/DependencyDumperTest.php delete mode 100644 tests/PHPStan/Dependency/data/Child.php delete mode 100644 tests/PHPStan/Dependency/data/GrandChild.php delete mode 100644 tests/PHPStan/Dependency/data/Parent.php diff --git a/bin/phpstan b/bin/phpstan index de25ec2c0a..1a7a618c64 100755 --- a/bin/phpstan +++ b/bin/phpstan @@ -4,7 +4,6 @@ use PHPStan\Command\AnalyseCommand; use PHPStan\Command\ClearResultCacheCommand; use PHPStan\Command\FixerWorkerCommand; -use PHPStan\Command\DumpDependenciesCommand; use PHPStan\Command\WorkerCommand; use Symfony\Component\Console\Helper\ProgressBar; @@ -95,7 +94,6 @@ use Symfony\Component\Console\Helper\ProgressBar; $reversedComposerAutoloaderProjectPaths = array_reverse($composerAutoloaderProjectPaths); $application->add(new AnalyseCommand($reversedComposerAutoloaderProjectPaths)); - $application->add(new DumpDependenciesCommand($reversedComposerAutoloaderProjectPaths)); $application->add(new WorkerCommand($reversedComposerAutoloaderProjectPaths)); $application->add(new ClearResultCacheCommand($reversedComposerAutoloaderProjectPaths)); $application->add(new FixerWorkerCommand($reversedComposerAutoloaderProjectPaths)); diff --git a/conf/config.neon b/conf/config.neon index 1bcf0120bd..96066de357 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -533,11 +533,6 @@ services: arguments: parser: @regexParser - - - class: PHPStan\Dependency\DependencyDumper - arguments: - fileFinder: @fileFinderAnalyse - - class: PHPStan\Dependency\DependencyResolver diff --git a/src/Command/DumpDependenciesCommand.php b/src/Command/DumpDependenciesCommand.php deleted file mode 100644 index aa69b5900d..0000000000 --- a/src/Command/DumpDependenciesCommand.php +++ /dev/null @@ -1,130 +0,0 @@ -composerAutoloaderProjectPaths = $composerAutoloaderProjectPaths; - } - - protected function configure(): void - { - $this->setName(self::NAME) - ->setDescription('Dumps files dependency tree') - ->setDefinition([ - new InputArgument('paths', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'Paths with source code to run dump on'), - new InputOption('paths-file', null, InputOption::VALUE_REQUIRED, 'Path to a file with a list of paths to run analysis on'), - new InputOption('configuration', 'c', InputOption::VALUE_REQUIRED, 'Path to project configuration file'), - new InputOption(ErrorsConsoleStyle::OPTION_NO_PROGRESS, null, InputOption::VALUE_NONE, 'Do not show progress bar, only results'), - new InputOption('autoload-file', 'a', InputOption::VALUE_REQUIRED, 'Project\'s additional autoload file path'), - new InputOption('memory-limit', null, InputOption::VALUE_REQUIRED, 'Memory limit for the run'), - new InputOption('analysed-paths', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Project-scope paths'), - new InputOption('xdebug', null, InputOption::VALUE_NONE, 'Allow running with XDebug for debugging purposes'), - ]); - } - - protected function execute(InputInterface $input, OutputInterface $output): int - { - try { - /** @var string[] $paths */ - $paths = $input->getArgument('paths'); - - /** @var string|null $memoryLimit */ - $memoryLimit = $input->getOption('memory-limit'); - - /** @var string|null $autoloadFile */ - $autoloadFile = $input->getOption('autoload-file'); - - /** @var string|null $configurationFile */ - $configurationFile = $input->getOption('configuration'); - - /** @var string|null $pathsFile */ - $pathsFile = $input->getOption('paths-file'); - - /** @var bool $allowXdebug */ - $allowXdebug = $input->getOption('xdebug'); - - $inceptionResult = CommandHelper::begin( - $input, - $output, - $paths, - $pathsFile, - $memoryLimit, - $autoloadFile, - $this->composerAutoloaderProjectPaths, - $configurationFile, - null, - '0', // irrelevant but prevents an error when a config file is passed - $allowXdebug, - true - ); - } catch (\PHPStan\Command\InceptionNotSuccessfulException $e) { - return 1; - } - - try { - [$files] = $inceptionResult->getFiles(); - } catch (\PHPStan\File\PathNotFoundException $e) { - $inceptionResult->getErrorOutput()->writeLineFormatted(sprintf('%s', $e->getMessage())); - return 1; - } - - $stdOutput = $inceptionResult->getStdOutput(); - $stdOutputStyole = $stdOutput->getStyle(); - - /** @var DependencyDumper $dependencyDumper */ - $dependencyDumper = $inceptionResult->getContainer()->getByType(DependencyDumper::class); - - /** @var FileHelper $fileHelper */ - $fileHelper = $inceptionResult->getContainer()->getByType(FileHelper::class); - - /** @var string[] $analysedPaths */ - $analysedPaths = $input->getOption('analysed-paths'); - $analysedPaths = array_map(static function (string $path) use ($fileHelper): string { - return $fileHelper->absolutizePath($path); - }, $analysedPaths); - $dependencies = $dependencyDumper->dumpDependencies( - $files, - static function (int $count) use ($stdOutputStyole): void { - $stdOutputStyole->progressStart($count); - }, - static function () use ($stdOutputStyole): void { - $stdOutputStyole->progressAdvance(); - }, - count($analysedPaths) > 0 ? $analysedPaths : null - ); - $stdOutputStyole->progressFinish(); - - try { - $stdOutput->writeLineFormatted(Json::encode($dependencies, Json::PRETTY)); - } catch (\Nette\Utils\JsonException $e) { - $inceptionResult->getErrorOutput()->writeLineFormatted(sprintf('%s', $e->getMessage())); - return 1; - } - - return $inceptionResult->handleReturn(0); - } - -} diff --git a/src/Dependency/DependencyDumper.php b/src/Dependency/DependencyDumper.php deleted file mode 100644 index c7480dc01a..0000000000 --- a/src/Dependency/DependencyDumper.php +++ /dev/null @@ -1,97 +0,0 @@ -dependencyResolver = $dependencyResolver; - $this->nodeScopeResolver = $nodeScopeResolver; - $this->parser = $parser; - $this->scopeFactory = $scopeFactory; - $this->fileFinder = $fileFinder; - } - - /** - * @param string[] $files - * @param callable(int $count): void $countCallback - * @param callable(): void $progressCallback - * @param string[]|null $analysedPaths - * @return string[][] - */ - public function dumpDependencies( - array $files, - callable $countCallback, - callable $progressCallback, - ?array $analysedPaths - ): array - { - $analysedFiles = $files; - if ($analysedPaths !== null) { - $analysedFiles = $this->fileFinder->findFiles($analysedPaths)->getFiles(); - } - $this->nodeScopeResolver->setAnalysedFiles($analysedFiles); - $analysedFiles = array_fill_keys($analysedFiles, true); - - $dependencies = []; - $countCallback(count($files)); - foreach ($files as $file) { - try { - $parserNodes = $this->parser->parseFile($file); - } catch (\PHPStan\Parser\ParserErrorsException $e) { - continue; - } - - $fileDependencies = []; - try { - $this->nodeScopeResolver->processNodes( - $parserNodes, - $this->scopeFactory->create(ScopeContext::create($file)), - function (\PhpParser\Node $node, Scope $scope) use ($analysedFiles, &$fileDependencies): void { - $dependencies = $this->dependencyResolver->resolveDependencies($node, $scope); - $fileDependencies = array_merge( - $fileDependencies, - $dependencies->getFileDependencies($scope->getFile(), $analysedFiles) - ); - } - ); - } catch (\PHPStan\AnalysedCodeException $e) { - // pass - } - - foreach (array_unique($fileDependencies) as $fileDependency) { - $dependencies[$fileDependency][] = $file; - } - - $progressCallback(); - } - - return $dependencies; - } - -} diff --git a/tests/PHPStan/Dependency/DependencyDumperTest.php b/tests/PHPStan/Dependency/DependencyDumperTest.php deleted file mode 100644 index 7ed6cff27b..0000000000 --- a/tests/PHPStan/Dependency/DependencyDumperTest.php +++ /dev/null @@ -1,145 +0,0 @@ -getByType(NodeScopeResolver::class); - - /** @var Parser $realParser */ - $realParser = $container->getByType(Parser::class); - - $mockParser = $this->createMock(Parser::class); - $mockParser->method('parseFile') - ->willReturnCallback(static function (string $file) use ($realParser): array { - if (file_exists($file)) { - return $realParser->parseFile($file); - } - - return []; - }); - - /** @var Broker $realBroker */ - $realBroker = $container->getByType(Broker::class); - - $fileHelper = new FileHelper(__DIR__); - - $mockBroker = $this->createMock(Broker::class); - $mockBroker->method('getClass') - ->willReturnCallback(function (string $class) use ($realBroker, $fileHelper): ClassReflection { - if (in_array($class, [ - GrandChild::class, - Child::class, - ParentClass::class, - ], true)) { - return $realBroker->getClass($class); - } - - $nameParts = explode('\\', $class); - $shortClass = array_pop($nameParts); - - $classReflection = $this->createMock(ClassReflection::class); - $classReflection->method('getInterfaces')->willReturn([]); - $classReflection->method('getTraits')->willReturn([]); - $classReflection->method('getParentClass')->willReturn(false); - $classReflection->method('getFilename')->willReturn( - $fileHelper->normalizePath(__DIR__ . '/data/' . $shortClass . '.php') - ); - - return $classReflection; - }); - - $expectedDependencyTree = $this->getExpectedDependencyTree($fileHelper); - - /** @var ScopeFactory $scopeFactory */ - $scopeFactory = $container->getByType(ScopeFactory::class); - - /** @var FileFinder $fileFinder */ - $fileFinder = $container->getService('fileFinderAnalyse'); - - $dumper = new DependencyDumper( - new DependencyResolver($fileHelper, $mockBroker, new ExportedNodeResolver(self::getContainer()->getByType(FileTypeMapper::class), new Standard())), - $nodeScopeResolver, - $mockParser, - $scopeFactory, - $fileFinder - ); - - $dependencies = $dumper->dumpDependencies( - array_merge( - [$fileHelper->normalizePath(__DIR__ . '/data/GrandChild.php')], - array_keys($expectedDependencyTree) - ), - static function (): void { - }, - static function (): void { - }, - null - ); - - $this->assertCount(count($expectedDependencyTree), $dependencies); - foreach ($expectedDependencyTree as $file => $files) { - $this->assertArrayHasKey($file, $dependencies); - $this->assertSame($files, $dependencies[$file]); - } - } - - /** - * @param FileHelper $fileHelper - * @return string[][] - */ - private function getExpectedDependencyTree(FileHelper $fileHelper): array - { - $tree = [ - 'Child.php' => [ - 'GrandChild.php', - ], - 'Parent.php' => [ - 'GrandChild.php', - 'Child.php', - ], - 'MethodNativeReturnTypehint.php' => [ - 'GrandChild.php', - ], - 'MethodPhpDocReturnTypehint.php' => [ - 'GrandChild.php', - ], - 'ParamNativeReturnTypehint.php' => [ - 'GrandChild.php', - ], - 'ParamPhpDocReturnTypehint.php' => [ - 'GrandChild.php', - ], - ]; - - $expectedTree = []; - foreach ($tree as $file => $files) { - $expectedTree[$fileHelper->normalizePath(__DIR__ . '/data/' . $file)] = array_map(static function (string $file) use ($fileHelper): string { - return $fileHelper->normalizePath(__DIR__ . '/data/' . $file); - }, $files); - } - - return $expectedTree; - } - -} diff --git a/tests/PHPStan/Dependency/data/Child.php b/tests/PHPStan/Dependency/data/Child.php deleted file mode 100644 index 8e9e8a3495..0000000000 --- a/tests/PHPStan/Dependency/data/Child.php +++ /dev/null @@ -1,8 +0,0 @@ - Date: Sun, 12 Sep 2021 23:05:51 +0200 Subject: [PATCH 0261/1284] [BCB] Removed baselineNeon error formatter, use --generate-baseline CLI option instead --- conf/config.neon | 5 ----- src/Command/AnalyseCommand.php | 13 ------------- 2 files changed, 18 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index 96066de357..3e8af44e65 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1747,11 +1747,6 @@ services: errorFormatter.raw: class: PHPStan\Command\ErrorFormatter\RawErrorFormatter - errorFormatter.baselineNeon: - class: PHPStan\Command\ErrorFormatter\BaselineNeonErrorFormatter - arguments: - relativePathHelper: @simpleRelativePathHelper - errorFormatter.table: class: PHPStan\Command\ErrorFormatter\TableErrorFormatter arguments: diff --git a/src/Command/AnalyseCommand.php b/src/Command/AnalyseCommand.php index 1cbd1ed38d..4f54ceec3c 100644 --- a/src/Command/AnalyseCommand.php +++ b/src/Command/AnalyseCommand.php @@ -181,19 +181,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int return 1; } - if ($errorFormat === 'baselineNeon') { - $errorOutput = $inceptionResult->getErrorOutput(); - $errorOutput->writeLineFormatted('⚠️ You\'re using an obsolete option --error-format baselineNeon. ⚠️️'); - $errorOutput->writeLineFormatted(''); - $errorOutput->writeLineFormatted(' There\'s a new and much better option --generate-baseline. Here are the advantages:'); - $errorOutput->writeLineFormatted(' 1) The current baseline file does not have to be commented-out'); - $errorOutput->writeLineFormatted(' nor emptied when generating the new baseline. It\'s excluded automatically.'); - $errorOutput->writeLineFormatted(' 2) Output no longer has to be redirected to a file, PHPStan saves the baseline'); - $errorOutput->writeLineFormatted(' to a specified path (defaults to phpstan-baseline.neon).'); - $errorOutput->writeLineFormatted(' 3) Baseline contains correct relative paths if saved to a subdirectory.'); - $errorOutput->writeLineFormatted(''); - } - $generateBaselineFile = $inceptionResult->getGenerateBaselineFile(); if ($generateBaselineFile !== null) { $baselineExtension = pathinfo($generateBaselineFile, PATHINFO_EXTENSION); From 8933c7e28c2ebb1fd69584c031f579cd53ddf4ae Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 12 Sep 2021 23:07:57 +0200 Subject: [PATCH 0262/1284] [BCB] Removed polluteCatchScopeWithTryAssignments config parameter --- conf/config.neon | 3 -- src/Analyser/NodeScopeResolver.php | 7 +--- src/Testing/RuleTestCase.php | 6 --- src/Testing/TypeInferenceTestCase.php | 4 -- .../Analyser/LegacyNodeScopeResolverTest.php | 39 ++++++------------- .../Variables/DefinedVariableRuleTest.php | 32 --------------- 6 files changed, 13 insertions(+), 78 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index 3e8af44e65..2dc97fbd5c 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -99,7 +99,6 @@ parameters: phpVersion: null polluteScopeWithLoopInitialAssignments: true polluteScopeWithAlwaysIterableForeach: true - polluteCatchScopeWithTryAssignments: false propertyAlwaysWrittenTags: [] propertyAlwaysReadTags: [] additionalConstructors: [] @@ -285,7 +284,6 @@ parametersSchema: phpVersion: schema(anyOf(schema(int(), min(70100), max(80099))), nullable()) polluteScopeWithLoopInitialAssignments: bool() polluteScopeWithAlwaysIterableForeach: bool() - polluteCatchScopeWithTryAssignments: bool() propertyAlwaysWrittenTags: listOf(string()) propertyAlwaysReadTags: listOf(string()) additionalConstructors: listOf(string()) @@ -477,7 +475,6 @@ services: arguments: classReflector: @nodeScopeResolverClassReflector polluteScopeWithLoopInitialAssignments: %polluteScopeWithLoopInitialAssignments% - polluteCatchScopeWithTryAssignments: %polluteCatchScopeWithTryAssignments% polluteScopeWithAlwaysIterableForeach: %polluteScopeWithAlwaysIterableForeach% earlyTerminatingMethodCalls: %earlyTerminatingMethodCalls% earlyTerminatingFunctionCalls: %earlyTerminatingFunctionCalls% diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 06ee0c3069..7186cdb1d4 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -150,8 +150,6 @@ class NodeScopeResolver private bool $polluteScopeWithLoopInitialAssignments; - private bool $polluteCatchScopeWithTryAssignments; - private bool $polluteScopeWithAlwaysIterableForeach; /** @var string[][] className(string) => methods(string[]) */ @@ -176,7 +174,6 @@ class NodeScopeResolver * @param FileHelper $fileHelper * @param TypeSpecifier $typeSpecifier * @param bool $polluteScopeWithLoopInitialAssignments - * @param bool $polluteCatchScopeWithTryAssignments * @param bool $polluteScopeWithAlwaysIterableForeach * @param string[][] $earlyTerminatingMethodCalls className(string) => methods(string[]) * @param array $earlyTerminatingFunctionCalls @@ -196,7 +193,6 @@ public function __construct( TypeSpecifier $typeSpecifier, DynamicThrowTypeExtensionProvider $dynamicThrowTypeExtensionProvider, bool $polluteScopeWithLoopInitialAssignments, - bool $polluteCatchScopeWithTryAssignments, bool $polluteScopeWithAlwaysIterableForeach, array $earlyTerminatingMethodCalls, array $earlyTerminatingFunctionCalls, @@ -216,7 +212,6 @@ public function __construct( $this->typeSpecifier = $typeSpecifier; $this->dynamicThrowTypeExtensionProvider = $dynamicThrowTypeExtensionProvider; $this->polluteScopeWithLoopInitialAssignments = $polluteScopeWithLoopInitialAssignments; - $this->polluteCatchScopeWithTryAssignments = $polluteCatchScopeWithTryAssignments; $this->polluteScopeWithAlwaysIterableForeach = $polluteScopeWithAlwaysIterableForeach; $this->earlyTerminatingMethodCalls = $earlyTerminatingMethodCalls; $this->earlyTerminatingFunctionCalls = $earlyTerminatingFunctionCalls; @@ -1185,7 +1180,7 @@ private function processStmtNode( foreach ($stmt->catches as $catchNode) { $nodeCallback($catchNode, $scope); - if ($this->preciseExceptionTracking || !$this->polluteCatchScopeWithTryAssignments) { + if ($this->preciseExceptionTracking) { $catchType = TypeCombinator::union(...array_map(static function (Name $name): Type { return new ObjectType($name->toString()); }, $catchNode->types)); diff --git a/src/Testing/RuleTestCase.php b/src/Testing/RuleTestCase.php index 8d6d7f6611..c2bac456d6 100644 --- a/src/Testing/RuleTestCase.php +++ b/src/Testing/RuleTestCase.php @@ -85,7 +85,6 @@ private function getAnalyser(): Analyser $typeSpecifier, self::getContainer()->getByType(DynamicThrowTypeExtensionProvider::class), $this->shouldPolluteScopeWithLoopInitialAssignments(), - $this->shouldPolluteCatchScopeWithTryAssignments(), $this->shouldPolluteScopeWithAlwaysIterableForeach(), [], [], @@ -185,11 +184,6 @@ protected function shouldPolluteScopeWithLoopInitialAssignments(): bool return false; } - protected function shouldPolluteCatchScopeWithTryAssignments(): bool - { - return false; - } - protected function shouldPolluteScopeWithAlwaysIterableForeach(): bool { return true; diff --git a/src/Testing/TypeInferenceTestCase.php b/src/Testing/TypeInferenceTestCase.php index 06774f7fb8..8630cbd3f9 100644 --- a/src/Testing/TypeInferenceTestCase.php +++ b/src/Testing/TypeInferenceTestCase.php @@ -31,9 +31,6 @@ abstract class TypeInferenceTestCase extends \PHPStan\Testing\TestCase { - /** @var bool */ - protected $polluteCatchScopeWithTryAssignments = true; - /** * @param string $file * @param callable(\PhpParser\Node, \PHPStan\Analyser\Scope): void $callback @@ -77,7 +74,6 @@ public function processFile( $typeSpecifier, self::getContainer()->getByType(DynamicThrowTypeExtensionProvider::class), true, - $this->polluteCatchScopeWithTryAssignments, true, $this->getEarlyTerminatingMethodCalls(), $this->getEarlyTerminatingFunctionCalls(), diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 9f37b047cd..6b12af562f 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -10360,33 +10360,18 @@ public function testTryCatchScope( string $evaluatedPointExpression ): void { - foreach ([true, false] as $polluteCatchScopeWithTryAssignments) { - $this->polluteCatchScopeWithTryAssignments = $polluteCatchScopeWithTryAssignments; - - try { - $this->assertTypes( - __DIR__ . '/data/try-catch-scope.php', - $description, - $expression, - [], - [], - [], - [], - $evaluatedPointExpression, - [], - false - ); - } catch (\PHPUnit\Framework\ExpectationFailedException $e) { - throw new \PHPUnit\Framework\ExpectationFailedException( - sprintf( - '%s (polluteCatchScopeWithTryAssignments: %s)', - $e->getMessage(), - $polluteCatchScopeWithTryAssignments ? 'true' : 'false' - ), - $e->getComparisonFailure() - ); - } - } + $this->assertTypes( + __DIR__ . '/data/try-catch-scope.php', + $description, + $expression, + [], + [], + [], + [], + $evaluatedPointExpression, + [], + false + ); } /** diff --git a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php index 3beb38ad2b..7e82582d7d 100644 --- a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php +++ b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php @@ -17,9 +17,6 @@ class DefinedVariableRuleTest extends \PHPStan\Testing\RuleTestCase /** @var bool */ private $polluteScopeWithLoopInitialAssignments; - /** @var bool */ - private $polluteCatchScopeWithTryAssignments; - /** @var bool */ private $polluteScopeWithAlwaysIterableForeach; @@ -36,11 +33,6 @@ protected function shouldPolluteScopeWithLoopInitialAssignments(): bool return $this->polluteScopeWithLoopInitialAssignments; } - protected function shouldPolluteCatchScopeWithTryAssignments(): bool - { - return $this->polluteCatchScopeWithTryAssignments; - } - protected function shouldPolluteScopeWithAlwaysIterableForeach(): bool { return $this->polluteScopeWithAlwaysIterableForeach; @@ -51,7 +43,6 @@ public function testDefinedVariables(): void require_once __DIR__ . '/data/defined-variables-definition.php'; $this->cliArgumentsVariablesRegistered = true; $this->polluteScopeWithLoopInitialAssignments = false; - $this->polluteCatchScopeWithTryAssignments = false; $this->checkMaybeUndefinedVariables = true; $this->polluteScopeWithAlwaysIterableForeach = true; $this->analyse([__DIR__ . '/data/defined-variables.php'], [ @@ -254,7 +245,6 @@ public function testDefinedVariablesInClosures(): void { $this->cliArgumentsVariablesRegistered = true; $this->polluteScopeWithLoopInitialAssignments = false; - $this->polluteCatchScopeWithTryAssignments = false; $this->checkMaybeUndefinedVariables = true; $this->polluteScopeWithAlwaysIterableForeach = true; $this->analyse([__DIR__ . '/data/defined-variables-closures.php'], [ @@ -269,7 +259,6 @@ public function testDefinedVariablesInShortArrayDestructuringSyntax(): void { $this->cliArgumentsVariablesRegistered = true; $this->polluteScopeWithLoopInitialAssignments = false; - $this->polluteCatchScopeWithTryAssignments = false; $this->checkMaybeUndefinedVariables = true; $this->polluteScopeWithAlwaysIterableForeach = true; $this->analyse([__DIR__ . '/data/defined-variables-array-destructuring-short-syntax.php'], [ @@ -292,7 +281,6 @@ public function testCliArgumentsVariablesNotRegistered(): void { $this->cliArgumentsVariablesRegistered = false; $this->polluteScopeWithLoopInitialAssignments = false; - $this->polluteCatchScopeWithTryAssignments = false; $this->checkMaybeUndefinedVariables = true; $this->polluteScopeWithAlwaysIterableForeach = true; $this->analyse([__DIR__ . '/data/cli-arguments-variables.php'], [ @@ -311,7 +299,6 @@ public function testCliArgumentsVariablesRegistered(): void { $this->cliArgumentsVariablesRegistered = true; $this->polluteScopeWithLoopInitialAssignments = false; - $this->polluteCatchScopeWithTryAssignments = false; $this->checkMaybeUndefinedVariables = true; $this->polluteScopeWithAlwaysIterableForeach = true; $this->analyse([__DIR__ . '/data/cli-arguments-variables.php'], [ @@ -370,7 +357,6 @@ public function testLoopInitialAssignments( ): void { $this->cliArgumentsVariablesRegistered = false; - $this->polluteCatchScopeWithTryAssignments = false; $this->polluteScopeWithLoopInitialAssignments = $polluteScopeWithLoopInitialAssignments; $this->checkMaybeUndefinedVariables = $checkMaybeUndefinedVariables; $this->polluteScopeWithAlwaysIterableForeach = true; @@ -381,7 +367,6 @@ public function testDefineVariablesInClass(): void { $this->cliArgumentsVariablesRegistered = true; $this->polluteScopeWithLoopInitialAssignments = false; - $this->polluteCatchScopeWithTryAssignments = false; $this->checkMaybeUndefinedVariables = true; $this->polluteScopeWithAlwaysIterableForeach = true; $this->analyse([__DIR__ . '/data/define-variables-class.php'], []); @@ -391,7 +376,6 @@ public function testDeadBranches(): void { $this->cliArgumentsVariablesRegistered = true; $this->polluteScopeWithLoopInitialAssignments = false; - $this->polluteCatchScopeWithTryAssignments = false; $this->checkMaybeUndefinedVariables = true; $this->polluteScopeWithAlwaysIterableForeach = true; $this->analyse([__DIR__ . '/data/dead-branches.php'], [ @@ -422,7 +406,6 @@ public function testForeach(): void { $this->cliArgumentsVariablesRegistered = true; $this->polluteScopeWithLoopInitialAssignments = false; - $this->polluteCatchScopeWithTryAssignments = false; $this->checkMaybeUndefinedVariables = true; $this->polluteScopeWithAlwaysIterableForeach = true; $this->analyse([__DIR__ . '/data/foreach.php'], [ @@ -595,7 +578,6 @@ public function testForeachPolluteScopeWithAlwaysIterableForeach(bool $polluteSc { $this->cliArgumentsVariablesRegistered = true; $this->polluteScopeWithLoopInitialAssignments = false; - $this->polluteCatchScopeWithTryAssignments = false; $this->checkMaybeUndefinedVariables = true; $this->polluteScopeWithAlwaysIterableForeach = $polluteScopeWithAlwaysIterableForeach; $this->analyse([__DIR__ . '/data/foreach-always-iterable.php'], $errors); @@ -605,7 +587,6 @@ public function testBooleanOperatorsTruthyFalsey(): void { $this->cliArgumentsVariablesRegistered = true; $this->polluteScopeWithLoopInitialAssignments = false; - $this->polluteCatchScopeWithTryAssignments = false; $this->checkMaybeUndefinedVariables = true; $this->polluteScopeWithAlwaysIterableForeach = true; $this->analyse([__DIR__ . '/data/boolean-op-truthy-falsey.php'], [ @@ -628,7 +609,6 @@ public function testArrowFunctions(): void $this->cliArgumentsVariablesRegistered = true; $this->polluteScopeWithLoopInitialAssignments = false; - $this->polluteCatchScopeWithTryAssignments = false; $this->checkMaybeUndefinedVariables = true; $this->polluteScopeWithAlwaysIterableForeach = true; $this->analyse([__DIR__ . '/data/defined-variables-arrow-functions.php'], [ @@ -651,7 +631,6 @@ public function testCoalesceAssign(): void $this->cliArgumentsVariablesRegistered = true; $this->polluteScopeWithLoopInitialAssignments = false; - $this->polluteCatchScopeWithTryAssignments = false; $this->checkMaybeUndefinedVariables = true; $this->polluteScopeWithAlwaysIterableForeach = true; $this->analyse([__DIR__ . '/data/defined-variables-coalesce-assign.php'], [ @@ -666,7 +645,6 @@ public function testBug2748(): void { $this->cliArgumentsVariablesRegistered = true; $this->polluteScopeWithLoopInitialAssignments = false; - $this->polluteCatchScopeWithTryAssignments = false; $this->checkMaybeUndefinedVariables = true; $this->polluteScopeWithAlwaysIterableForeach = true; $this->analyse([__DIR__ . '/data/bug-2748.php'], [ @@ -685,7 +663,6 @@ public function testGlobalVariables(): void { $this->cliArgumentsVariablesRegistered = true; $this->polluteScopeWithLoopInitialAssignments = false; - $this->polluteCatchScopeWithTryAssignments = false; $this->checkMaybeUndefinedVariables = true; $this->polluteScopeWithAlwaysIterableForeach = true; $this->analyse([__DIR__ . '/data/global-variables.php'], []); @@ -695,7 +672,6 @@ public function testRootScopeMaybeDefined(): void { $this->cliArgumentsVariablesRegistered = true; $this->polluteScopeWithLoopInitialAssignments = false; - $this->polluteCatchScopeWithTryAssignments = false; $this->checkMaybeUndefinedVariables = false; $this->polluteScopeWithAlwaysIterableForeach = true; $this->analyse([__DIR__ . '/data/root-scope-maybe.php'], []); @@ -705,7 +681,6 @@ public function testRootScopeMaybeDefinedCheck(): void { $this->cliArgumentsVariablesRegistered = true; $this->polluteScopeWithLoopInitialAssignments = false; - $this->polluteCatchScopeWithTryAssignments = false; $this->checkMaybeUndefinedVariables = true; $this->polluteScopeWithAlwaysIterableForeach = true; $this->analyse([__DIR__ . '/data/root-scope-maybe.php'], [ @@ -724,7 +699,6 @@ public function testFormerThisVariableRule(): void { $this->cliArgumentsVariablesRegistered = true; $this->polluteScopeWithLoopInitialAssignments = false; - $this->polluteCatchScopeWithTryAssignments = false; $this->checkMaybeUndefinedVariables = true; $this->polluteScopeWithAlwaysIterableForeach = true; $this->analyse([__DIR__ . '/data/this.php'], [ @@ -751,7 +725,6 @@ public function testClosureUse(): void { $this->cliArgumentsVariablesRegistered = true; $this->polluteScopeWithLoopInitialAssignments = false; - $this->polluteCatchScopeWithTryAssignments = false; $this->checkMaybeUndefinedVariables = true; $this->polluteScopeWithAlwaysIterableForeach = true; $this->analyse([__DIR__ . '/data/defined-variables-anonymous-function-use.php'], [ @@ -790,7 +763,6 @@ public function testNullsafeIsset(): void $this->cliArgumentsVariablesRegistered = true; $this->polluteScopeWithLoopInitialAssignments = false; - $this->polluteCatchScopeWithTryAssignments = false; $this->checkMaybeUndefinedVariables = true; $this->polluteScopeWithAlwaysIterableForeach = true; $this->analyse([__DIR__ . '/data/variable-nullsafe-isset.php'], []); @@ -800,7 +772,6 @@ public function testBug1306(): void { $this->cliArgumentsVariablesRegistered = true; $this->polluteScopeWithLoopInitialAssignments = false; - $this->polluteCatchScopeWithTryAssignments = false; $this->checkMaybeUndefinedVariables = true; $this->polluteScopeWithAlwaysIterableForeach = true; $this->analyse([__DIR__ . '/data/bug-1306.php'], []); @@ -810,7 +781,6 @@ public function testBug3515(): void { $this->cliArgumentsVariablesRegistered = true; $this->polluteScopeWithLoopInitialAssignments = false; - $this->polluteCatchScopeWithTryAssignments = false; $this->checkMaybeUndefinedVariables = true; $this->polluteScopeWithAlwaysIterableForeach = true; $this->analyse([__DIR__ . '/data/bug-3515.php'], [ @@ -829,7 +799,6 @@ public function testBug4412(): void { $this->cliArgumentsVariablesRegistered = true; $this->polluteScopeWithLoopInitialAssignments = false; - $this->polluteCatchScopeWithTryAssignments = false; $this->checkMaybeUndefinedVariables = true; $this->polluteScopeWithAlwaysIterableForeach = true; $this->analyse([__DIR__ . '/data/bug-4412.php'], [ @@ -844,7 +813,6 @@ public function testBug3283(): void { $this->cliArgumentsVariablesRegistered = true; $this->polluteScopeWithLoopInitialAssignments = false; - $this->polluteCatchScopeWithTryAssignments = false; $this->checkMaybeUndefinedVariables = true; $this->polluteScopeWithAlwaysIterableForeach = true; $this->analyse([__DIR__ . '/data/bug-3283.php'], []); From 4588e73dd618e1077bfe1cec7b8e62e251d5eb95 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 12 Sep 2021 23:12:37 +0200 Subject: [PATCH 0263/1284] Precise exception tracking for everyone --- conf/bleedingEdge.neon | 1 - conf/config.level4.neon | 12 +- conf/config.neon | 3 - src/Analyser/NodeScopeResolver.php | 151 +++++++++--------------- src/Testing/RuleTestCase.php | 1 - src/Testing/TypeInferenceTestCase.php | 1 - tests/PHPStan/Analyser/AnalyserTest.php | 2 - 7 files changed, 57 insertions(+), 114 deletions(-) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 245fb055b1..6c4006b75a 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -17,7 +17,6 @@ parameters: objectFromNewClass: true skipCheckGenericClasses: [] rememberFunctionValues: true - preciseExceptionTracking: true apiRules: true deepInspectTypes: true neverInGenericReturnType: true diff --git a/conf/config.level4.neon b/conf/config.level4.neon index dc7cb46a1d..f0b24f7bab 100644 --- a/conf/config.level4.neon +++ b/conf/config.level4.neon @@ -6,6 +6,8 @@ rules: - PHPStan\Rules\Comparison\NumberComparisonOperatorsConstantConditionRule - PHPStan\Rules\DeadCode\NoopRule - PHPStan\Rules\DeadCode\UnreachableStatementRule + - PHPStan\Rules\Exceptions\CatchWithUnthrownExceptionRule + - PHPStan\Rules\Exceptions\OverwrittenExitPointByFinallyRule - PHPStan\Rules\Functions\CallToFunctionStamentWithoutSideEffectsRule - PHPStan\Rules\Methods\CallToConstructorStatementWithoutSideEffectsRule - PHPStan\Rules\Methods\CallToMethodStamentWithoutSideEffectsRule @@ -17,10 +19,6 @@ rules: - PHPStan\Rules\TooWideTypehints\TooWideFunctionReturnTypehintRule conditionalTags: - PHPStan\Rules\Exceptions\CatchWithUnthrownExceptionRule: - phpstan.rules.rule: %featureToggles.preciseExceptionTracking% - PHPStan\Rules\Exceptions\OverwrittenExitPointByFinallyRule: - phpstan.rules.rule: %featureToggles.preciseExceptionTracking% PHPStan\Rules\DeadCode\UnusedPrivateConstantRule: phpstan.rules.rule: %featureToggles.unusedClassElements% PHPStan\Rules\DeadCode\UnusedPrivateMethodRule: @@ -149,12 +147,6 @@ services: tags: - phpstan.rules.rule - - - class: PHPStan\Rules\Exceptions\CatchWithUnthrownExceptionRule - - - - class: PHPStan\Rules\Exceptions\OverwrittenExitPointByFinallyRule - - class: PHPStan\Rules\TooWideTypehints\TooWideMethodReturnTypehintRule arguments: diff --git a/conf/config.neon b/conf/config.neon index 2dc97fbd5c..741dafc2b8 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -42,7 +42,6 @@ parameters: - RecursiveArrayIterator - WeakMap rememberFunctionValues: false - preciseExceptionTracking: false apiRules: false deepInspectTypes: false neverInGenericReturnType: false @@ -228,7 +227,6 @@ parametersSchema: objectFromNewClass: bool(), skipCheckGenericClasses: listOf(string()), rememberFunctionValues: bool(), - preciseExceptionTracking: bool(), apiRules: bool(), deepInspectTypes: bool(), neverInGenericReturnType: bool(), @@ -479,7 +477,6 @@ services: earlyTerminatingMethodCalls: %earlyTerminatingMethodCalls% earlyTerminatingFunctionCalls: %earlyTerminatingFunctionCalls% implicitThrows: %implicitThrows% - preciseExceptionTracking: %featureToggles.preciseExceptionTracking% - implement: PHPStan\Analyser\ResultCache\ResultCacheManagerFactory diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 7186cdb1d4..cd565f360b 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -160,8 +160,6 @@ class NodeScopeResolver private bool $implicitThrows; - private bool $preciseExceptionTracking; - /** @var bool[] filePath(string) => bool(true) */ private array $analysedFiles = []; @@ -178,7 +176,6 @@ class NodeScopeResolver * @param string[][] $earlyTerminatingMethodCalls className(string) => methods(string[]) * @param array $earlyTerminatingFunctionCalls * @param bool $implicitThrows - * @param bool $preciseExceptionTracking */ public function __construct( ReflectionProvider $reflectionProvider, @@ -196,8 +193,7 @@ public function __construct( bool $polluteScopeWithAlwaysIterableForeach, array $earlyTerminatingMethodCalls, array $earlyTerminatingFunctionCalls, - bool $implicitThrows, - bool $preciseExceptionTracking + bool $implicitThrows ) { $this->reflectionProvider = $reflectionProvider; @@ -216,7 +212,6 @@ public function __construct( $this->earlyTerminatingMethodCalls = $earlyTerminatingMethodCalls; $this->earlyTerminatingFunctionCalls = $earlyTerminatingFunctionCalls; $this->implicitThrows = $implicitThrows; - $this->preciseExceptionTracking = $preciseExceptionTracking; } /** @@ -1180,89 +1175,78 @@ private function processStmtNode( foreach ($stmt->catches as $catchNode) { $nodeCallback($catchNode, $scope); - if ($this->preciseExceptionTracking) { - $catchType = TypeCombinator::union(...array_map(static function (Name $name): Type { - return new ObjectType($name->toString()); - }, $catchNode->types)); - $originalCatchType = $catchType; - $catchType = TypeCombinator::remove($catchType, $pastCatchTypes); - $pastCatchTypes = TypeCombinator::union($pastCatchTypes, $originalCatchType); - $matchingThrowPoints = []; - $newThrowPoints = []; - foreach ($throwPoints as $throwPoint) { - if (!$throwPoint->isExplicit() && !$catchType->isSuperTypeOf(new ObjectType(\Throwable::class))->yes()) { - continue; - } - $isSuperType = $catchType->isSuperTypeOf($throwPoint->getType()); - if ($isSuperType->no()) { - continue; - } + $catchType = TypeCombinator::union(...array_map(static function (Name $name): Type { + return new ObjectType($name->toString()); + }, $catchNode->types)); + $originalCatchType = $catchType; + $catchType = TypeCombinator::remove($catchType, $pastCatchTypes); + $pastCatchTypes = TypeCombinator::union($pastCatchTypes, $originalCatchType); + $matchingThrowPoints = []; + $newThrowPoints = []; + foreach ($throwPoints as $throwPoint) { + if (!$throwPoint->isExplicit() && !$catchType->isSuperTypeOf(new ObjectType(\Throwable::class))->yes()) { + continue; + } + $isSuperType = $catchType->isSuperTypeOf($throwPoint->getType()); + if ($isSuperType->no()) { + continue; + } + $matchingThrowPoints[] = $throwPoint; + } + $hasExplicit = count($matchingThrowPoints) > 0; + foreach ($throwPoints as $throwPoint) { + $isSuperType = $catchType->isSuperTypeOf($throwPoint->getType()); + if (!$hasExplicit && !$isSuperType->no()) { $matchingThrowPoints[] = $throwPoint; } - $hasExplicit = count($matchingThrowPoints) > 0; - foreach ($throwPoints as $throwPoint) { - $isSuperType = $catchType->isSuperTypeOf($throwPoint->getType()); - if (!$hasExplicit && !$isSuperType->no()) { - $matchingThrowPoints[] = $throwPoint; - } - if ($isSuperType->yes()) { - continue; - } - $newThrowPoints[] = $throwPoint->subtractCatchType($catchType); + if ($isSuperType->yes()) { + continue; } - $throwPoints = $newThrowPoints; - - if (count($matchingThrowPoints) === 0) { - $throwableThrowPoints = []; - if ($originalCatchType->isSuperTypeOf(new ObjectType(\Throwable::class))->yes()) { - foreach ($branchScopeResult->getThrowPoints() as $originalThrowPoint) { - if (!$originalThrowPoint->canContainAnyThrowable()) { - continue; - } + $newThrowPoints[] = $throwPoint->subtractCatchType($catchType); + } + $throwPoints = $newThrowPoints; - $throwableThrowPoints[] = $originalThrowPoint; + if (count($matchingThrowPoints) === 0) { + $throwableThrowPoints = []; + if ($originalCatchType->isSuperTypeOf(new ObjectType(\Throwable::class))->yes()) { + foreach ($branchScopeResult->getThrowPoints() as $originalThrowPoint) { + if (!$originalThrowPoint->canContainAnyThrowable()) { + continue; } - } - if (count($throwableThrowPoints) === 0) { - $nodeCallback(new CatchWithUnthrownExceptionNode($catchNode, $catchType, $originalCatchType), $scope); - continue; + $throwableThrowPoints[] = $originalThrowPoint; } - - $matchingThrowPoints = $throwableThrowPoints; } - $catchScope = null; - foreach ($matchingThrowPoints as $matchingThrowPoint) { - if ($catchScope === null) { - $catchScope = $matchingThrowPoint->getScope(); - } else { - $catchScope = $catchScope->mergeWith($matchingThrowPoint->getScope()); - } + if (count($throwableThrowPoints) === 0) { + $nodeCallback(new CatchWithUnthrownExceptionNode($catchNode, $catchType, $originalCatchType), $scope); + continue; } - $variableName = null; - if ($catchNode->var !== null) { - if (!is_string($catchNode->var->name)) { - throw new \PHPStan\ShouldNotHappenException(); - } + $matchingThrowPoints = $throwableThrowPoints; + } - $variableName = $catchNode->var->name; + $catchScope = null; + foreach ($matchingThrowPoints as $matchingThrowPoint) { + if ($catchScope === null) { + $catchScope = $matchingThrowPoint->getScope(); + } else { + $catchScope = $catchScope->mergeWith($matchingThrowPoint->getScope()); } + } - $catchScopeResult = $this->processStmtNodes($catchNode, $catchNode->stmts, $catchScope->enterCatchType($catchType, $variableName), $nodeCallback); - $catchScopeForFinally = $catchScopeResult->getScope(); - } else { - $initialScope = $scope; - if (count($throwPoints) > 0) { - $initialScope = $throwPoints[0]->getScope(); + $variableName = null; + if ($catchNode->var !== null) { + if (!is_string($catchNode->var->name)) { + throw new \PHPStan\ShouldNotHappenException(); } - $catchScopeForFinally = $this->processCatchNode($catchNode, $branchScope, $nodeCallback)->getScope(); - $catchScopeResult = $this->processCatchNode($catchNode, $initialScope->mergeWith($branchScope), static function (): void { - }); + $variableName = $catchNode->var->name; } + $catchScopeResult = $this->processStmtNodes($catchNode, $catchNode->stmts, $catchScope->enterCatchType($catchType, $variableName), $nodeCallback); + $catchScopeForFinally = $catchScopeResult->getScope(); + $finalScope = $catchScopeResult->isAlwaysTerminating() ? $finalScope : $catchScopeResult->getScope()->mergeWith($finalScope); $alwaysTerminating = $alwaysTerminating && $catchScopeResult->isAlwaysTerminating(); $hasYield = $hasYield || $catchScopeResult->hasYield(); @@ -1501,31 +1485,6 @@ private function createAstClassReflection(Node\Stmt\ClassLike $stmt, Scope $scop ); } - /** - * @param Node\Stmt\Catch_ $catchNode - * @param MutatingScope $catchScope - * @param callable(\PhpParser\Node $node, Scope $scope): void $nodeCallback - * @return StatementResult - */ - private function processCatchNode( - Node\Stmt\Catch_ $catchNode, - MutatingScope $catchScope, - callable $nodeCallback - ): StatementResult - { - $variableName = null; - if ($catchNode->var !== null) { - if (!is_string($catchNode->var->name)) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $variableName = $catchNode->var->name; - } - - $catchScope = $catchScope->enterCatch($catchNode->types, $variableName); - return $this->processStmtNodes($catchNode, $catchNode->stmts, $catchScope, $nodeCallback); - } - private function lookForEnterVariableAssign(MutatingScope $scope, Expr $expr): MutatingScope { if (!$expr instanceof ArrayDimFetch || $expr->dim !== null) { diff --git a/src/Testing/RuleTestCase.php b/src/Testing/RuleTestCase.php index c2bac456d6..3d9f831cdf 100644 --- a/src/Testing/RuleTestCase.php +++ b/src/Testing/RuleTestCase.php @@ -88,7 +88,6 @@ private function getAnalyser(): Analyser $this->shouldPolluteScopeWithAlwaysIterableForeach(), [], [], - true, true ); $fileAnalyser = new FileAnalyser( diff --git a/src/Testing/TypeInferenceTestCase.php b/src/Testing/TypeInferenceTestCase.php index 8630cbd3f9..7095ad9773 100644 --- a/src/Testing/TypeInferenceTestCase.php +++ b/src/Testing/TypeInferenceTestCase.php @@ -77,7 +77,6 @@ public function processFile( true, $this->getEarlyTerminatingMethodCalls(), $this->getEarlyTerminatingFunctionCalls(), - true, true ); $resolver->setAnalysedFiles(array_map(static function (string $file) use ($fileHelper): string { diff --git a/tests/PHPStan/Analyser/AnalyserTest.php b/tests/PHPStan/Analyser/AnalyserTest.php index a98abc07f5..b06373d234 100644 --- a/tests/PHPStan/Analyser/AnalyserTest.php +++ b/tests/PHPStan/Analyser/AnalyserTest.php @@ -515,11 +515,9 @@ private function createAnalyser(bool $reportUnmatchedIgnoredErrors): \PHPStan\An $typeSpecifier, self::getContainer()->getByType(DynamicThrowTypeExtensionProvider::class), false, - false, true, [], [], - true, true ); $lexer = new \PhpParser\Lexer(['usedAttributes' => ['comments', 'startLine', 'endLine', 'startTokenPos', 'endTokenPos']]); From f76875a84d8dcca8f9d67c08b6492c609e9aff98 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 12 Sep 2021 23:15:08 +0200 Subject: [PATCH 0264/1284] [BCB] NodeDependencies no longer iterable --- src/Dependency/NodeDependencies.php | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/Dependency/NodeDependencies.php b/src/Dependency/NodeDependencies.php index b693e5091c..cd75370e7d 100644 --- a/src/Dependency/NodeDependencies.php +++ b/src/Dependency/NodeDependencies.php @@ -2,14 +2,10 @@ namespace PHPStan\Dependency; -use IteratorAggregate; use PHPStan\File\FileHelper; use PHPStan\Reflection\ReflectionWithFilename; -/** - * @implements \IteratorAggregate - */ -class NodeDependencies implements IteratorAggregate +class NodeDependencies { private FileHelper $fileHelper; @@ -34,11 +30,6 @@ public function __construct( $this->exportedNode = $exportedNode; } - public function getIterator(): \Traversable - { - return new \ArrayIterator($this->reflections); - } - /** * @param string $currentFile * @param array $analysedFiles From e86742db58f9cb3459b777570cc33d64af0ed8fa Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 12 Sep 2021 23:15:52 +0200 Subject: [PATCH 0265/1284] get_class() returns false for mixed argument types This reverts commit 921f48e5772e287e28d9696461f114eb03b873ff. --- .../Php/GetClassDynamicReturnTypeExtension.php | 14 ++++++++++++-- tests/PHPStan/Analyser/data/generics.php | 4 ++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Type/Php/GetClassDynamicReturnTypeExtension.php b/src/Type/Php/GetClassDynamicReturnTypeExtension.php index 5a1bc77551..b2a62501f9 100644 --- a/src/Type/Php/GetClassDynamicReturnTypeExtension.php +++ b/src/Type/Php/GetClassDynamicReturnTypeExtension.php @@ -49,9 +49,19 @@ static function (Type $type, callable $traverse): Type { } if ($type instanceof TemplateType && !$type instanceof TypeWithClassName) { - return new GenericClassStringType($type); + if ($type instanceof ObjectWithoutClassType) { + return new GenericClassStringType($type); + } + + return new UnionType([ + new GenericClassStringType($type), + new ConstantBooleanType(false), + ]); } elseif ($type instanceof MixedType) { - return new ClassStringType(); + return new UnionType([ + new ClassStringType(), + new ConstantBooleanType(false), + ]); } elseif ($type instanceof StaticType) { return new GenericClassStringType($type->getStaticObjectType()); } elseif ($type instanceof TypeWithClassName) { diff --git a/tests/PHPStan/Analyser/data/generics.php b/tests/PHPStan/Analyser/data/generics.php index b034b2fe1b..aaecc87e75 100644 --- a/tests/PHPStan/Analyser/data/generics.php +++ b/tests/PHPStan/Analyser/data/generics.php @@ -1299,7 +1299,7 @@ function arrayOfGenericClassStrings(array $a): void function getClassOnTemplateType($a, $b, $c, $d, $object, $mixed, $tObject) { assertType( - 'class-string', + 'class-string|false', get_class($a) ); assertType( @@ -1324,7 +1324,7 @@ function getClassOnTemplateType($a, $b, $c, $d, $object, $mixed, $tObject) ); assertType('class-string', get_class($object)); - assertType('class-string', get_class($mixed)); + assertType('class-string|false', get_class($mixed)); assertType('class-string', get_class($tObject)); } From d25c5e5a6c62a4537d4a108707ff26b5bea687d9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 12 Sep 2021 23:21:22 +0200 Subject: [PATCH 0266/1284] Deprecated excludes_analyse option --- build/phpstan.neon | 2 +- src/Command/CommandHelper.php | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/build/phpstan.neon b/build/phpstan.neon index 2ae97ea454..a42918c5dc 100644 --- a/build/phpstan.neon +++ b/build/phpstan.neon @@ -18,7 +18,7 @@ parameters: - ../tests/phpstan-bootstrap.php checkUninitializedProperties: true checkMissingCallableSignature: true - excludes_analyse: + excludePaths: - ../src/Reflection/SignatureMap/functionMap.php - ../src/Reflection/SignatureMap/functionMetadata.php - ../tests/*/data/* diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php index 1a5caa2dce..e743d13cf8 100644 --- a/src/Command/CommandHelper.php +++ b/src/Command/CommandHelper.php @@ -419,6 +419,10 @@ public static function begin( $errorOutput->writeLineFormatted(''); throw new \PHPStan\Command\InceptionNotSuccessfulException(); + } elseif (count($excludesAnalyse) > 0) { + $errorOutput->writeLineFormatted('⚠️ You\'re using a deprecated config option excludes_analyse. ⚠️️'); + $errorOutput->writeLineFormatted(''); + $errorOutput->writeLineFormatted(sprintf('Parameter excludes_analyse has been deprecated so use excludePaths only from now on.')); } $tempResultCachePath = $container->getParameter('tempResultCachePath'); From 7a21246cae9dd7968bf7bef92223b53f5d681b72 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 12 Sep 2021 23:22:17 +0200 Subject: [PATCH 0267/1284] [BCB] Removed autoload_files --- conf/config.neon | 3 -- src/Command/CommandHelper.php | 29 ------------------- .../BetterReflectionSourceLocatorFactory.php | 7 +---- 3 files changed, 1 insertion(+), 38 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index 741dafc2b8..0026004d23 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -9,7 +9,6 @@ parameters: excludes_analyse: [] excludePaths: null autoload_directories: [] - autoload_files: [] level: null paths: [] exceptions: @@ -195,7 +194,6 @@ parametersSchema: ]) ), nullable()) autoload_directories: listOf(string()) - autoload_files: listOf(string()) level: schema(anyOf(int(), string()), nullable()) paths: listOf(string()) exceptions: structure([ @@ -1687,7 +1685,6 @@ services: parser: @phpParserDecorator php8Parser: @php8PhpParser autoloadDirectories: %autoload_directories% - autoloadFiles: %autoload_files% scanFiles: %scanFiles% scanDirectories: %scanDirectories% analysedPaths: %analysedPaths% diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php index e743d13cf8..20569322de 100644 --- a/src/Command/CommandHelper.php +++ b/src/Command/CommandHelper.php @@ -308,25 +308,6 @@ public static function begin( throw new \PHPStan\Command\InceptionNotSuccessfulException(); } - $autoloadFiles = $container->getParameter('autoload_files'); - if ($manageMemoryLimitFile && count($autoloadFiles) > 0) { - $errorOutput->writeLineFormatted('⚠️ You\'re using a deprecated config option autoload_files. ⚠️️'); - $errorOutput->writeLineFormatted(''); - $errorOutput->writeLineFormatted('You might not need it anymore - try removing it from your'); - $errorOutput->writeLineFormatted('configuration file and run PHPStan again.'); - $errorOutput->writeLineFormatted(''); - $errorOutput->writeLineFormatted('If the analysis fails, there are now two distinct options'); - $errorOutput->writeLineFormatted('to choose from to replace autoload_files:'); - $errorOutput->writeLineFormatted('1) scanFiles - PHPStan will scan those for classes and functions'); - $errorOutput->writeLineFormatted(' definitions. PHPStan will not execute those files.'); - $errorOutput->writeLineFormatted('2) bootstrapFiles - PHPStan will execute these files to prepare'); - $errorOutput->writeLineFormatted(' the PHP runtime environment for the analysis.'); - $errorOutput->writeLineFormatted(''); - $errorOutput->writeLineFormatted('Read more about this in PHPStan\'s documentation:'); - $errorOutput->writeLineFormatted('https://phpstan.org/user-guide/discovering-symbols'); - $errorOutput->writeLineFormatted(''); - } - $autoloadDirectories = $container->getParameter('autoload_directories'); if (count($autoloadDirectories) > 0 && $manageMemoryLimitFile) { $errorOutput->writeLineFormatted('⚠️ You\'re using a deprecated config option autoload_directories. ⚠️️'); @@ -341,16 +322,6 @@ public static function begin( $errorOutput->writeLineFormatted(''); } - foreach ($autoloadFiles as $parameterAutoloadFile) { - if (!file_exists($parameterAutoloadFile)) { - $errorOutput->writeLineFormatted(sprintf('Autoload file %s does not exist.', $parameterAutoloadFile)); - throw new \PHPStan\Command\InceptionNotSuccessfulException(); - } - (static function (string $file) use ($container): void { - require_once $file; - })($parameterAutoloadFile); - } - $bootstrapFile = $container->getParameter('bootstrap'); if ($bootstrapFile !== null) { if ($manageMemoryLimitFile) { diff --git a/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php b/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php index ad3962e587..63108ab313 100644 --- a/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php +++ b/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php @@ -56,9 +56,6 @@ class BetterReflectionSourceLocatorFactory /** @var string[] */ private $autoloadDirectories; - /** @var string[] */ - private $autoloadFiles; - /** @var string[] */ private $scanFiles; @@ -102,7 +99,6 @@ public function __construct( AutoloadSourceLocator $autoloadSourceLocator, Container $container, array $autoloadDirectories, - array $autoloadFiles, array $scanFiles, array $scanDirectories, array $analysedPaths, @@ -122,7 +118,6 @@ public function __construct( $this->autoloadSourceLocator = $autoloadSourceLocator; $this->container = $container; $this->autoloadDirectories = $autoloadDirectories; - $this->autoloadFiles = $autoloadFiles; $this->scanFiles = $scanFiles; $this->scanDirectories = $scanDirectories; $this->analysedPaths = $analysedPaths; @@ -156,7 +151,7 @@ public function create(): SourceLocator $analysedDirectories[] = $analysedPath; } - $analysedFiles = array_unique(array_merge($analysedFiles, $this->autoloadFiles, $this->scanFiles)); + $analysedFiles = array_unique(array_merge($analysedFiles, $this->scanFiles)); foreach ($analysedFiles as $analysedFile) { $locators[] = $this->optimizedSingleFileSourceLocatorRepository->getOrCreate($analysedFile); } From f67b48a71220b295a1704fe9749492e5de9ee18f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 12 Sep 2021 23:23:20 +0200 Subject: [PATCH 0268/1284] [BCB] Removed autoload_directories --- conf/config.neon | 3 --- src/Command/CommandHelper.php | 14 -------------- .../BetterReflectionSourceLocatorFactory.php | 9 +-------- 3 files changed, 1 insertion(+), 25 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index 0026004d23..e4b4c12bfa 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -8,7 +8,6 @@ parameters: - ../stubs/runtime/Attribute.php excludes_analyse: [] excludePaths: null - autoload_directories: [] level: null paths: [] exceptions: @@ -193,7 +192,6 @@ parametersSchema: analyseAndScan: listOf(string()) ]) ), nullable()) - autoload_directories: listOf(string()) level: schema(anyOf(int(), string()), nullable()) paths: listOf(string()) exceptions: structure([ @@ -1684,7 +1682,6 @@ services: arguments: parser: @phpParserDecorator php8Parser: @php8PhpParser - autoloadDirectories: %autoload_directories% scanFiles: %scanFiles% scanDirectories: %scanDirectories% analysedPaths: %analysedPaths% diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php index 20569322de..e6a74c9f02 100644 --- a/src/Command/CommandHelper.php +++ b/src/Command/CommandHelper.php @@ -308,20 +308,6 @@ public static function begin( throw new \PHPStan\Command\InceptionNotSuccessfulException(); } - $autoloadDirectories = $container->getParameter('autoload_directories'); - if (count($autoloadDirectories) > 0 && $manageMemoryLimitFile) { - $errorOutput->writeLineFormatted('⚠️ You\'re using a deprecated config option autoload_directories. ⚠️️'); - $errorOutput->writeLineFormatted(''); - $errorOutput->writeLineFormatted('You might not need it anymore - try removing it from your'); - $errorOutput->writeLineFormatted('configuration file and run PHPStan again.'); - $errorOutput->writeLineFormatted(''); - $errorOutput->writeLineFormatted('If the analysis fails, replace it with scanDirectories.'); - $errorOutput->writeLineFormatted(''); - $errorOutput->writeLineFormatted('Read more about this in PHPStan\'s documentation:'); - $errorOutput->writeLineFormatted('https://phpstan.org/user-guide/discovering-symbols'); - $errorOutput->writeLineFormatted(''); - } - $bootstrapFile = $container->getParameter('bootstrap'); if ($bootstrapFile !== null) { if ($manageMemoryLimitFile) { diff --git a/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php b/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php index 63108ab313..e8a3289b90 100644 --- a/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php +++ b/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php @@ -53,9 +53,6 @@ class BetterReflectionSourceLocatorFactory /** @var \PHPStan\DependencyInjection\Container */ private $container; - /** @var string[] */ - private $autoloadDirectories; - /** @var string[] */ private $scanFiles; @@ -78,8 +75,6 @@ class BetterReflectionSourceLocatorFactory private array $staticReflectionClassNamePatterns; /** - * @param string[] $autoloadDirectories - * @param string[] $autoloadFiles * @param string[] $scanFiles * @param string[] $scanDirectories * @param string[] $analysedPaths @@ -98,7 +93,6 @@ public function __construct( ComposerJsonAndInstalledJsonSourceLocatorMaker $composerJsonAndInstalledJsonSourceLocatorMaker, AutoloadSourceLocator $autoloadSourceLocator, Container $container, - array $autoloadDirectories, array $scanFiles, array $scanDirectories, array $analysedPaths, @@ -117,7 +111,6 @@ public function __construct( $this->composerJsonAndInstalledJsonSourceLocatorMaker = $composerJsonAndInstalledJsonSourceLocatorMaker; $this->autoloadSourceLocator = $autoloadSourceLocator; $this->container = $container; - $this->autoloadDirectories = $autoloadDirectories; $this->scanFiles = $scanFiles; $this->scanDirectories = $scanDirectories; $this->analysedPaths = $analysedPaths; @@ -156,7 +149,7 @@ public function create(): SourceLocator $locators[] = $this->optimizedSingleFileSourceLocatorRepository->getOrCreate($analysedFile); } - $directories = array_unique(array_merge($analysedDirectories, $this->autoloadDirectories, $this->scanDirectories)); + $directories = array_unique(array_merge($analysedDirectories, $this->scanDirectories)); foreach ($directories as $directory) { $locators[] = $this->optimizedDirectorySourceLocatorRepository->getOrCreate($directory); } From 1baa29425d2e170af8ac53e4e579c34c25b640e4 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 12 Sep 2021 23:25:20 +0200 Subject: [PATCH 0269/1284] [BCB] Removed bootstrap --- conf/config.neon | 2 -- phpstan-baseline.neon | 2 +- src/Command/CommandHelper.php | 13 ------------- 3 files changed, 1 insertion(+), 16 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index e4b4c12bfa..2fb41f6752 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1,7 +1,6 @@ includes: - config.stubFiles.neon parameters: - bootstrap: null bootstrapFiles: - ../stubs/runtime/ReflectionUnionType.php - ../stubs/runtime/ReflectionAttribute.php @@ -177,7 +176,6 @@ extensions: parametersSchema: PHPStan\DependencyInjection\ParametersSchemaExtension parametersSchema: - bootstrap: schema(string(), nullable()) bootstrapFiles: listOf(string()) excludes_analyse: listOf(string()) excludePaths: schema(anyOf( diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index ce31795698..af1efadda4 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -12,7 +12,7 @@ parameters: - message: "#^Anonymous function has an unused use \\$container\\.$#" - count: 2 + count: 1 path: src/Command/CommandHelper.php - diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php index e6a74c9f02..53156cde5b 100644 --- a/src/Command/CommandHelper.php +++ b/src/Command/CommandHelper.php @@ -308,19 +308,6 @@ public static function begin( throw new \PHPStan\Command\InceptionNotSuccessfulException(); } - $bootstrapFile = $container->getParameter('bootstrap'); - if ($bootstrapFile !== null) { - if ($manageMemoryLimitFile) { - $errorOutput->writeLineFormatted('⚠️ You\'re using a deprecated config option bootstrap. ⚠️️'); - $errorOutput->writeLineFormatted(''); - $errorOutput->writeLineFormatted('This option has been replaced with bootstrapFiles which accepts a list of files'); - $errorOutput->writeLineFormatted('to execute before the analysis.'); - $errorOutput->writeLineFormatted(''); - } - - self::executeBootstrapFile($bootstrapFile, $container, $errorOutput, $debugEnabled); - } - foreach ($container->getParameter('bootstrapFiles') as $bootstrapFileFromArray) { self::executeBootstrapFile($bootstrapFileFromArray, $container, $errorOutput, $debugEnabled); } From 1c34d8dc855cb5f4cf5c8b033b7155eeed0e277b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 12 Sep 2021 23:29:43 +0200 Subject: [PATCH 0270/1284] [BCB] Removed MissingClosureNativeReturnTypehintRule, no longer needed thanks to type inference --- composer.lock | 8 +- conf/config.level0.neon | 11 -- conf/config.neon | 2 - ...MissingClosureNativeReturnTypehintRule.php | 137 ------------------ .../Analyser/NodeScopeResolverTest.php | 1 + ...missing-closure-native-return-typehint.php | 55 +++++++ ...ingClosureNativeReturnTypehintRuleTest.php | 77 ---------- ...missing-closure-native-return-typehint.php | 55 ------- 8 files changed, 60 insertions(+), 286 deletions(-) delete mode 100644 src/Rules/Missing/MissingClosureNativeReturnTypehintRule.php create mode 100644 tests/PHPStan/Analyser/data/missing-closure-native-return-typehint.php delete mode 100644 tests/PHPStan/Rules/Missing/MissingClosureNativeReturnTypehintRuleTest.php delete mode 100644 tests/PHPStan/Rules/Missing/data/missing-closure-native-return-typehint.php diff --git a/composer.lock b/composer.lock index 32cdd037ce..6136375f7a 100644 --- a/composer.lock +++ b/composer.lock @@ -4473,12 +4473,12 @@ "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "6a7b2714e4e05d9db2cacad9ec2c245d63565607" + "reference": "80d247b087dd6b046bfc808f8a49dbd6277ec19c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/6a7b2714e4e05d9db2cacad9ec2c245d63565607", - "reference": "6a7b2714e4e05d9db2cacad9ec2c245d63565607", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/80d247b087dd6b046bfc808f8a49dbd6277ec19c", + "reference": "80d247b087dd6b046bfc808f8a49dbd6277ec19c", "shasum": "" }, "require": { @@ -4516,7 +4516,7 @@ "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", "source": "https://github.com/phpstan/phpstan-strict-rules/tree/master" }, - "time": "2021-09-12T20:26:32+00:00" + "time": "2021-09-12T21:41:14+00:00" }, { "name": "phpunit/php-code-coverage", diff --git a/conf/config.level0.neon b/conf/config.level0.neon index 6f1bbf757e..b76b57f0e0 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -1,6 +1,5 @@ parameters: customRulesetUsed: false - missingClosureNativeReturnCheckObjectTypehint: false conditionalTags: PHPStan\Rules\Api\ApiInstantiationRule: @@ -23,8 +22,6 @@ conditionalTags: phpstan.rules.rule: %featureToggles.classConstants% PHPStan\Rules\Functions\ClosureUsesThisRule: phpstan.rules.rule: %featureToggles.closureUsesThis% - PHPStan\Rules\Missing\MissingClosureNativeReturnTypehintRule: - phpstan.rules.rule: %checkMissingClosureNativeReturnTypehintRule% PHPStan\Rules\Whitespace\FileWhitespaceRule: phpstan.rules.rule: %featureToggles.fileWhitespace% PHPStan\Rules\Properties\UninitializedPropertyRule: @@ -32,9 +29,6 @@ conditionalTags: PHPStan\Rules\Properties\OverridingPropertyRule: phpstan.rules.rule: %featureToggles.overridingProperty% -parametersSchema: - missingClosureNativeReturnCheckObjectTypehint: bool() - rules: - PHPStan\Rules\Arrays\DuplicateKeysInLiteralArraysRule - PHPStan\Rules\Arrays\EmptyArrayItemRule @@ -162,11 +156,6 @@ services: tags: - phpstan.rules.rule - - - class: PHPStan\Rules\Missing\MissingClosureNativeReturnTypehintRule - arguments: - checkObjectTypehint: %missingClosureNativeReturnCheckObjectTypehint% - - class: PHPStan\Rules\Missing\MissingReturnRule arguments: diff --git a/conf/config.neon b/conf/config.neon index 2fb41f6752..3839a182a2 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -73,7 +73,6 @@ parameters: checkPhpDocMissingReturn: false checkPhpDocMethodSignatures: false checkExtraArguments: false - checkMissingClosureNativeReturnTypehintRule: false checkMissingTypehints: false checkTooWideReturnTypesInProtectedAndPublicMethods: false checkUninitializedProperties: false @@ -255,7 +254,6 @@ parametersSchema: checkPhpDocMissingReturn: bool() checkPhpDocMethodSignatures: bool() checkExtraArguments: bool() - checkMissingClosureNativeReturnTypehintRule: bool() checkMissingTypehints: bool() checkTooWideReturnTypesInProtectedAndPublicMethods: bool() checkUninitializedProperties: bool() diff --git a/src/Rules/Missing/MissingClosureNativeReturnTypehintRule.php b/src/Rules/Missing/MissingClosureNativeReturnTypehintRule.php deleted file mode 100644 index c51602e500..0000000000 --- a/src/Rules/Missing/MissingClosureNativeReturnTypehintRule.php +++ /dev/null @@ -1,137 +0,0 @@ - - */ -class MissingClosureNativeReturnTypehintRule implements Rule -{ - - private bool $checkObjectTypehint; - - public function __construct(bool $checkObjectTypehint) - { - $this->checkObjectTypehint = $checkObjectTypehint; - } - - public function getNodeType(): string - { - return ClosureReturnStatementsNode::class; - } - - public function processNode(Node $node, Scope $scope): array - { - $closure = $node->getClosureExpr(); - if ($closure->returnType !== null) { - return []; - } - - $messagePattern = 'Anonymous function should have native return typehint "%s".'; - $statementResult = $node->getStatementResult(); - if ($statementResult->hasYield()) { - return [ - RuleErrorBuilder::message(sprintf($messagePattern, 'Generator'))->build(), - ]; - } - - $returnStatements = $node->getReturnStatements(); - if (count($returnStatements) === 0) { - return [ - RuleErrorBuilder::message(sprintf($messagePattern, 'void'))->build(), - ]; - } - - $returnTypes = []; - $voidReturnNodes = []; - $hasNull = false; - foreach ($returnStatements as $returnStatement) { - $returnNode = $returnStatement->getReturnNode(); - if ($returnNode->expr === null) { - $voidReturnNodes[] = $returnNode; - $hasNull = true; - continue; - } - - $returnTypes[] = $returnStatement->getScope()->getType($returnNode->expr); - } - - if (count($returnTypes) === 0) { - return [ - RuleErrorBuilder::message(sprintf($messagePattern, 'void'))->build(), - ]; - } - - $messages = []; - foreach ($voidReturnNodes as $voidReturnStatement) { - $messages[] = RuleErrorBuilder::message('Mixing returning values with empty return statements - return null should be used here.') - ->line($voidReturnStatement->getLine()) - ->build(); - } - - $returnType = TypeCombinator::union(...$returnTypes); - if ( - $returnType instanceof MixedType - || $returnType instanceof NeverType - || $returnType instanceof IntersectionType - || $returnType instanceof NullType - ) { - return $messages; - } - - if (TypeCombinator::containsNull($returnType)) { - $hasNull = true; - $returnType = TypeCombinator::removeNull($returnType); - } - - if ( - $returnType instanceof UnionType - || $returnType instanceof ResourceType - ) { - return $messages; - } - - if (!$statementResult->isAlwaysTerminating()) { - $messages[] = RuleErrorBuilder::message('Anonymous function sometimes return something but return statement at the end is missing.')->build(); - return $messages; - } - - $returnType = TypeUtils::generalizeType($returnType, GeneralizePrecision::lessSpecific()); - $description = $returnType->describe(VerbosityLevel::typeOnly()); - if ($returnType->isArray()->yes()) { - $description = 'array'; - } - if ($hasNull) { - $description = '?' . $description; - } - - if ( - !$this->checkObjectTypehint - && $returnType instanceof ObjectWithoutClassType - ) { - return $messages; - } - - $messages[] = RuleErrorBuilder::message(sprintf($messagePattern, $description))->build(); - - return $messages; - } - -} diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index f33db67112..2ab6cf53b7 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -504,6 +504,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5615.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/array_map_multiple.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/range-numeric-string.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/missing-closure-native-return-typehint.php'); } /** diff --git a/tests/PHPStan/Analyser/data/missing-closure-native-return-typehint.php b/tests/PHPStan/Analyser/data/missing-closure-native-return-typehint.php new file mode 100644 index 0000000000..43b55bae18 --- /dev/null +++ b/tests/PHPStan/Analyser/data/missing-closure-native-return-typehint.php @@ -0,0 +1,55 @@ +', (function (bool $bool) { + if ($bool) { + return; + } else { + yield 1; + } + })()); + \PHPStan\Testing\assertType('1|null', (function (bool $bool) { + if ($bool) { + return; + } else { + return 1; + } + })()); + \PHPStan\Testing\assertType('1', (function (): int { + return 1; + })()); + \PHPStan\Testing\assertType('1|null', (function (bool $bool) { + if ($bool) { + return null; + } else { + return 1; + } + })()); + \PHPStan\Testing\assertType('1', (function (bool $bool) { + if ($bool) { + return 1; + } + })()); + + \PHPStan\Testing\assertType('array(\'foo\' => \'bar\')', (function () { + $array = [ + 'foo' => 'bar', + ]; + + return $array; + })()); + } + +} diff --git a/tests/PHPStan/Rules/Missing/MissingClosureNativeReturnTypehintRuleTest.php b/tests/PHPStan/Rules/Missing/MissingClosureNativeReturnTypehintRuleTest.php deleted file mode 100644 index 83ac8d74ae..0000000000 --- a/tests/PHPStan/Rules/Missing/MissingClosureNativeReturnTypehintRuleTest.php +++ /dev/null @@ -1,77 +0,0 @@ - - */ -class MissingClosureNativeReturnTypehintRuleTest extends RuleTestCase -{ - - protected function getRule(): Rule - { - return new MissingClosureNativeReturnTypehintRule(true); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/missing-closure-native-return-typehint.php'], [ - [ - 'Anonymous function should have native return typehint "void".', - 10, - ], - [ - 'Anonymous function should have native return typehint "void".', - 13, - ], - [ - 'Anonymous function should have native return typehint "Generator".', - 16, - ], - [ - 'Mixing returning values with empty return statements - return null should be used here.', - 25, - ], - [ - 'Anonymous function should have native return typehint "?int".', - 23, - ], - [ - 'Anonymous function should have native return typehint "?int".', - 33, - ], - [ - 'Anonymous function sometimes return something but return statement at the end is missing.', - 40, - ], - [ - 'Anonymous function should have native return typehint "array".', - 46, - ], - ]); - } - - public function testBug2682(): void - { - $this->analyse([__DIR__ . '/data/bug-2682.php'], [ - [ - 'Anonymous function should have native return typehint "void".', - 9, - ], - ]); - } - - public function testBug5164(): void - { - $this->analyse([__DIR__ . '/data/bug-5164.php'], [ - [ - 'Anonymous function should have native return typehint "Closure".', - 9, - ], - ]); - } - -} diff --git a/tests/PHPStan/Rules/Missing/data/missing-closure-native-return-typehint.php b/tests/PHPStan/Rules/Missing/data/missing-closure-native-return-typehint.php deleted file mode 100644 index 1eb7d37d14..0000000000 --- a/tests/PHPStan/Rules/Missing/data/missing-closure-native-return-typehint.php +++ /dev/null @@ -1,55 +0,0 @@ - 'bar', - ]; - - return $array; - }; - } - -} From 003ab1aea558085cd27636a0b95a4c9f0e062e9a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 12 Sep 2021 23:38:50 +0200 Subject: [PATCH 0271/1284] [BCB] Rename rules with typos --- conf/config.level4.neon | 6 +++--- ...hp => CallToFunctionStatementWithoutSideEffectsRule.php} | 2 +- ....php => CallToMethodStatementWithoutSideEffectsRule.php} | 2 +- ...> CallToStaticMethodStatementWithoutSideEffectsRule.php} | 2 +- ...> CallToFunctionStatementWithoutSideEffectsRuleTest.php} | 6 +++--- ... => CallToMethodStatementWithoutSideEffectsRuleTest.php} | 6 +++--- ...llToStaticMethodStatementWithoutSideEffectsRuleTest.php} | 6 +++--- 7 files changed, 15 insertions(+), 15 deletions(-) rename src/Rules/Functions/{CallToFunctionStamentWithoutSideEffectsRule.php => CallToFunctionStatementWithoutSideEffectsRule.php} (96%) rename src/Rules/Methods/{CallToMethodStamentWithoutSideEffectsRule.php => CallToMethodStatementWithoutSideEffectsRule.php} (97%) rename src/Rules/Methods/{CallToStaticMethodStamentWithoutSideEffectsRule.php => CallToStaticMethodStatementWithoutSideEffectsRule.php} (97%) rename tests/PHPStan/Rules/Functions/{CallToFunctionStamentWithoutSideEffectsRuleTest.php => CallToFunctionStatementWithoutSideEffectsRuleTest.php} (83%) rename tests/PHPStan/Rules/Methods/{CallToMethodStamentWithoutSideEffectsRuleTest.php => CallToMethodStatementWithoutSideEffectsRuleTest.php} (86%) rename tests/PHPStan/Rules/Methods/{CallToStaticMethodStamentWithoutSideEffectsRuleTest.php => CallToStaticMethodStatementWithoutSideEffectsRuleTest.php} (87%) diff --git a/conf/config.level4.neon b/conf/config.level4.neon index f0b24f7bab..ef6b167142 100644 --- a/conf/config.level4.neon +++ b/conf/config.level4.neon @@ -8,10 +8,10 @@ rules: - PHPStan\Rules\DeadCode\UnreachableStatementRule - PHPStan\Rules\Exceptions\CatchWithUnthrownExceptionRule - PHPStan\Rules\Exceptions\OverwrittenExitPointByFinallyRule - - PHPStan\Rules\Functions\CallToFunctionStamentWithoutSideEffectsRule + - PHPStan\Rules\Functions\CallToFunctionStatementWithoutSideEffectsRule - PHPStan\Rules\Methods\CallToConstructorStatementWithoutSideEffectsRule - - PHPStan\Rules\Methods\CallToMethodStamentWithoutSideEffectsRule - - PHPStan\Rules\Methods\CallToStaticMethodStamentWithoutSideEffectsRule + - PHPStan\Rules\Methods\CallToMethodStatementWithoutSideEffectsRule + - PHPStan\Rules\Methods\CallToStaticMethodStatementWithoutSideEffectsRule - PHPStan\Rules\Methods\NullsafeMethodCallRule - PHPStan\Rules\Properties\NullsafePropertyFetchRule - PHPStan\Rules\TooWideTypehints\TooWideArrowFunctionReturnTypehintRule diff --git a/src/Rules/Functions/CallToFunctionStamentWithoutSideEffectsRule.php b/src/Rules/Functions/CallToFunctionStatementWithoutSideEffectsRule.php similarity index 96% rename from src/Rules/Functions/CallToFunctionStamentWithoutSideEffectsRule.php rename to src/Rules/Functions/CallToFunctionStatementWithoutSideEffectsRule.php index b38bc1feaa..5c4bc21420 100644 --- a/src/Rules/Functions/CallToFunctionStamentWithoutSideEffectsRule.php +++ b/src/Rules/Functions/CallToFunctionStatementWithoutSideEffectsRule.php @@ -13,7 +13,7 @@ /** * @implements \PHPStan\Rules\Rule<\PhpParser\Node\Stmt\Expression> */ -class CallToFunctionStamentWithoutSideEffectsRule implements Rule +class CallToFunctionStatementWithoutSideEffectsRule implements Rule { private \PHPStan\Reflection\ReflectionProvider $reflectionProvider; diff --git a/src/Rules/Methods/CallToMethodStamentWithoutSideEffectsRule.php b/src/Rules/Methods/CallToMethodStatementWithoutSideEffectsRule.php similarity index 97% rename from src/Rules/Methods/CallToMethodStamentWithoutSideEffectsRule.php rename to src/Rules/Methods/CallToMethodStatementWithoutSideEffectsRule.php index 7592c9bd18..0ade6b4c03 100644 --- a/src/Rules/Methods/CallToMethodStamentWithoutSideEffectsRule.php +++ b/src/Rules/Methods/CallToMethodStatementWithoutSideEffectsRule.php @@ -15,7 +15,7 @@ /** * @implements \PHPStan\Rules\Rule<\PhpParser\Node\Stmt\Expression> */ -class CallToMethodStamentWithoutSideEffectsRule implements Rule +class CallToMethodStatementWithoutSideEffectsRule implements Rule { private \PHPStan\Rules\RuleLevelHelper $ruleLevelHelper; diff --git a/src/Rules/Methods/CallToStaticMethodStamentWithoutSideEffectsRule.php b/src/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRule.php similarity index 97% rename from src/Rules/Methods/CallToStaticMethodStamentWithoutSideEffectsRule.php rename to src/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRule.php index 85d8a47a3b..70d25300bb 100644 --- a/src/Rules/Methods/CallToStaticMethodStamentWithoutSideEffectsRule.php +++ b/src/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRule.php @@ -17,7 +17,7 @@ /** * @implements \PHPStan\Rules\Rule<\PhpParser\Node\Stmt\Expression> */ -class CallToStaticMethodStamentWithoutSideEffectsRule implements Rule +class CallToStaticMethodStatementWithoutSideEffectsRule implements Rule { private \PHPStan\Rules\RuleLevelHelper $ruleLevelHelper; diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionStamentWithoutSideEffectsRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionStatementWithoutSideEffectsRuleTest.php similarity index 83% rename from tests/PHPStan/Rules/Functions/CallToFunctionStamentWithoutSideEffectsRuleTest.php rename to tests/PHPStan/Rules/Functions/CallToFunctionStatementWithoutSideEffectsRuleTest.php index 202ab84ec6..f47f1f4461 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionStamentWithoutSideEffectsRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionStatementWithoutSideEffectsRuleTest.php @@ -6,14 +6,14 @@ use PHPStan\Testing\RuleTestCase; /** - * @extends \PHPStan\Testing\RuleTestCase + * @extends \PHPStan\Testing\RuleTestCase */ -class CallToFunctionStamentWithoutSideEffectsRuleTest extends RuleTestCase +class CallToFunctionStatementWithoutSideEffectsRuleTest extends RuleTestCase { protected function getRule(): Rule { - return new CallToFunctionStamentWithoutSideEffectsRule($this->createReflectionProvider()); + return new CallToFunctionStatementWithoutSideEffectsRule($this->createReflectionProvider()); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Methods/CallToMethodStamentWithoutSideEffectsRuleTest.php b/tests/PHPStan/Rules/Methods/CallToMethodStatementWithoutSideEffectsRuleTest.php similarity index 86% rename from tests/PHPStan/Rules/Methods/CallToMethodStamentWithoutSideEffectsRuleTest.php rename to tests/PHPStan/Rules/Methods/CallToMethodStatementWithoutSideEffectsRuleTest.php index 01ac6f193d..a2a63eb692 100644 --- a/tests/PHPStan/Rules/Methods/CallToMethodStamentWithoutSideEffectsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallToMethodStatementWithoutSideEffectsRuleTest.php @@ -7,14 +7,14 @@ use PHPStan\Testing\RuleTestCase; /** - * @extends \PHPStan\Testing\RuleTestCase + * @extends \PHPStan\Testing\RuleTestCase */ -class CallToMethodStamentWithoutSideEffectsRuleTest extends RuleTestCase +class CallToMethodStatementWithoutSideEffectsRuleTest extends RuleTestCase { protected function getRule(): Rule { - return new CallToMethodStamentWithoutSideEffectsRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false)); + return new CallToMethodStatementWithoutSideEffectsRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false)); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Methods/CallToStaticMethodStamentWithoutSideEffectsRuleTest.php b/tests/PHPStan/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRuleTest.php similarity index 87% rename from tests/PHPStan/Rules/Methods/CallToStaticMethodStamentWithoutSideEffectsRuleTest.php rename to tests/PHPStan/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRuleTest.php index ec5ade3b82..08832088ee 100644 --- a/tests/PHPStan/Rules/Methods/CallToStaticMethodStamentWithoutSideEffectsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRuleTest.php @@ -7,15 +7,15 @@ use PHPStan\Testing\RuleTestCase; /** - * @extends \PHPStan\Testing\RuleTestCase + * @extends \PHPStan\Testing\RuleTestCase */ -class CallToStaticMethodStamentWithoutSideEffectsRuleTest extends RuleTestCase +class CallToStaticMethodStatementWithoutSideEffectsRuleTest extends RuleTestCase { protected function getRule(): Rule { $broker = $this->createReflectionProvider(); - return new CallToStaticMethodStamentWithoutSideEffectsRule( + return new CallToStaticMethodStatementWithoutSideEffectsRule( new RuleLevelHelper($broker, true, false, true, false), $broker ); From 71e9e1d846493b6a50f0c7a817b7d7c76a57b5a2 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 12 Sep 2021 23:50:55 +0200 Subject: [PATCH 0272/1284] Update PHPStan extensions --- composer.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.lock b/composer.lock index 6136375f7a..79060042fb 100644 --- a/composer.lock +++ b/composer.lock @@ -4418,12 +4418,12 @@ "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-phpunit.git", - "reference": "6c0e48e98f082e94be11bca4db64489194c66b06" + "reference": "a316f5d59f620ef06f888f6fc95fead9d38e0377" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/6c0e48e98f082e94be11bca4db64489194c66b06", - "reference": "6c0e48e98f082e94be11bca4db64489194c66b06", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/a316f5d59f620ef06f888f6fc95fead9d38e0377", + "reference": "a316f5d59f620ef06f888f6fc95fead9d38e0377", "shasum": "" }, "require": { @@ -4465,7 +4465,7 @@ "issues": "https://github.com/phpstan/phpstan-phpunit/issues", "source": "https://github.com/phpstan/phpstan-phpunit/tree/master" }, - "time": "2021-09-12T20:25:39+00:00" + "time": "2021-09-12T21:47:21+00:00" }, { "name": "phpstan/phpstan-strict-rules", @@ -4473,12 +4473,12 @@ "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "80d247b087dd6b046bfc808f8a49dbd6277ec19c" + "reference": "598568270074e0233fe01dc81fe7e88913f2689d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/80d247b087dd6b046bfc808f8a49dbd6277ec19c", - "reference": "80d247b087dd6b046bfc808f8a49dbd6277ec19c", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/598568270074e0233fe01dc81fe7e88913f2689d", + "reference": "598568270074e0233fe01dc81fe7e88913f2689d", "shasum": "" }, "require": { @@ -4516,7 +4516,7 @@ "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", "source": "https://github.com/phpstan/phpstan-strict-rules/tree/master" }, - "time": "2021-09-12T21:41:14+00:00" + "time": "2021-09-12T21:46:22+00:00" }, { "name": "phpunit/php-code-coverage", From 0b9288832073210d52a3915d9fdc1d7991a1b86b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 12 Sep 2021 23:53:25 +0200 Subject: [PATCH 0273/1284] Require generic RecursiveIterator, RecursiveArrayIterator, WeakMap --- conf/config.neon | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index 3839a182a2..4ad60e9b8b 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -34,10 +34,7 @@ parameters: wrongVarUsage: false arrayDestructuring: false objectFromNewClass: false - skipCheckGenericClasses: - - RecursiveIterator - - RecursiveArrayIterator - - WeakMap + skipCheckGenericClasses: [] rememberFunctionValues: false apiRules: false deepInspectTypes: false From 84b09349dc04fa30097900b5deb8e316b1657e4b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 13 Sep 2021 09:48:14 +0200 Subject: [PATCH 0274/1284] MethodCall with Expr name adds an implicit throw point --- src/Analyser/NodeScopeResolver.php | 1 + .../PHPStan/Rules/Exceptions/data/unthrown-exception.php | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index cd565f360b..a1c6971924 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -1961,6 +1961,7 @@ function (MutatingScope $scope) use ($expr, $nodeCallback, $context): Expression if ($expr->name instanceof Expr) { $methodNameResult = $this->processExprNode($expr->name, $scope, $nodeCallback, $context->enterDeep()); $throwPoints = array_merge($throwPoints, $methodNameResult->getThrowPoints()); + $throwPoints[] = ThrowPoint::createImplicit($scope, $expr); $scope = $methodNameResult->getScope(); } else { $calledOnType = $scope->getType($expr->var); diff --git a/tests/PHPStan/Rules/Exceptions/data/unthrown-exception.php b/tests/PHPStan/Rules/Exceptions/data/unthrown-exception.php index c1e0a88a2d..78236201ac 100644 --- a/tests/PHPStan/Rules/Exceptions/data/unthrown-exception.php +++ b/tests/PHPStan/Rules/Exceptions/data/unthrown-exception.php @@ -534,4 +534,13 @@ public function doFoo(\Exception $e) } } + public function doBar(string $s) + { + try { + $this->{'doFoo' . $s}(); + } catch (\InvalidArgumentException $e) { + + } + } + } From 96015f30f6bec4ed3654b56f66bc2a4b0efd744e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 13 Sep 2021 09:52:21 +0200 Subject: [PATCH 0275/1284] Fix CommandHelper tests --- tests/PHPStan/Command/CommandHelperTest.php | 24 +++++++------------ .../Command/relative-paths/nested/nested.neon | 2 +- .../PHPStan/Command/relative-paths/root.neon | 11 +-------- 3 files changed, 10 insertions(+), 27 deletions(-) diff --git a/tests/PHPStan/Command/CommandHelperTest.php b/tests/PHPStan/Command/CommandHelperTest.php index 6ce191d7ce..191b70fbfc 100644 --- a/tests/PHPStan/Command/CommandHelperTest.php +++ b/tests/PHPStan/Command/CommandHelperTest.php @@ -166,23 +166,12 @@ public function dataParameters(): array [ __DIR__ . '/relative-paths/root.neon', [ - 'bootstrap' => __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'here.php', 'bootstrapFiles' => [ realpath(__DIR__ . '/../../../stubs/runtime/ReflectionUnionType.php'), realpath(__DIR__ . '/../../../stubs/runtime/ReflectionAttribute.php'), realpath(__DIR__ . '/../../../stubs/runtime/Attribute.php'), __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'here.php', ], - 'autoload_files' => [ - __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'here.php', - __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'test' . DIRECTORY_SEPARATOR . 'there.php', - __DIR__ . DIRECTORY_SEPARATOR . 'up.php', - ], - 'autoload_directories' => [ - __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'src', - __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths', - realpath(__DIR__ . '/../../../') . '/conf', - ], 'scanFiles' => [ __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'here.php', __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'test' . DIRECTORY_SEPARATOR . 'there.php', @@ -197,17 +186,20 @@ public function dataParameters(): array __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'src', ], 'memoryLimitFile' => __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . '.memory_limit', - 'excludes_analyse' => [ - __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'src', - __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . '*' . DIRECTORY_SEPARATOR . 'data', - '*/src/*/data', + 'excludePaths' => [ + 'analyseAndScan' => [ + __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'src', + __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . '*' . DIRECTORY_SEPARATOR . 'data', + '*/src/*/data', + ], + 'analyse' => [], ], ], ], [ __DIR__ . '/relative-paths/nested/nested.neon', [ - 'autoload_files' => [ + 'scanFiles' => [ __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'nested' . DIRECTORY_SEPARATOR . 'here.php', __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'nested' . DIRECTORY_SEPARATOR . 'test' . DIRECTORY_SEPARATOR . 'there.php', __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'up.php', diff --git a/tests/PHPStan/Command/relative-paths/nested/nested.neon b/tests/PHPStan/Command/relative-paths/nested/nested.neon index c18dd719f7..6e097edf21 100644 --- a/tests/PHPStan/Command/relative-paths/nested/nested.neon +++ b/tests/PHPStan/Command/relative-paths/nested/nested.neon @@ -1,5 +1,5 @@ parameters: - autoload_files: + scanFiles: - here.php - test/there.php - ../up.php diff --git a/tests/PHPStan/Command/relative-paths/root.neon b/tests/PHPStan/Command/relative-paths/root.neon index 138d7e341c..834bcfd001 100644 --- a/tests/PHPStan/Command/relative-paths/root.neon +++ b/tests/PHPStan/Command/relative-paths/root.neon @@ -1,15 +1,6 @@ parameters: - bootstrap: here.php bootstrapFiles: - here.php - autoload_files: - - here.php - - test/there.php - - ../up.php - autoload_directories: - - src - - . - - %rootDir%/conf scanFiles: - here.php - test/there.php @@ -21,7 +12,7 @@ parameters: paths: - src memoryLimitFile: .memory_limit - excludes_analyse: + excludePaths: - src - src/*/data - */src/*/data From 5ffe1b4e532bfa52b0f8328b23790ccf33e6709c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 13 Sep 2021 09:52:43 +0200 Subject: [PATCH 0276/1284] Fix --- src/Analyser/NodeScopeResolver.php | 5 ++--- tests/PHPStan/Analyser/traitsCachingIssue/phpstan.neon | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index a1c6971924..7dec2afc94 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -1961,7 +1961,6 @@ function (MutatingScope $scope) use ($expr, $nodeCallback, $context): Expression if ($expr->name instanceof Expr) { $methodNameResult = $this->processExprNode($expr->name, $scope, $nodeCallback, $context->enterDeep()); $throwPoints = array_merge($throwPoints, $methodNameResult->getThrowPoints()); - $throwPoints[] = ThrowPoint::createImplicit($scope, $expr); $scope = $methodNameResult->getScope(); } else { $calledOnType = $scope->getType($expr->var); @@ -1977,8 +1976,6 @@ function (MutatingScope $scope) use ($expr, $nodeCallback, $context): Expression if ($methodThrowPoint !== null) { $throwPoints[] = $methodThrowPoint; } - } else { - $throwPoints[] = ThrowPoint::createImplicit($scope, $expr); } } $result = $this->processArgs($methodReflection, $parametersAcceptor, $expr->args, $scope, $nodeCallback, $context); @@ -1991,6 +1988,8 @@ function (MutatingScope $scope) use ($expr, $nodeCallback, $context): Expression $scope = $scope->invalidateExpression($arg->value, true); } } + } else { + $throwPoints[] = ThrowPoint::createImplicit($scope, $expr); } $hasYield = $hasYield || $result->hasYield(); $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); diff --git a/tests/PHPStan/Analyser/traitsCachingIssue/phpstan.neon b/tests/PHPStan/Analyser/traitsCachingIssue/phpstan.neon index 799d286c7a..d20f1be41c 100644 --- a/tests/PHPStan/Analyser/traitsCachingIssue/phpstan.neon +++ b/tests/PHPStan/Analyser/traitsCachingIssue/phpstan.neon @@ -1,4 +1,4 @@ parameters: tmpDir: tmp - autoload_directories: + scanDirectories: - data From 44ba33320df351d4d07123287ca1fbfb228ac19a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 13 Sep 2021 10:33:18 +0200 Subject: [PATCH 0277/1284] Fix --- .../BaselineNeonErrorFormatterIntegrationTest.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/PHPStan/Command/ErrorFormatter/BaselineNeonErrorFormatterIntegrationTest.php b/tests/PHPStan/Command/ErrorFormatter/BaselineNeonErrorFormatterIntegrationTest.php index bf62cc8e5f..e74ec77c8a 100644 --- a/tests/PHPStan/Command/ErrorFormatter/BaselineNeonErrorFormatterIntegrationTest.php +++ b/tests/PHPStan/Command/ErrorFormatter/BaselineNeonErrorFormatterIntegrationTest.php @@ -5,7 +5,6 @@ use Nette\Utils\Json; use PHPUnit\Framework\TestCase; use function chdir; -use function file_put_contents; use function getcwd; /** @@ -24,9 +23,8 @@ public function testErrorWithTrait(): void public function testGenerateBaselineAndRunAgainWithIt(): void { - $output = $this->runPhpStan(__DIR__ . '/data/', __DIR__ . '/empty.neon', 'baselineNeon'); $baselineFile = __DIR__ . '/../../../../baseline.neon'; - file_put_contents($baselineFile, $output); + $output = $this->runPhpStan(__DIR__ . '/data/', __DIR__ . '/empty.neon', 'json', $baselineFile); $output = $this->runPhpStan(__DIR__ . '/data/', $baselineFile); @unlink($baselineFile); @@ -54,7 +52,8 @@ public function testRunUnixFileWithWindowsBaseline(): void private function runPhpStan( string $analysedPath, ?string $configFile, - string $errorFormatter = 'json' + string $errorFormatter = 'json', + ?string $baselineFile = null ): string { $originalDir = getcwd(); @@ -67,7 +66,7 @@ private function runPhpStan( throw new \PHPStan\ShouldNotHappenException('Could not clear result cache.'); } - exec(sprintf('%s %s analyse --no-progress --error-format=%s --level=7 %s %s', escapeshellarg(PHP_BINARY), 'bin/phpstan', $errorFormatter, $configFile !== null ? '--configuration ' . escapeshellarg($configFile) : '', escapeshellarg($analysedPath)), $outputLines); + exec(sprintf('%s %s analyse --no-progress --error-format=%s --level=7 %s %s%s', escapeshellarg(PHP_BINARY), 'bin/phpstan', $errorFormatter, $configFile !== null ? '--configuration ' . escapeshellarg($configFile) : '', escapeshellarg($analysedPath), $baselineFile !== null ? ' --generate-baseline ' . escapeshellarg($baselineFile) : ''), $outputLines); chdir($originalDir); return implode("\n", $outputLines); From 755bd66a210e460b6cce1cb19386e63226783e49 Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Fri, 20 Aug 2021 16:23:40 +0200 Subject: [PATCH 0278/1284] [BCB] Renamed TestCase to PHPStanTestCase --- compiler/build/scoper.inc.php | 4 ++-- phpcs.xml | 2 +- phpstan-baseline.neon | 2 +- src/Testing/ErrorFormatterTestCase.php | 2 +- src/Testing/{TestCase.php => PHPStanTestCase.php} | 2 +- src/Testing/RuleTestCase.php | 2 +- src/Testing/TypeInferenceTestCase.php | 2 +- tests/PHPStan/Analyser/AnalyserIntegrationTest.php | 2 +- tests/PHPStan/Analyser/AnalyserTest.php | 2 +- tests/PHPStan/Analyser/AnalyserTraitsIntegrationTest.php | 2 +- tests/PHPStan/Analyser/ErrorTest.php | 2 +- tests/PHPStan/Analyser/ScopeTest.php | 4 ++-- tests/PHPStan/Analyser/StatementResultTest.php | 2 +- tests/PHPStan/Analyser/TypeSpecifierContextTest.php | 2 +- tests/PHPStan/Analyser/TypeSpecifierTest.php | 2 +- .../traitsCachingIssue/TraitsCachingIssueIntegrationTest.php | 4 ++-- tests/PHPStan/Broker/BrokerTest.php | 2 +- tests/PHPStan/Command/AnalyseApplicationIntegrationTest.php | 2 +- tests/PHPStan/Command/AnalyseCommandTest.php | 2 +- tests/PHPStan/Command/AnalysisResultTest.php | 4 ++-- tests/PHPStan/Command/IgnoredRegexValidatorTest.php | 4 ++-- tests/PHPStan/File/FileExcluderTest.php | 2 +- tests/PHPStan/File/FileHelperTest.php | 2 +- tests/PHPStan/Generics/TemplateTypeFactoryTest.php | 2 +- tests/PHPStan/Parser/CachedParserTest.php | 4 ++-- .../AnnotationsMethodsClassReflectionExtensionTest.php | 2 +- .../AnnotationsPropertiesClassReflectionExtensionTest.php | 2 +- .../Reflection/Annotations/DeprecatedAnnotationsTest.php | 2 +- tests/PHPStan/Reflection/Annotations/FinalAnnotationsTest.php | 2 +- .../Reflection/Annotations/InternalAnnotationsTest.php | 2 +- .../PHPStan/Reflection/Annotations/ThrowsAnnotationsTest.php | 2 +- .../SourceLocator/AutoloadSourceLocatorTest.php | 4 ++-- .../SourceLocator/OptimizedDirectorySourceLocatorTest.php | 4 ++-- .../SourceLocator/OptimizedSingleFileSourceLocatorTest.php | 4 ++-- tests/PHPStan/Reflection/ClassReflectionTest.php | 2 +- .../Reflection/GenericParametersAcceptorResolverTest.php | 2 +- tests/PHPStan/Reflection/MixedTypeTest.php | 4 ++-- tests/PHPStan/Reflection/ParametersAcceptorSelectorTest.php | 2 +- .../Php/UniversalObjectCratesClassReflectionExtensionTest.php | 2 +- tests/PHPStan/Reflection/ReflectionProviderTest.php | 4 ++-- .../PHPStan/Reflection/SignatureMap/FunctionMetadataTest.php | 4 ++-- .../Reflection/SignatureMap/Php8SignatureMapProviderTest.php | 4 ++-- .../Reflection/SignatureMap/SignatureMapParserTest.php | 2 +- .../Reflection/Type/IntersectionTypeMethodReflectionTest.php | 2 +- .../PHPStan/Reflection/Type/UnionTypeMethodReflectionTest.php | 2 +- tests/PHPStan/Reflection/UnionTypesTest.php | 4 ++-- .../Rules/Exceptions/DefaultExceptionTypeResolverTest.php | 4 ++-- tests/PHPStan/Rules/RegistryTest.php | 2 +- tests/PHPStan/TrinaryLogicTest.php | 2 +- tests/PHPStan/Type/Accessory/HasMethodTypeTest.php | 2 +- tests/PHPStan/Type/Accessory/HasPropertyTypeTest.php | 2 +- tests/PHPStan/Type/ArrayTypeTest.php | 2 +- tests/PHPStan/Type/BooleanTypeTest.php | 2 +- tests/PHPStan/Type/CallableTypeTest.php | 2 +- tests/PHPStan/Type/ClassStringTypeTest.php | 4 ++-- tests/PHPStan/Type/ClosureTypeTest.php | 2 +- tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php | 2 +- tests/PHPStan/Type/Constant/ConstantFloatTypeTest.php | 2 +- tests/PHPStan/Type/Constant/ConstantIntegerTypeTest.php | 2 +- tests/PHPStan/Type/Constant/ConstantStringTypeTest.php | 4 ++-- tests/PHPStan/Type/FileTypeMapperTest.php | 2 +- tests/PHPStan/Type/FloatTypeTest.php | 2 +- tests/PHPStan/Type/Generic/GenericClassStringTypeTest.php | 2 +- tests/PHPStan/Type/Generic/GenericObjectTypeTest.php | 2 +- tests/PHPStan/Type/Generic/TemplateTypeHelperTest.php | 2 +- tests/PHPStan/Type/IntegerTypeTest.php | 2 +- tests/PHPStan/Type/IntersectionTypeTest.php | 2 +- tests/PHPStan/Type/IterableTypeTest.php | 2 +- tests/PHPStan/Type/MixedTypeTest.php | 2 +- tests/PHPStan/Type/ObjectTypeTest.php | 2 +- tests/PHPStan/Type/ObjectWithoutClassTypeTest.php | 2 +- tests/PHPStan/Type/StaticTypeTest.php | 2 +- tests/PHPStan/Type/StringTypeTest.php | 4 ++-- tests/PHPStan/Type/TemplateTypeTest.php | 2 +- tests/PHPStan/Type/TypeCombinatorTest.php | 2 +- tests/PHPStan/Type/UnionTypeTest.php | 2 +- tests/bootstrap-runtime-reflection.php | 2 +- tests/bootstrap-static-reflection.php | 4 ++-- 78 files changed, 97 insertions(+), 97 deletions(-) rename src/Testing/{TestCase.php => PHPStanTestCase.php} (99%) diff --git a/compiler/build/scoper.inc.php b/compiler/build/scoper.inc.php index 3c1509cebc..f097836c95 100644 --- a/compiler/build/scoper.inc.php +++ b/compiler/build/scoper.inc.php @@ -80,7 +80,7 @@ function (string $filePath, string $prefix, string $content): string { return $content; }, function (string $filePath, string $prefix, string $content): string { - if ($filePath !== 'src/Testing/TestCase.php') { + if ($filePath !== 'src/Testing/PHPStanTestCase.php') { return $content; } return str_replace(sprintf('\\%s\\PHPUnit\\Framework\\TestCase', $prefix), '\\PHPUnit\\Framework\\TestCase', $content); @@ -160,7 +160,7 @@ function (string $filePath, string $prefix, string $content): string { function (string $filePath, string $prefix, string $content): string { if (!in_array($filePath, [ 'src/Testing/TestCaseSourceLocatorFactory.php', - 'src/Testing/TestCase.php', + 'src/Testing/PHPStanTestCase.php', ], true)) { return $content; } diff --git a/phpcs.xml b/phpcs.xml index 03130b34b2..b3b5bb5651 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -52,7 +52,7 @@ src/Command/CommandHelper.php - src/Testing/TestCase.php + src/Testing/PHPStanTestCase.php tests diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index af1efadda4..f2cb4a97f4 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -243,7 +243,7 @@ parameters: - message: "#^Anonymous function has an unused use \\$container\\.$#" count: 1 - path: src/Testing/TestCase.php + path: src/Testing/PHPStanTestCase.php - message: "#^Unreachable statement \\- code above always terminates\\.$#" diff --git a/src/Testing/ErrorFormatterTestCase.php b/src/Testing/ErrorFormatterTestCase.php index 94cf71da56..8a3dbd8214 100644 --- a/src/Testing/ErrorFormatterTestCase.php +++ b/src/Testing/ErrorFormatterTestCase.php @@ -11,7 +11,7 @@ use Symfony\Component\Console\Input\StringInput; use Symfony\Component\Console\Output\StreamOutput; -abstract class ErrorFormatterTestCase extends \PHPStan\Testing\TestCase +abstract class ErrorFormatterTestCase extends \PHPStan\Testing\PHPStanTestCase { protected const DIRECTORY_PATH = '/data/folder/with space/and unicode 😃/project'; diff --git a/src/Testing/TestCase.php b/src/Testing/PHPStanTestCase.php similarity index 99% rename from src/Testing/TestCase.php rename to src/Testing/PHPStanTestCase.php index bac8ae96cf..22ab3fa362 100644 --- a/src/Testing/TestCase.php +++ b/src/Testing/PHPStanTestCase.php @@ -76,7 +76,7 @@ use PHPStan\Type\TypeAliasResolver; /** @api */ -abstract class TestCase extends \PHPUnit\Framework\TestCase +abstract class PHPStanTestCase extends \PHPUnit\Framework\TestCase { /** @var bool */ diff --git a/src/Testing/RuleTestCase.php b/src/Testing/RuleTestCase.php index 3d9f831cdf..3c10a0d4de 100644 --- a/src/Testing/RuleTestCase.php +++ b/src/Testing/RuleTestCase.php @@ -28,7 +28,7 @@ * @api * @template TRule of \PHPStan\Rules\Rule */ -abstract class RuleTestCase extends \PHPStan\Testing\TestCase +abstract class RuleTestCase extends \PHPStan\Testing\PHPStanTestCase { private ?\PHPStan\Analyser\Analyser $analyser = null; diff --git a/src/Testing/TypeInferenceTestCase.php b/src/Testing/TypeInferenceTestCase.php index 7095ad9773..8225650da6 100644 --- a/src/Testing/TypeInferenceTestCase.php +++ b/src/Testing/TypeInferenceTestCase.php @@ -28,7 +28,7 @@ use PHPStan\Type\VerbosityLevel; /** @api */ -abstract class TypeInferenceTestCase extends \PHPStan\Testing\TestCase +abstract class TypeInferenceTestCase extends \PHPStan\Testing\PHPStanTestCase { /** diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index d2eb33b39d..6ce746f5be 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -13,7 +13,7 @@ use const PHP_VERSION_ID; use function array_reverse; -class AnalyserIntegrationTest extends \PHPStan\Testing\TestCase +class AnalyserIntegrationTest extends \PHPStan\Testing\PHPStanTestCase { public function testUndefinedVariableFromAssignErrorHasLine(): void diff --git a/tests/PHPStan/Analyser/AnalyserTest.php b/tests/PHPStan/Analyser/AnalyserTest.php index b06373d234..e74fd612e2 100644 --- a/tests/PHPStan/Analyser/AnalyserTest.php +++ b/tests/PHPStan/Analyser/AnalyserTest.php @@ -23,7 +23,7 @@ use PHPStan\Rules\Registry; use PHPStan\Type\FileTypeMapper; -class AnalyserTest extends \PHPStan\Testing\TestCase +class AnalyserTest extends \PHPStan\Testing\PHPStanTestCase { public function testReturnErrorIfIgnoredMessagesDoesNotOccur(): void diff --git a/tests/PHPStan/Analyser/AnalyserTraitsIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserTraitsIntegrationTest.php index dad3508986..7c908f6742 100644 --- a/tests/PHPStan/Analyser/AnalyserTraitsIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserTraitsIntegrationTest.php @@ -4,7 +4,7 @@ use PHPStan\File\FileHelper; -class AnalyserTraitsIntegrationTest extends \PHPStan\Testing\TestCase +class AnalyserTraitsIntegrationTest extends \PHPStan\Testing\PHPStanTestCase { /** @var \PHPStan\File\FileHelper */ diff --git a/tests/PHPStan/Analyser/ErrorTest.php b/tests/PHPStan/Analyser/ErrorTest.php index d2347ebbfc..ec7647c2f3 100644 --- a/tests/PHPStan/Analyser/ErrorTest.php +++ b/tests/PHPStan/Analyser/ErrorTest.php @@ -2,7 +2,7 @@ namespace PHPStan\Analyser; -class ErrorTest extends \PHPStan\Testing\TestCase +class ErrorTest extends \PHPStan\Testing\PHPStanTestCase { public function testError(): void diff --git a/tests/PHPStan/Analyser/ScopeTest.php b/tests/PHPStan/Analyser/ScopeTest.php index fd63c699de..c6509673e0 100644 --- a/tests/PHPStan/Analyser/ScopeTest.php +++ b/tests/PHPStan/Analyser/ScopeTest.php @@ -4,7 +4,7 @@ use PhpParser\Node\Expr\ConstFetch; use PhpParser\Node\Name\FullyQualified; -use PHPStan\Testing\TestCase; +use PHPStan\Testing\PHPStanTestCase; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantIntegerType; @@ -14,7 +14,7 @@ use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; -class ScopeTest extends TestCase +class ScopeTest extends PHPStanTestCase { public function dataGeneralize(): array diff --git a/tests/PHPStan/Analyser/StatementResultTest.php b/tests/PHPStan/Analyser/StatementResultTest.php index 966db5f6ab..a0f71eccde 100644 --- a/tests/PHPStan/Analyser/StatementResultTest.php +++ b/tests/PHPStan/Analyser/StatementResultTest.php @@ -9,7 +9,7 @@ use PHPStan\Type\MixedType; use PHPStan\Type\StringType; -class StatementResultTest extends \PHPStan\Testing\TestCase +class StatementResultTest extends \PHPStan\Testing\PHPStanTestCase { public function dataIsAlwaysTerminating(): array diff --git a/tests/PHPStan/Analyser/TypeSpecifierContextTest.php b/tests/PHPStan/Analyser/TypeSpecifierContextTest.php index ac18184fd0..d7931f98e6 100644 --- a/tests/PHPStan/Analyser/TypeSpecifierContextTest.php +++ b/tests/PHPStan/Analyser/TypeSpecifierContextTest.php @@ -2,7 +2,7 @@ namespace PHPStan\Analyser; -class TypeSpecifierContextTest extends \PHPStan\Testing\TestCase +class TypeSpecifierContextTest extends \PHPStan\Testing\PHPStanTestCase { public function dataContext(): array diff --git a/tests/PHPStan/Analyser/TypeSpecifierTest.php b/tests/PHPStan/Analyser/TypeSpecifierTest.php index a6e56b4461..9f2644b2b4 100644 --- a/tests/PHPStan/Analyser/TypeSpecifierTest.php +++ b/tests/PHPStan/Analyser/TypeSpecifierTest.php @@ -27,7 +27,7 @@ use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; -class TypeSpecifierTest extends \PHPStan\Testing\TestCase +class TypeSpecifierTest extends \PHPStan\Testing\PHPStanTestCase { private const FALSEY_TYPE_DESCRIPTION = '0|0.0|\'\'|\'0\'|array()|false|null'; diff --git a/tests/PHPStan/Analyser/traitsCachingIssue/TraitsCachingIssueIntegrationTest.php b/tests/PHPStan/Analyser/traitsCachingIssue/TraitsCachingIssueIntegrationTest.php index 7b0fa2c07d..ee7dd1f215 100644 --- a/tests/PHPStan/Analyser/traitsCachingIssue/TraitsCachingIssueIntegrationTest.php +++ b/tests/PHPStan/Analyser/traitsCachingIssue/TraitsCachingIssueIntegrationTest.php @@ -4,7 +4,7 @@ use PHPStan\File\FileHelper; use PHPStan\File\FileReader; -use PHPStan\Testing\TestCase; +use PHPStan\Testing\PHPStanTestCase; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; use function file_exists; @@ -12,7 +12,7 @@ /** * @group exec */ -class TraitsCachingIssueIntegrationTest extends TestCase +class TraitsCachingIssueIntegrationTest extends PHPStanTestCase { /** @var string|null */ diff --git a/tests/PHPStan/Broker/BrokerTest.php b/tests/PHPStan/Broker/BrokerTest.php index 637fc2de78..91bdc7ca13 100644 --- a/tests/PHPStan/Broker/BrokerTest.php +++ b/tests/PHPStan/Broker/BrokerTest.php @@ -22,7 +22,7 @@ use PHPStan\Reflection\SignatureMap\NativeFunctionReflectionProvider; use PHPStan\Type\FileTypeMapper; -class BrokerTest extends \PHPStan\Testing\TestCase +class BrokerTest extends \PHPStan\Testing\PHPStanTestCase { /** @var \PHPStan\Broker\Broker */ diff --git a/tests/PHPStan/Command/AnalyseApplicationIntegrationTest.php b/tests/PHPStan/Command/AnalyseApplicationIntegrationTest.php index 844dc62b09..c4605b5bcb 100644 --- a/tests/PHPStan/Command/AnalyseApplicationIntegrationTest.php +++ b/tests/PHPStan/Command/AnalyseApplicationIntegrationTest.php @@ -11,7 +11,7 @@ use Symfony\Component\Console\Output\StreamOutput; use Symfony\Component\Console\Style\SymfonyStyle; -class AnalyseApplicationIntegrationTest extends \PHPStan\Testing\TestCase +class AnalyseApplicationIntegrationTest extends \PHPStan\Testing\PHPStanTestCase { public function testExecuteOnAFile(): void diff --git a/tests/PHPStan/Command/AnalyseCommandTest.php b/tests/PHPStan/Command/AnalyseCommandTest.php index 6754544607..5fddff391f 100644 --- a/tests/PHPStan/Command/AnalyseCommandTest.php +++ b/tests/PHPStan/Command/AnalyseCommandTest.php @@ -8,7 +8,7 @@ /** * @group exec */ -class AnalyseCommandTest extends \PHPStan\Testing\TestCase +class AnalyseCommandTest extends \PHPStan\Testing\PHPStanTestCase { /** diff --git a/tests/PHPStan/Command/AnalysisResultTest.php b/tests/PHPStan/Command/AnalysisResultTest.php index d72143624f..7be232359a 100644 --- a/tests/PHPStan/Command/AnalysisResultTest.php +++ b/tests/PHPStan/Command/AnalysisResultTest.php @@ -3,9 +3,9 @@ namespace PHPStan\Command; use PHPStan\Analyser\Error; -use PHPStan\Testing\TestCase; +use PHPStan\Testing\PHPStanTestCase; -final class AnalysisResultTest extends TestCase +final class AnalysisResultTest extends PHPStanTestCase { public function testErrorsAreSortedByFileNameAndLine(): void diff --git a/tests/PHPStan/Command/IgnoredRegexValidatorTest.php b/tests/PHPStan/Command/IgnoredRegexValidatorTest.php index 0220251751..7646267702 100644 --- a/tests/PHPStan/Command/IgnoredRegexValidatorTest.php +++ b/tests/PHPStan/Command/IgnoredRegexValidatorTest.php @@ -3,9 +3,9 @@ namespace PHPStan\Command; use PHPStan\PhpDoc\TypeStringResolver; -use PHPStan\Testing\TestCase; +use PHPStan\Testing\PHPStanTestCase; -class IgnoredRegexValidatorTest extends TestCase +class IgnoredRegexValidatorTest extends PHPStanTestCase { public function dataValidate(): array diff --git a/tests/PHPStan/File/FileExcluderTest.php b/tests/PHPStan/File/FileExcluderTest.php index 808b36395c..ae415683c4 100644 --- a/tests/PHPStan/File/FileExcluderTest.php +++ b/tests/PHPStan/File/FileExcluderTest.php @@ -2,7 +2,7 @@ namespace PHPStan\File; -class FileExcluderTest extends \PHPStan\Testing\TestCase +class FileExcluderTest extends \PHPStan\Testing\PHPStanTestCase { /** diff --git a/tests/PHPStan/File/FileHelperTest.php b/tests/PHPStan/File/FileHelperTest.php index 9e2d88ec8d..4082896b25 100644 --- a/tests/PHPStan/File/FileHelperTest.php +++ b/tests/PHPStan/File/FileHelperTest.php @@ -2,7 +2,7 @@ namespace PHPStan\File; -class FileHelperTest extends \PHPStan\Testing\TestCase +class FileHelperTest extends \PHPStan\Testing\PHPStanTestCase { /** diff --git a/tests/PHPStan/Generics/TemplateTypeFactoryTest.php b/tests/PHPStan/Generics/TemplateTypeFactoryTest.php index cea4dd2da3..e2b36d0a05 100644 --- a/tests/PHPStan/Generics/TemplateTypeFactoryTest.php +++ b/tests/PHPStan/Generics/TemplateTypeFactoryTest.php @@ -14,7 +14,7 @@ use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; -class TemplateTypeFactoryTest extends \PHPStan\Testing\TestCase +class TemplateTypeFactoryTest extends \PHPStan\Testing\PHPStanTestCase { /** @return array */ diff --git a/tests/PHPStan/Parser/CachedParserTest.php b/tests/PHPStan/Parser/CachedParserTest.php index a76dd3f97c..3624cc798d 100644 --- a/tests/PHPStan/Parser/CachedParserTest.php +++ b/tests/PHPStan/Parser/CachedParserTest.php @@ -4,9 +4,9 @@ use PhpParser\Node\Stmt\Namespace_; use PHPStan\File\FileReader; -use PHPStan\Testing\TestCase; +use PHPStan\Testing\PHPStanTestCase; -class CachedParserTest extends TestCase +class CachedParserTest extends PHPStanTestCase { /** diff --git a/tests/PHPStan/Reflection/Annotations/AnnotationsMethodsClassReflectionExtensionTest.php b/tests/PHPStan/Reflection/Annotations/AnnotationsMethodsClassReflectionExtensionTest.php index d585540774..ddda1e195d 100644 --- a/tests/PHPStan/Reflection/Annotations/AnnotationsMethodsClassReflectionExtensionTest.php +++ b/tests/PHPStan/Reflection/Annotations/AnnotationsMethodsClassReflectionExtensionTest.php @@ -9,7 +9,7 @@ use PHPStan\Reflection\Php\PhpMethodReflection; use PHPStan\Type\VerbosityLevel; -class AnnotationsMethodsClassReflectionExtensionTest extends \PHPStan\Testing\TestCase +class AnnotationsMethodsClassReflectionExtensionTest extends \PHPStan\Testing\PHPStanTestCase { public function dataMethods(): array diff --git a/tests/PHPStan/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtensionTest.php b/tests/PHPStan/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtensionTest.php index 0291842501..67a9fc47d8 100644 --- a/tests/PHPStan/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtensionTest.php +++ b/tests/PHPStan/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtensionTest.php @@ -6,7 +6,7 @@ use PHPStan\Broker\Broker; use PHPStan\Type\VerbosityLevel; -class AnnotationsPropertiesClassReflectionExtensionTest extends \PHPStan\Testing\TestCase +class AnnotationsPropertiesClassReflectionExtensionTest extends \PHPStan\Testing\PHPStanTestCase { public function dataProperties(): array diff --git a/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php b/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php index 6ffdc2fee1..2da095b58e 100644 --- a/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php +++ b/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php @@ -6,7 +6,7 @@ use PHPStan\Analyser\Scope; use PHPStan\Broker\Broker; -class DeprecatedAnnotationsTest extends \PHPStan\Testing\TestCase +class DeprecatedAnnotationsTest extends \PHPStan\Testing\PHPStanTestCase { public function dataDeprecatedAnnotations(): array diff --git a/tests/PHPStan/Reflection/Annotations/FinalAnnotationsTest.php b/tests/PHPStan/Reflection/Annotations/FinalAnnotationsTest.php index d876b4765f..c14a273b2d 100644 --- a/tests/PHPStan/Reflection/Annotations/FinalAnnotationsTest.php +++ b/tests/PHPStan/Reflection/Annotations/FinalAnnotationsTest.php @@ -6,7 +6,7 @@ use PHPStan\Analyser\Scope; use PHPStan\Broker\Broker; -class FinalAnnotationsTest extends \PHPStan\Testing\TestCase +class FinalAnnotationsTest extends \PHPStan\Testing\PHPStanTestCase { public function dataFinalAnnotations(): array diff --git a/tests/PHPStan/Reflection/Annotations/InternalAnnotationsTest.php b/tests/PHPStan/Reflection/Annotations/InternalAnnotationsTest.php index 095001f84b..24eac4c81d 100644 --- a/tests/PHPStan/Reflection/Annotations/InternalAnnotationsTest.php +++ b/tests/PHPStan/Reflection/Annotations/InternalAnnotationsTest.php @@ -6,7 +6,7 @@ use PHPStan\Analyser\Scope; use PHPStan\Broker\Broker; -class InternalAnnotationsTest extends \PHPStan\Testing\TestCase +class InternalAnnotationsTest extends \PHPStan\Testing\PHPStanTestCase { public function dataInternalAnnotations(): array diff --git a/tests/PHPStan/Reflection/Annotations/ThrowsAnnotationsTest.php b/tests/PHPStan/Reflection/Annotations/ThrowsAnnotationsTest.php index 4e1de7a891..5cc633832c 100644 --- a/tests/PHPStan/Reflection/Annotations/ThrowsAnnotationsTest.php +++ b/tests/PHPStan/Reflection/Annotations/ThrowsAnnotationsTest.php @@ -7,7 +7,7 @@ use PHPStan\Broker\Broker; use PHPStan\Type\VerbosityLevel; -class ThrowsAnnotationsTest extends \PHPStan\Testing\TestCase +class ThrowsAnnotationsTest extends \PHPStan\Testing\PHPStanTestCase { public function dataThrowsAnnotations(): array diff --git a/tests/PHPStan/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocatorTest.php b/tests/PHPStan/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocatorTest.php index 6d37cd64fd..4196986d48 100644 --- a/tests/PHPStan/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocatorTest.php +++ b/tests/PHPStan/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocatorTest.php @@ -6,7 +6,7 @@ use PHPStan\BetterReflection\Reflector\ClassReflector; use PHPStan\BetterReflection\Reflector\ConstantReflector; use PHPStan\BetterReflection\Reflector\FunctionReflector; -use PHPStan\Testing\TestCase; +use PHPStan\Testing\PHPStanTestCase; use TestSingleFileSourceLocator\AFoo; use TestSingleFileSourceLocator\InCondition; @@ -15,7 +15,7 @@ function testFunctionForLocator(): void // phpcs:disable } -class AutoloadSourceLocatorTest extends TestCase +class AutoloadSourceLocatorTest extends PHPStanTestCase { public function testAutoloadEverythingInFile(): void diff --git a/tests/PHPStan/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorTest.php b/tests/PHPStan/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorTest.php index 2399a64366..616e2617a2 100644 --- a/tests/PHPStan/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorTest.php +++ b/tests/PHPStan/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorTest.php @@ -5,10 +5,10 @@ use PHPStan\BetterReflection\Reflector\ClassReflector; use PHPStan\BetterReflection\Reflector\Exception\IdentifierNotFound; use PHPStan\BetterReflection\Reflector\FunctionReflector; -use PHPStan\Testing\TestCase; +use PHPStan\Testing\PHPStanTestCase; use TestDirectorySourceLocator\AFoo; -class OptimizedDirectorySourceLocatorTest extends TestCase +class OptimizedDirectorySourceLocatorTest extends PHPStanTestCase { public function dataClass(): array diff --git a/tests/PHPStan/Reflection/BetterReflection/SourceLocator/OptimizedSingleFileSourceLocatorTest.php b/tests/PHPStan/Reflection/BetterReflection/SourceLocator/OptimizedSingleFileSourceLocatorTest.php index 38185ccfc8..c28ab59464 100644 --- a/tests/PHPStan/Reflection/BetterReflection/SourceLocator/OptimizedSingleFileSourceLocatorTest.php +++ b/tests/PHPStan/Reflection/BetterReflection/SourceLocator/OptimizedSingleFileSourceLocatorTest.php @@ -6,10 +6,10 @@ use PHPStan\BetterReflection\Reflector\ConstantReflector; use PHPStan\BetterReflection\Reflector\Exception\IdentifierNotFound; use PHPStan\BetterReflection\Reflector\FunctionReflector; -use PHPStan\Testing\TestCase; +use PHPStan\Testing\PHPStanTestCase; use TestSingleFileSourceLocator\AFoo; -class OptimizedSingleFileSourceLocatorTest extends TestCase +class OptimizedSingleFileSourceLocatorTest extends PHPStanTestCase { public function dataClass(): array diff --git a/tests/PHPStan/Reflection/ClassReflectionTest.php b/tests/PHPStan/Reflection/ClassReflectionTest.php index 48cce715eb..63d810a67a 100644 --- a/tests/PHPStan/Reflection/ClassReflectionTest.php +++ b/tests/PHPStan/Reflection/ClassReflectionTest.php @@ -13,7 +13,7 @@ use PHPStan\Type\FileTypeMapper; use WrongClassConstantFile\SecuredRouter; -class ClassReflectionTest extends \PHPStan\Testing\TestCase +class ClassReflectionTest extends \PHPStan\Testing\PHPStanTestCase { public function dataHasTraitUse(): array diff --git a/tests/PHPStan/Reflection/GenericParametersAcceptorResolverTest.php b/tests/PHPStan/Reflection/GenericParametersAcceptorResolverTest.php index 909057cfb5..7168dfc167 100644 --- a/tests/PHPStan/Reflection/GenericParametersAcceptorResolverTest.php +++ b/tests/PHPStan/Reflection/GenericParametersAcceptorResolverTest.php @@ -18,7 +18,7 @@ use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; -class GenericParametersAcceptorResolverTest extends \PHPStan\Testing\TestCase +class GenericParametersAcceptorResolverTest extends \PHPStan\Testing\PHPStanTestCase { /** diff --git a/tests/PHPStan/Reflection/MixedTypeTest.php b/tests/PHPStan/Reflection/MixedTypeTest.php index edc0370428..c9b5b7eb66 100644 --- a/tests/PHPStan/Reflection/MixedTypeTest.php +++ b/tests/PHPStan/Reflection/MixedTypeTest.php @@ -4,10 +4,10 @@ use NativeMixedType\Foo; use PhpParser\Node\Name; -use PHPStan\Testing\TestCase; +use PHPStan\Testing\PHPStanTestCase; use PHPStan\Type\MixedType; -class MixedTypeTest extends TestCase +class MixedTypeTest extends PHPStanTestCase { public function testMixedType(): void diff --git a/tests/PHPStan/Reflection/ParametersAcceptorSelectorTest.php b/tests/PHPStan/Reflection/ParametersAcceptorSelectorTest.php index ecf59410e3..60f2746c0a 100644 --- a/tests/PHPStan/Reflection/ParametersAcceptorSelectorTest.php +++ b/tests/PHPStan/Reflection/ParametersAcceptorSelectorTest.php @@ -22,7 +22,7 @@ use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; -class ParametersAcceptorSelectorTest extends \PHPStan\Testing\TestCase +class ParametersAcceptorSelectorTest extends \PHPStan\Testing\PHPStanTestCase { public function dataSelectFromTypes(): \Generator diff --git a/tests/PHPStan/Reflection/Php/UniversalObjectCratesClassReflectionExtensionTest.php b/tests/PHPStan/Reflection/Php/UniversalObjectCratesClassReflectionExtensionTest.php index 0c5c5d812b..db97736a8b 100644 --- a/tests/PHPStan/Reflection/Php/UniversalObjectCratesClassReflectionExtensionTest.php +++ b/tests/PHPStan/Reflection/Php/UniversalObjectCratesClassReflectionExtensionTest.php @@ -6,7 +6,7 @@ use PHPStan\Type\ObjectType; use PHPStan\Type\StringType; -class UniversalObjectCratesClassReflectionExtensionTest extends \PHPStan\Testing\TestCase +class UniversalObjectCratesClassReflectionExtensionTest extends \PHPStan\Testing\PHPStanTestCase { public function testNonexistentClass(): void diff --git a/tests/PHPStan/Reflection/ReflectionProviderTest.php b/tests/PHPStan/Reflection/ReflectionProviderTest.php index 431214c141..8690fd0160 100644 --- a/tests/PHPStan/Reflection/ReflectionProviderTest.php +++ b/tests/PHPStan/Reflection/ReflectionProviderTest.php @@ -3,12 +3,12 @@ namespace PHPStan\Reflection; use PhpParser\Node\Name; -use PHPStan\Testing\TestCase; +use PHPStan\Testing\PHPStanTestCase; use PHPStan\Type\ObjectType; use PHPStan\Type\Type; use PHPStan\Type\VerbosityLevel; -class ReflectionProviderTest extends TestCase +class ReflectionProviderTest extends PHPStanTestCase { public function dataFunctionThrowType(): iterable diff --git a/tests/PHPStan/Reflection/SignatureMap/FunctionMetadataTest.php b/tests/PHPStan/Reflection/SignatureMap/FunctionMetadataTest.php index ac5feca510..891505b8db 100644 --- a/tests/PHPStan/Reflection/SignatureMap/FunctionMetadataTest.php +++ b/tests/PHPStan/Reflection/SignatureMap/FunctionMetadataTest.php @@ -4,9 +4,9 @@ use Nette\Schema\Expect; use Nette\Schema\Processor; -use PHPStan\Testing\TestCase; +use PHPStan\Testing\PHPStanTestCase; -class FunctionMetadataTest extends TestCase +class FunctionMetadataTest extends PHPStanTestCase { public function testSchema(): void diff --git a/tests/PHPStan/Reflection/SignatureMap/Php8SignatureMapProviderTest.php b/tests/PHPStan/Reflection/SignatureMap/Php8SignatureMapProviderTest.php index 8684859503..390ad5a955 100644 --- a/tests/PHPStan/Reflection/SignatureMap/Php8SignatureMapProviderTest.php +++ b/tests/PHPStan/Reflection/SignatureMap/Php8SignatureMapProviderTest.php @@ -7,7 +7,7 @@ use PHPStan\Reflection\BetterReflection\SourceLocator\FileNodesFetcher; use PHPStan\Reflection\Native\NativeParameterReflection; use PHPStan\Reflection\PassedByReference; -use PHPStan\Testing\TestCase; +use PHPStan\Testing\PHPStanTestCase; use PHPStan\Type\ArrayType; use PHPStan\Type\BooleanType; use PHPStan\Type\CallableType; @@ -26,7 +26,7 @@ use PHPStan\Type\VerbosityLevel; use PHPStan\Type\VoidType; -class Php8SignatureMapProviderTest extends TestCase +class Php8SignatureMapProviderTest extends PHPStanTestCase { public function dataFunctions(): array diff --git a/tests/PHPStan/Reflection/SignatureMap/SignatureMapParserTest.php b/tests/PHPStan/Reflection/SignatureMap/SignatureMapParserTest.php index a0b85a89ce..d191e7c11a 100644 --- a/tests/PHPStan/Reflection/SignatureMap/SignatureMapParserTest.php +++ b/tests/PHPStan/Reflection/SignatureMap/SignatureMapParserTest.php @@ -18,7 +18,7 @@ use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; -class SignatureMapParserTest extends \PHPStan\Testing\TestCase +class SignatureMapParserTest extends \PHPStan\Testing\PHPStanTestCase { public function dataGetFunctions(): array diff --git a/tests/PHPStan/Reflection/Type/IntersectionTypeMethodReflectionTest.php b/tests/PHPStan/Reflection/Type/IntersectionTypeMethodReflectionTest.php index cbd42d5f9c..191aa71e2d 100644 --- a/tests/PHPStan/Reflection/Type/IntersectionTypeMethodReflectionTest.php +++ b/tests/PHPStan/Reflection/Type/IntersectionTypeMethodReflectionTest.php @@ -5,7 +5,7 @@ use PHPStan\Reflection\MethodReflection; use PHPStan\TrinaryLogic; -class IntersectionTypeMethodReflectionTest extends \PHPStan\Testing\TestCase +class IntersectionTypeMethodReflectionTest extends \PHPStan\Testing\PHPStanTestCase { public function testCollectsDeprecatedMessages(): void diff --git a/tests/PHPStan/Reflection/Type/UnionTypeMethodReflectionTest.php b/tests/PHPStan/Reflection/Type/UnionTypeMethodReflectionTest.php index 93a168e43e..b17b962fb0 100644 --- a/tests/PHPStan/Reflection/Type/UnionTypeMethodReflectionTest.php +++ b/tests/PHPStan/Reflection/Type/UnionTypeMethodReflectionTest.php @@ -5,7 +5,7 @@ use PHPStan\Reflection\MethodReflection; use PHPStan\TrinaryLogic; -class UnionTypeMethodReflectionTest extends \PHPStan\Testing\TestCase +class UnionTypeMethodReflectionTest extends \PHPStan\Testing\PHPStanTestCase { public function testCollectsDeprecatedMessages(): void diff --git a/tests/PHPStan/Reflection/UnionTypesTest.php b/tests/PHPStan/Reflection/UnionTypesTest.php index 692b4f31e1..8ef3e7ef6a 100644 --- a/tests/PHPStan/Reflection/UnionTypesTest.php +++ b/tests/PHPStan/Reflection/UnionTypesTest.php @@ -4,11 +4,11 @@ use NativeUnionTypes\Foo; use PhpParser\Node\Name; -use PHPStan\Testing\TestCase; +use PHPStan\Testing\PHPStanTestCase; use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; -class UnionTypesTest extends TestCase +class UnionTypesTest extends PHPStanTestCase { public function testUnionTypes(): void diff --git a/tests/PHPStan/Rules/Exceptions/DefaultExceptionTypeResolverTest.php b/tests/PHPStan/Rules/Exceptions/DefaultExceptionTypeResolverTest.php index bfcaf40aea..4b56182cb1 100644 --- a/tests/PHPStan/Rules/Exceptions/DefaultExceptionTypeResolverTest.php +++ b/tests/PHPStan/Rules/Exceptions/DefaultExceptionTypeResolverTest.php @@ -4,9 +4,9 @@ use PHPStan\Analyser\ScopeContext; use PHPStan\Analyser\ScopeFactory; -use PHPStan\Testing\TestCase; +use PHPStan\Testing\PHPStanTestCase; -class DefaultExceptionTypeResolverTest extends TestCase +class DefaultExceptionTypeResolverTest extends PHPStanTestCase { public function dataIsCheckedException(): array diff --git a/tests/PHPStan/Rules/RegistryTest.php b/tests/PHPStan/Rules/RegistryTest.php index 77469e4362..3544f01136 100644 --- a/tests/PHPStan/Rules/RegistryTest.php +++ b/tests/PHPStan/Rules/RegistryTest.php @@ -4,7 +4,7 @@ use PHPStan\Analyser\Scope; -class RegistryTest extends \PHPStan\Testing\TestCase +class RegistryTest extends \PHPStan\Testing\PHPStanTestCase { public function testGetRules(): void diff --git a/tests/PHPStan/TrinaryLogicTest.php b/tests/PHPStan/TrinaryLogicTest.php index def1e3522e..a532a71836 100644 --- a/tests/PHPStan/TrinaryLogicTest.php +++ b/tests/PHPStan/TrinaryLogicTest.php @@ -2,7 +2,7 @@ namespace PHPStan; -class TrinaryLogicTest extends \PHPStan\Testing\TestCase +class TrinaryLogicTest extends \PHPStan\Testing\PHPStanTestCase { public function dataAnd(): array diff --git a/tests/PHPStan/Type/Accessory/HasMethodTypeTest.php b/tests/PHPStan/Type/Accessory/HasMethodTypeTest.php index 525b1f6f16..787f73de19 100644 --- a/tests/PHPStan/Type/Accessory/HasMethodTypeTest.php +++ b/tests/PHPStan/Type/Accessory/HasMethodTypeTest.php @@ -14,7 +14,7 @@ use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; -class HasMethodTypeTest extends \PHPStan\Testing\TestCase +class HasMethodTypeTest extends \PHPStan\Testing\PHPStanTestCase { public function dataIsSuperTypeOf(): array diff --git a/tests/PHPStan/Type/Accessory/HasPropertyTypeTest.php b/tests/PHPStan/Type/Accessory/HasPropertyTypeTest.php index 9e0012051a..a863a3cd17 100644 --- a/tests/PHPStan/Type/Accessory/HasPropertyTypeTest.php +++ b/tests/PHPStan/Type/Accessory/HasPropertyTypeTest.php @@ -14,7 +14,7 @@ use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; -class HasPropertyTypeTest extends \PHPStan\Testing\TestCase +class HasPropertyTypeTest extends \PHPStan\Testing\PHPStanTestCase { public function dataIsSuperTypeOf(): array diff --git a/tests/PHPStan/Type/ArrayTypeTest.php b/tests/PHPStan/Type/ArrayTypeTest.php index 1ec0a20c9c..df1211e805 100644 --- a/tests/PHPStan/Type/ArrayTypeTest.php +++ b/tests/PHPStan/Type/ArrayTypeTest.php @@ -11,7 +11,7 @@ use PHPStan\Type\Generic\TemplateTypeScope; use PHPStan\Type\Generic\TemplateTypeVariance; -class ArrayTypeTest extends \PHPStan\Testing\TestCase +class ArrayTypeTest extends \PHPStan\Testing\PHPStanTestCase { public function dataIsSuperTypeOf(): array diff --git a/tests/PHPStan/Type/BooleanTypeTest.php b/tests/PHPStan/Type/BooleanTypeTest.php index 0d52f3f64c..1d67a1a641 100644 --- a/tests/PHPStan/Type/BooleanTypeTest.php +++ b/tests/PHPStan/Type/BooleanTypeTest.php @@ -6,7 +6,7 @@ use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantIntegerType; -class BooleanTypeTest extends \PHPStan\Testing\TestCase +class BooleanTypeTest extends \PHPStan\Testing\PHPStanTestCase { public function dataAccepts(): array diff --git a/tests/PHPStan/Type/CallableTypeTest.php b/tests/PHPStan/Type/CallableTypeTest.php index 166b499817..9695e032ee 100644 --- a/tests/PHPStan/Type/CallableTypeTest.php +++ b/tests/PHPStan/Type/CallableTypeTest.php @@ -14,7 +14,7 @@ use PHPStan\Type\Generic\TemplateTypeScope; use PHPStan\Type\Generic\TemplateTypeVariance; -class CallableTypeTest extends \PHPStan\Testing\TestCase +class CallableTypeTest extends \PHPStan\Testing\PHPStanTestCase { public function dataIsSuperTypeOf(): array diff --git a/tests/PHPStan/Type/ClassStringTypeTest.php b/tests/PHPStan/Type/ClassStringTypeTest.php index 5d715a948c..12d3106ca9 100644 --- a/tests/PHPStan/Type/ClassStringTypeTest.php +++ b/tests/PHPStan/Type/ClassStringTypeTest.php @@ -2,12 +2,12 @@ namespace PHPStan\Type; -use PHPStan\Testing\TestCase; +use PHPStan\Testing\PHPStanTestCase; use PHPStan\TrinaryLogic; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\Generic\GenericClassStringType; -class ClassStringTypeTest extends TestCase +class ClassStringTypeTest extends PHPStanTestCase { public function dataIsSuperTypeOf(): array diff --git a/tests/PHPStan/Type/ClosureTypeTest.php b/tests/PHPStan/Type/ClosureTypeTest.php index c788121178..7f4298dd7d 100644 --- a/tests/PHPStan/Type/ClosureTypeTest.php +++ b/tests/PHPStan/Type/ClosureTypeTest.php @@ -4,7 +4,7 @@ use PHPStan\TrinaryLogic; -class ClosureTypeTest extends \PHPStan\Testing\TestCase +class ClosureTypeTest extends \PHPStan\Testing\PHPStanTestCase { public function dataIsSuperTypeOf(): array diff --git a/tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php b/tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php index 8ffce8f7ad..f15c2eaf79 100644 --- a/tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php +++ b/tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php @@ -18,7 +18,7 @@ use PHPStan\Type\TypeCombinator; use PHPStan\Type\VerbosityLevel; -class ConstantArrayTypeTest extends \PHPStan\Testing\TestCase +class ConstantArrayTypeTest extends \PHPStan\Testing\PHPStanTestCase { public function dataAccepts(): iterable diff --git a/tests/PHPStan/Type/Constant/ConstantFloatTypeTest.php b/tests/PHPStan/Type/Constant/ConstantFloatTypeTest.php index b8261bbea7..1cc80e95a5 100644 --- a/tests/PHPStan/Type/Constant/ConstantFloatTypeTest.php +++ b/tests/PHPStan/Type/Constant/ConstantFloatTypeTest.php @@ -4,7 +4,7 @@ use PHPStan\Type\VerbosityLevel; -class ConstantFloatTypeTest extends \PHPStan\Testing\TestCase +class ConstantFloatTypeTest extends \PHPStan\Testing\PHPStanTestCase { public function dataDescribe(): array diff --git a/tests/PHPStan/Type/Constant/ConstantIntegerTypeTest.php b/tests/PHPStan/Type/Constant/ConstantIntegerTypeTest.php index 5a23289569..23fc7a5d63 100644 --- a/tests/PHPStan/Type/Constant/ConstantIntegerTypeTest.php +++ b/tests/PHPStan/Type/Constant/ConstantIntegerTypeTest.php @@ -7,7 +7,7 @@ use PHPStan\Type\Type; use PHPStan\Type\VerbosityLevel; -class ConstantIntegerTypeTest extends \PHPStan\Testing\TestCase +class ConstantIntegerTypeTest extends \PHPStan\Testing\PHPStanTestCase { public function dataAccepts(): iterable diff --git a/tests/PHPStan/Type/Constant/ConstantStringTypeTest.php b/tests/PHPStan/Type/Constant/ConstantStringTypeTest.php index 3f5360f072..806148573c 100644 --- a/tests/PHPStan/Type/Constant/ConstantStringTypeTest.php +++ b/tests/PHPStan/Type/Constant/ConstantStringTypeTest.php @@ -2,7 +2,7 @@ namespace PHPStan\Type\Constant; -use PHPStan\Testing\TestCase; +use PHPStan\Testing\PHPStanTestCase; use PHPStan\TrinaryLogic; use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\Generic\GenericClassStringType; @@ -14,7 +14,7 @@ use PHPStan\Type\Type; use PHPStan\Type\VerbosityLevel; -class ConstantStringTypeTest extends TestCase +class ConstantStringTypeTest extends PHPStanTestCase { public function dataIsSuperTypeOf(): array diff --git a/tests/PHPStan/Type/FileTypeMapperTest.php b/tests/PHPStan/Type/FileTypeMapperTest.php index a9b34cf944..82ec2ff7eb 100644 --- a/tests/PHPStan/Type/FileTypeMapperTest.php +++ b/tests/PHPStan/Type/FileTypeMapperTest.php @@ -2,7 +2,7 @@ namespace PHPStan\Type; -class FileTypeMapperTest extends \PHPStan\Testing\TestCase +class FileTypeMapperTest extends \PHPStan\Testing\PHPStanTestCase { public function testGetResolvedPhpDoc(): void diff --git a/tests/PHPStan/Type/FloatTypeTest.php b/tests/PHPStan/Type/FloatTypeTest.php index 903beaf221..f99c0afefd 100644 --- a/tests/PHPStan/Type/FloatTypeTest.php +++ b/tests/PHPStan/Type/FloatTypeTest.php @@ -7,7 +7,7 @@ use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; -class FloatTypeTest extends \PHPStan\Testing\TestCase +class FloatTypeTest extends \PHPStan\Testing\PHPStanTestCase { public function dataAccepts(): array diff --git a/tests/PHPStan/Type/Generic/GenericClassStringTypeTest.php b/tests/PHPStan/Type/Generic/GenericClassStringTypeTest.php index 277a3875f5..a281a78c70 100644 --- a/tests/PHPStan/Type/Generic/GenericClassStringTypeTest.php +++ b/tests/PHPStan/Type/Generic/GenericClassStringTypeTest.php @@ -15,7 +15,7 @@ use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; -class GenericClassStringTypeTest extends \PHPStan\Testing\TestCase +class GenericClassStringTypeTest extends \PHPStan\Testing\PHPStanTestCase { public function dataIsSuperTypeOf(): array diff --git a/tests/PHPStan/Type/Generic/GenericObjectTypeTest.php b/tests/PHPStan/Type/Generic/GenericObjectTypeTest.php index fe412c6971..72d721b310 100644 --- a/tests/PHPStan/Type/Generic/GenericObjectTypeTest.php +++ b/tests/PHPStan/Type/Generic/GenericObjectTypeTest.php @@ -16,7 +16,7 @@ use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; -class GenericObjectTypeTest extends \PHPStan\Testing\TestCase +class GenericObjectTypeTest extends \PHPStan\Testing\PHPStanTestCase { public function dataIsSuperTypeOf(): array diff --git a/tests/PHPStan/Type/Generic/TemplateTypeHelperTest.php b/tests/PHPStan/Type/Generic/TemplateTypeHelperTest.php index 279617324e..91bfcae0e2 100644 --- a/tests/PHPStan/Type/Generic/TemplateTypeHelperTest.php +++ b/tests/PHPStan/Type/Generic/TemplateTypeHelperTest.php @@ -6,7 +6,7 @@ use PHPStan\Type\ObjectType; use PHPStan\Type\VerbosityLevel; -class TemplateTypeHelperTest extends \PHPStan\Testing\TestCase +class TemplateTypeHelperTest extends \PHPStan\Testing\PHPStanTestCase { public function testIssue2512(): void diff --git a/tests/PHPStan/Type/IntegerTypeTest.php b/tests/PHPStan/Type/IntegerTypeTest.php index 7b98244725..8400b2df8a 100644 --- a/tests/PHPStan/Type/IntegerTypeTest.php +++ b/tests/PHPStan/Type/IntegerTypeTest.php @@ -7,7 +7,7 @@ use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; -class IntegerTypeTest extends \PHPStan\Testing\TestCase +class IntegerTypeTest extends \PHPStan\Testing\PHPStanTestCase { public function dataAccepts(): array diff --git a/tests/PHPStan/Type/IntersectionTypeTest.php b/tests/PHPStan/Type/IntersectionTypeTest.php index 634c11b0b4..03434722b1 100644 --- a/tests/PHPStan/Type/IntersectionTypeTest.php +++ b/tests/PHPStan/Type/IntersectionTypeTest.php @@ -11,7 +11,7 @@ use PHPStan\Type\Constant\ConstantStringType; use Test\ClassWithToString; -class IntersectionTypeTest extends \PHPStan\Testing\TestCase +class IntersectionTypeTest extends \PHPStan\Testing\PHPStanTestCase { public function dataAccepts(): \Iterator diff --git a/tests/PHPStan/Type/IterableTypeTest.php b/tests/PHPStan/Type/IterableTypeTest.php index 7aaf86149c..0ac3b7ab07 100644 --- a/tests/PHPStan/Type/IterableTypeTest.php +++ b/tests/PHPStan/Type/IterableTypeTest.php @@ -11,7 +11,7 @@ use PHPStan\Type\Generic\TemplateTypeScope; use PHPStan\Type\Generic\TemplateTypeVariance; -class IterableTypeTest extends \PHPStan\Testing\TestCase +class IterableTypeTest extends \PHPStan\Testing\PHPStanTestCase { public function dataIsSuperTypeOf(): array diff --git a/tests/PHPStan/Type/MixedTypeTest.php b/tests/PHPStan/Type/MixedTypeTest.php index acd24ecad6..a0cda10b5f 100644 --- a/tests/PHPStan/Type/MixedTypeTest.php +++ b/tests/PHPStan/Type/MixedTypeTest.php @@ -5,7 +5,7 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\Constant\ConstantIntegerType; -class MixedTypeTest extends \PHPStan\Testing\TestCase +class MixedTypeTest extends \PHPStan\Testing\PHPStanTestCase { public function dataIsSuperTypeOf(): array diff --git a/tests/PHPStan/Type/ObjectTypeTest.php b/tests/PHPStan/Type/ObjectTypeTest.php index 848e97b029..7ce6f37a13 100644 --- a/tests/PHPStan/Type/ObjectTypeTest.php +++ b/tests/PHPStan/Type/ObjectTypeTest.php @@ -11,7 +11,7 @@ use PHPStan\Type\Generic\TemplateTypeScope; use PHPStan\Type\Generic\TemplateTypeVariance; -class ObjectTypeTest extends \PHPStan\Testing\TestCase +class ObjectTypeTest extends \PHPStan\Testing\PHPStanTestCase { public function dataIsIterable(): array diff --git a/tests/PHPStan/Type/ObjectWithoutClassTypeTest.php b/tests/PHPStan/Type/ObjectWithoutClassTypeTest.php index 12a2c179ae..b1692bc73e 100644 --- a/tests/PHPStan/Type/ObjectWithoutClassTypeTest.php +++ b/tests/PHPStan/Type/ObjectWithoutClassTypeTest.php @@ -4,7 +4,7 @@ use PHPStan\TrinaryLogic; -class ObjectWithoutClassTypeTest extends \PHPStan\Testing\TestCase +class ObjectWithoutClassTypeTest extends \PHPStan\Testing\PHPStanTestCase { public function dataIsSuperTypeOf(): array diff --git a/tests/PHPStan/Type/StaticTypeTest.php b/tests/PHPStan/Type/StaticTypeTest.php index 993b9c74c2..bf64cf800b 100644 --- a/tests/PHPStan/Type/StaticTypeTest.php +++ b/tests/PHPStan/Type/StaticTypeTest.php @@ -8,7 +8,7 @@ use StaticTypeTest\Child; use StaticTypeTest\FinalChild; -class StaticTypeTest extends \PHPStan\Testing\TestCase +class StaticTypeTest extends \PHPStan\Testing\PHPStanTestCase { public function dataIsIterable(): array diff --git a/tests/PHPStan/Type/StringTypeTest.php b/tests/PHPStan/Type/StringTypeTest.php index 2aabdb976a..c6fd99b935 100644 --- a/tests/PHPStan/Type/StringTypeTest.php +++ b/tests/PHPStan/Type/StringTypeTest.php @@ -2,7 +2,7 @@ namespace PHPStan\Type; -use PHPStan\Testing\TestCase; +use PHPStan\Testing\PHPStanTestCase; use PHPStan\TrinaryLogic; use PHPStan\Type\Accessory\HasPropertyType; use PHPStan\Type\Constant\ConstantStringType; @@ -12,7 +12,7 @@ use PHPStan\Type\Generic\TemplateTypeVariance; use Test\ClassWithToString; -class StringTypeTest extends TestCase +class StringTypeTest extends PHPStanTestCase { public function dataIsSuperTypeOf(): array diff --git a/tests/PHPStan/Type/TemplateTypeTest.php b/tests/PHPStan/Type/TemplateTypeTest.php index f52ad4b3b0..73cbbad976 100644 --- a/tests/PHPStan/Type/TemplateTypeTest.php +++ b/tests/PHPStan/Type/TemplateTypeTest.php @@ -9,7 +9,7 @@ use PHPStan\Type\Generic\TemplateTypeScope; use PHPStan\Type\Generic\TemplateTypeVariance; -class TemplateTypeTest extends \PHPStan\Testing\TestCase +class TemplateTypeTest extends \PHPStan\Testing\PHPStanTestCase { public function dataAccepts(): array diff --git a/tests/PHPStan/Type/TypeCombinatorTest.php b/tests/PHPStan/Type/TypeCombinatorTest.php index 54f20414d4..f6c4160113 100644 --- a/tests/PHPStan/Type/TypeCombinatorTest.php +++ b/tests/PHPStan/Type/TypeCombinatorTest.php @@ -24,7 +24,7 @@ use PHPStan\Type\Generic\TemplateTypeScope; use PHPStan\Type\Generic\TemplateTypeVariance; -class TypeCombinatorTest extends \PHPStan\Testing\TestCase +class TypeCombinatorTest extends \PHPStan\Testing\PHPStanTestCase { public function dataAddNull(): array diff --git a/tests/PHPStan/Type/UnionTypeTest.php b/tests/PHPStan/Type/UnionTypeTest.php index 1d8894733e..78233edb32 100644 --- a/tests/PHPStan/Type/UnionTypeTest.php +++ b/tests/PHPStan/Type/UnionTypeTest.php @@ -21,7 +21,7 @@ use PHPStan\Type\Generic\TemplateTypeScope; use PHPStan\Type\Generic\TemplateTypeVariance; -class UnionTypeTest extends \PHPStan\Testing\TestCase +class UnionTypeTest extends \PHPStan\Testing\PHPStanTestCase { public function dataIsCallable(): array diff --git a/tests/bootstrap-runtime-reflection.php b/tests/bootstrap-runtime-reflection.php index 7598e4fa5f..7e5193d45f 100644 --- a/tests/bootstrap-runtime-reflection.php +++ b/tests/bootstrap-runtime-reflection.php @@ -2,4 +2,4 @@ require_once __DIR__ . '/bootstrap.php'; -\PHPStan\Testing\TestCase::getContainer(); +\PHPStan\Testing\PHPStanTestCase::getContainer(); diff --git a/tests/bootstrap-static-reflection.php b/tests/bootstrap-static-reflection.php index 6f62631c68..ea0e3dfad6 100644 --- a/tests/bootstrap-static-reflection.php +++ b/tests/bootstrap-static-reflection.php @@ -2,6 +2,6 @@ require_once __DIR__ . '/bootstrap.php'; -\PHPStan\Testing\TestCase::$useStaticReflectionProvider = true; +\PHPStan\Testing\PHPStanTestCase::$useStaticReflectionProvider = true; -\PHPStan\Testing\TestCase::getContainer(); +\PHPStan\Testing\PHPStanTestCase::getContainer(); From 386b5bcb9d8dcb2ddd9482626dfa68c076e49f82 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 13 Sep 2021 13:18:17 +0200 Subject: [PATCH 0279/1284] Fixed InterRangeType multiplication/division of maximas by a negative constant --- src/Analyser/MutatingScope.php | 21 ++++++++++----- .../Analyser/data/integer-range-types.php | 26 +++++++++++++++++-- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 6bf6fb1cbc..11f39716c0 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -1021,12 +1021,7 @@ private function resolveType(Expr $node): Type } if ($type instanceof IntegerRangeType) { - $negativeRange = $this->resolveType(new Node\Expr\BinaryOp\Mul($node->expr, new LNumber(-1))); - - if ( $negativeRange instanceof IntegerRangeType && ($negativeRange->getMin() === null || $negativeRange->getMax() === null)) { - return IntegerRangeType::fromInterval($negativeRange->getMax(), $negativeRange->getMin()); - } - return $negativeRange; + return $this->resolveType(new Node\Expr\BinaryOp\Mul($node->expr, new LNumber(-1))); } return $type; @@ -5221,6 +5216,13 @@ private function integerRangeMath(Type $range, Expr $node, Type $operand): Type [$min, $max] = [$max, $min]; } + // invert maximas on multiplication with negative constants + if ((($range instanceof ConstantIntegerType && $range->getValue() < 0) + || ($operand instanceof ConstantIntegerType && $operand->getValue() < 0)) + && ($min === null || $max === null)) { + [$min, $max] = [$max, $min]; + } + } else { if ($operand instanceof ConstantIntegerType) { $min = $rangeMin !== null && $operand->getValue() !== 0 ? $rangeMin / $operand->getValue() : null; @@ -5246,6 +5248,13 @@ private function integerRangeMath(Type $range, Expr $node, Type $operand): Type [$min, $max] = [$max, $min]; } + // invert maximas on division with negative constants + if ((($range instanceof ConstantIntegerType && $range->getValue() < 0) + || ($operand instanceof ConstantIntegerType && $operand->getValue() < 0)) + && ($min === null || $max === null)) { + [$min, $max] = [$max, $min]; + } + if ($min === null && $max === null) { return new BenevolentUnionType([new IntegerType(), new FloatType()]); } diff --git a/tests/PHPStan/Analyser/data/integer-range-types.php b/tests/PHPStan/Analyser/data/integer-range-types.php index a65885182e..b0b23c538a 100644 --- a/tests/PHPStan/Analyser/data/integer-range-types.php +++ b/tests/PHPStan/Analyser/data/integer-range-types.php @@ -232,8 +232,8 @@ public function math($i, $j, $z, $pi, $r1, $r2, $rMin, $rMax, $x, $y) { assertType('int<-2, 9>|int<21, 30>', $r1 - $z); assertType('int<-200, -20>|int<1, 30>', $r1 * $z); assertType('float|int<0, 10>', $r1 / $z); - assertType('int', $rMin * $z); - assertType('int<-100, max>', $rMax * $z); + assertType('int', $rMin * $z); + assertType('int|int<5, max>', $rMax * $z); assertType('int<2, max>', $pi + 1); assertType('int<-1, max>', $pi - 2); @@ -277,6 +277,28 @@ public function math($i, $j, $z, $pi, $r1, $r2, $rMin, $rMax, $x, $y) { assertType('float|int<5, max>', $rMax / $r1); assertType('5|10|15|20|30', $x / $y); + + } + + /** + * @param int $rMin + * @param int<5, max> $rMax + * + * @see https://www.wolframalpha.com/input/?i=%28interval%5B2%2C%E2%88%9E%5D+%2F+-1%29 + * @see https://3v4l.org/ur9Wf + */ + public function maximaInversion($rMin, $rMax) { + assertType('int<-5, max>', -1 * $rMin); + assertType('int', -2 * $rMax); + + assertType('int<-5, max>', $rMin * -1); + assertType('int', $rMax * -2); + + assertType('float|int<0, max>', -1 / $rMin); + assertType('float|int', -2 / $rMax); + + assertType('float|int<-5, max>', $rMin / -1); + assertType('float|int', $rMax / -2); } /** From 21f6804ff3bc807ca5a5bae96faad538256d7847 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 13 Sep 2021 12:58:45 +0200 Subject: [PATCH 0280/1284] NeonAdapter - removed deprecated options --- src/DependencyInjection/NeonAdapter.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/DependencyInjection/NeonAdapter.php b/src/DependencyInjection/NeonAdapter.php index 8b3b7c9025..ea41b22669 100644 --- a/src/DependencyInjection/NeonAdapter.php +++ b/src/DependencyInjection/NeonAdapter.php @@ -14,7 +14,7 @@ class NeonAdapter implements Adapter { - public const CACHE_KEY = 'v12-excludePaths-merge'; + public const CACHE_KEY = 'v13-remove-deprecated'; private const PREVENT_MERGING_SUFFIX = '!'; @@ -88,8 +88,6 @@ public function process(array $arr, string $fileKey, string $file): array } if (in_array($keyToResolve, [ - '[parameters][autoload_files][]', - '[parameters][autoload_directories][]', '[parameters][paths][]', '[parameters][excludes_analyse][]', '[parameters][excludePaths][]', @@ -97,7 +95,6 @@ public function process(array $arr, string $fileKey, string $file): array '[parameters][excludePaths][analyseAndScan][]', '[parameters][ignoreErrors][][paths][]', '[parameters][ignoreErrors][][path]', - '[parameters][bootstrap]', '[parameters][bootstrapFiles][]', '[parameters][scanFiles][]', '[parameters][scanDirectories][]', From ac23ecd924b4432688f3f0b6ec4b427fbd16a0f9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 13 Sep 2021 13:53:17 +0200 Subject: [PATCH 0281/1284] array_filter test --- .../CallToFunctionParametersRuleTest.php | 30 +++++++++++++++++++ .../Functions/data/array_filter_callback.php | 25 ++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 tests/PHPStan/Rules/Functions/data/array_filter_callback.php diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 62a0835771..c61c4c1643 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -843,6 +843,36 @@ public function testArrayMapMultiple(bool $checkExplicitMixed): void ]); } + public function dataArrayFilterCallback(): array + { + return [ + [true], + [false], + ]; + } + + /** + * @dataProvider dataArrayFilterCallback + * @param bool $checkExplicitMixed + */ + public function testArrayFilterCallback(bool $checkExplicitMixed): void + { + $this->checkExplicitMixed = $checkExplicitMixed; + $errors = [ + [ + 'Parameter #2 $callback of function array_filter expects callable(int): mixed, Closure(string): true given.', + 17, + ], + ]; + if ($checkExplicitMixed) { + $errors[] =[ + 'Parameter #2 $callback of function array_filter expects callable(mixed): mixed, Closure(int): true given.', + 20, + ]; + } + $this->analyse([__DIR__ . '/data/array_filter_callback.php'], $errors); + } + public function testBug5356(): void { if (PHP_VERSION_ID < 70400 && !self::$useStaticReflectionProvider) { diff --git a/tests/PHPStan/Rules/Functions/data/array_filter_callback.php b/tests/PHPStan/Rules/Functions/data/array_filter_callback.php new file mode 100644 index 0000000000..96933c7e1a --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/array_filter_callback.php @@ -0,0 +1,25 @@ + Date: Mon, 13 Sep 2021 13:54:54 +0200 Subject: [PATCH 0282/1284] Regression test Closes https://github.com/phpstan/phpstan/issues/4741 --- tests/PHPStan/Analyser/NodeScopeResolverTest.php | 1 + tests/PHPStan/Analyser/data/bug-4741.php | 15 +++++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 tests/PHPStan/Analyser/data/bug-4741.php diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 2ab6cf53b7..631e4ae8b8 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -505,6 +505,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/array_map_multiple.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/range-numeric-string.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/missing-closure-native-return-typehint.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4741.php'); } /** diff --git a/tests/PHPStan/Analyser/data/bug-4741.php b/tests/PHPStan/Analyser/data/bug-4741.php new file mode 100644 index 0000000000..fd2191fe83 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-4741.php @@ -0,0 +1,15 @@ + Date: Mon, 13 Sep 2021 13:59:12 +0200 Subject: [PATCH 0283/1284] Fix CS --- .../Rules/Functions/CallToFunctionParametersRuleTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index c61c4c1643..7cdd046914 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -865,7 +865,7 @@ public function testArrayFilterCallback(bool $checkExplicitMixed): void ], ]; if ($checkExplicitMixed) { - $errors[] =[ + $errors[] = [ 'Parameter #2 $callback of function array_filter expects callable(mixed): mixed, Closure(int): true given.', 20, ]; From ee5531f2d2455ae12dc1cfce63bd2cd92c4229fe Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 14 Sep 2021 08:20:39 +0200 Subject: [PATCH 0284/1284] PHAR compilation - 1.* to master, 0.12.* to 0.12.x --- .github/workflows/phar-old.yml | 89 ++++++++++++++++++++++++++++++++++ .github/workflows/phar.yml | 2 +- 2 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/phar-old.yml diff --git a/.github/workflows/phar-old.yml b/.github/workflows/phar-old.yml new file mode 100644 index 0000000000..eb91eb3d60 --- /dev/null +++ b/.github/workflows/phar-old.yml @@ -0,0 +1,89 @@ +# https://help.github.com/en/categories/automating-your-workflow-with-github-actions + +name: "Compile PHAR" + +on: + push: + tags: + - '0.12.*' + +jobs: + compile: + name: "Compile PHAR" + runs-on: "ubuntu-latest" + timeout-minutes: 30 + + steps: + - name: "Checkout" + uses: "actions/checkout@v2" + with: + fetch-depth: 0 + + - name: "Install PHP" + uses: "shivammathur/setup-php@v2" + with: + coverage: "none" + php-version: "8.0" + + - name: "Install dependencies" + run: "composer install --no-interaction --no-progress --no-suggest" + + - name: "Install compiler dependencies" + run: "composer install --no-interaction --no-progress --no-suggest --working-dir=compiler" + + - name: "Transform source code" + run: php bin/transform-source.php + + - name: "Compile PHAR" + run: php compiler/bin/compile + + - name: "Configure GPG signing key" + run: echo "$GPG_SIGNING_KEY" | base64 --decode | gpg --import --no-tty --batch --yes + env: + GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }} + + - name: "Get Git log" + id: git-log + run: echo ::set-output name=log::$(git log ${{ github.event.before }}..${{ github.event.after }} --reverse --pretty='%H %s' | sed -e 's/^/https:\/\/github.com\/phpstan\/phpstan-src\/commit\//') + + - name: "Checkout phpstan-dist" + uses: "actions/checkout@v2" + with: + repository: phpstan/phpstan + ref: 0.12.x + path: phpstan-dist + token: ${{ secrets.PAT }} + + - name: "cp PHAR" + run: cp tmp/phpstan.phar phpstan-dist/phpstan.phar + + - name: "Sign PHAR" + working-directory: phpstan-dist + run: rm phpstan.phar.asc && gpg --command-fd 0 --pinentry-mode loopback -u "$GPG_ID" --batch --detach-sign --armor --output phpstan.phar.asc phpstan.phar + env: + GPG_ID: ${{ secrets.GPG_ID }} + + - name: "Verify PHAR" + working-directory: phpstan-dist + run: "gpg --verify phpstan.phar.asc" + + - name: "Set Git signing key" + working-directory: phpstan-dist + run: git config user.signingkey "$GPG_ID" + env: + GPG_ID: ${{ secrets.GPG_ID }} + + - name: "Configure Git" + working-directory: phpstan-dist + run: | + git config user.email "ondrej@mirtes.cz" && \ + git config user.name "Ondrej Mirtes" + + - name: "Commit PHAR - tag" + working-directory: phpstan-dist + run: | + git add phpstan.phar phpstan.phar.asc && \ + git commit -S -m "PHPStan ${GITHUB_REF#refs/tags/}" -m "${{ steps.git-log.outputs.log }}" && \ + git push --quiet origin 0.12.x && \ + git tag -s ${GITHUB_REF#refs/tags/} -m "${GITHUB_REF#refs/tags/}" && \ + git push --quiet origin ${GITHUB_REF#refs/tags/} diff --git a/.github/workflows/phar.yml b/.github/workflows/phar.yml index 685bcda26c..a0de139f4d 100644 --- a/.github/workflows/phar.yml +++ b/.github/workflows/phar.yml @@ -7,7 +7,7 @@ on: branches: - "master" tags: - - '*' + - '1.*' jobs: compile: From 36f197f1824327cb6be1cb1b12775f7180730d39 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 14 Sep 2021 15:14:21 +0200 Subject: [PATCH 0285/1284] Update PHPStan deps --- composer.lock | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/composer.lock b/composer.lock index 79060042fb..8052f368b4 100644 --- a/composer.lock +++ b/composer.lock @@ -4302,12 +4302,12 @@ "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-nette.git", - "reference": "b9fc63948d9e2f2e1c00681fd0734c8df7e40627" + "reference": "a265edd771369bbbaee0be78dded1c0a00972503" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-nette/zipball/b9fc63948d9e2f2e1c00681fd0734c8df7e40627", - "reference": "b9fc63948d9e2f2e1c00681fd0734c8df7e40627", + "url": "https://api.github.com/repos/phpstan/phpstan-nette/zipball/a265edd771369bbbaee0be78dded1c0a00972503", + "reference": "a265edd771369bbbaee0be78dded1c0a00972503", "shasum": "" }, "require": { @@ -4358,7 +4358,7 @@ "issues": "https://github.com/phpstan/phpstan-nette/issues", "source": "https://github.com/phpstan/phpstan-nette/tree/master" }, - "time": "2021-09-12T20:23:43+00:00" + "time": "2021-09-14T13:09:34+00:00" }, { "name": "phpstan/phpstan-php-parser", @@ -4366,12 +4366,12 @@ "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-php-parser.git", - "reference": "fd0b1ddaed99074bbd986b8493fd6391d59a86b2" + "reference": "b4fe8703df4841a59fd61571687e3bebe896cd6d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-php-parser/zipball/fd0b1ddaed99074bbd986b8493fd6391d59a86b2", - "reference": "fd0b1ddaed99074bbd986b8493fd6391d59a86b2", + "url": "https://api.github.com/repos/phpstan/phpstan-php-parser/zipball/b4fe8703df4841a59fd61571687e3bebe896cd6d", + "reference": "b4fe8703df4841a59fd61571687e3bebe896cd6d", "shasum": "" }, "require": { @@ -4410,7 +4410,7 @@ "issues": "https://github.com/phpstan/phpstan-php-parser/issues", "source": "https://github.com/phpstan/phpstan-php-parser/tree/master" }, - "time": "2021-09-12T20:24:59+00:00" + "time": "2021-09-13T11:29:18+00:00" }, { "name": "phpstan/phpstan-phpunit", @@ -4473,12 +4473,12 @@ "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "598568270074e0233fe01dc81fe7e88913f2689d" + "reference": "46cf4e8f72713a4c90cbf19c34205cba8cdde95e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/598568270074e0233fe01dc81fe7e88913f2689d", - "reference": "598568270074e0233fe01dc81fe7e88913f2689d", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/46cf4e8f72713a4c90cbf19c34205cba8cdde95e", + "reference": "46cf4e8f72713a4c90cbf19c34205cba8cdde95e", "shasum": "" }, "require": { @@ -4516,7 +4516,7 @@ "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", "source": "https://github.com/phpstan/phpstan-strict-rules/tree/master" }, - "time": "2021-09-12T21:46:22+00:00" + "time": "2021-09-13T18:59:45+00:00" }, { "name": "phpunit/php-code-coverage", From 7aabc848351689f18a1db99598df8d5b4944aa87 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 13 Sep 2021 11:11:07 +0200 Subject: [PATCH 0286/1284] [BCB] StaticType and ThisType - require ClassReflection in constructor --- src/Analyser/MutatingScope.php | 9 +- src/PhpDoc/TypeNodeResolver.php | 7 +- src/Type/ObjectType.php | 2 +- src/Type/ParserNodeTypeToPHPStanType.php | 7 +- src/Type/StaticType.php | 54 +++----- src/Type/ThisType.php | 24 +++- src/Type/TypehintHelper.php | 7 +- .../SignatureMap/SignatureMapParserTest.php | 3 +- .../Type/Constant/ConstantStringTypeTest.php | 7 +- .../Generic/GenericClassStringTypeTest.php | 18 ++- tests/PHPStan/Type/ObjectTypeTest.php | 26 ++-- tests/PHPStan/Type/StaticTypeTest.php | 128 +++++++++--------- tests/PHPStan/Type/TypeCombinatorTest.php | 10 +- tests/PHPStan/Type/UnionTypeTest.php | 6 +- 14 files changed, 161 insertions(+), 147 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 11f39716c0..51debd0376 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -2631,7 +2631,9 @@ public function resolveTypeByName(Name $name): TypeWithClassName { if ($name->toLowerString() === 'static' && $this->isInClass()) { if ($this->inClosureBindScopeClass !== null && $this->inClosureBindScopeClass !== 'static') { - return new StaticType($this->inClosureBindScopeClass); + if ($this->reflectionProvider->hasClass($this->inClosureBindScopeClass)) { + return new StaticType($this->reflectionProvider->getClass($this->inClosureBindScopeClass)); + } } return new StaticType($this->getClassReflection()); @@ -2640,7 +2642,10 @@ public function resolveTypeByName(Name $name): TypeWithClassName $originalClass = $this->resolveName($name); if ($this->isInClass()) { if ($this->inClosureBindScopeClass !== null && $this->inClosureBindScopeClass !== 'static') { - $thisType = new ThisType($this->inClosureBindScopeClass); + if ($this->reflectionProvider->hasClass($this->inClosureBindScopeClass)) { + return new ThisType($this->reflectionProvider->getClass($this->inClosureBindScopeClass)); + } + $thisType = new ThisType($this->getClassReflection()); } else { $thisType = new ThisType($this->getClassReflection()); } diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index 83d362090b..54fef4e139 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -302,8 +302,13 @@ private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameSco return new ObjectType($nameScope->getClassName()); case 'static': - return new StaticType($nameScope->getClassName()); + if ($this->getReflectionProvider()->hasClass($nameScope->getClassName())) { + $classReflection = $this->getReflectionProvider()->getClass($nameScope->getClassName()); + return new StaticType($classReflection); + } + + return new ErrorType(); case 'parent': if ($this->getReflectionProvider()->hasClass($nameScope->getClassName())) { $classReflection = $this->getReflectionProvider()->getClass($nameScope->getClassName()); diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 81baf4d731..f52093a9f9 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -200,7 +200,7 @@ public function getReferencedClasses(): array public function accepts(Type $type, bool $strictTypes): TrinaryLogic { if ($type instanceof StaticType) { - return $this->checkSubclassAcceptability($type->getBaseClass()); + return $this->checkSubclassAcceptability($type->getClassName()); } if ($type instanceof CompoundType) { diff --git a/src/Type/ParserNodeTypeToPHPStanType.php b/src/Type/ParserNodeTypeToPHPStanType.php index 00d3baf951..2bc1d2978f 100644 --- a/src/Type/ParserNodeTypeToPHPStanType.php +++ b/src/Type/ParserNodeTypeToPHPStanType.php @@ -23,6 +23,9 @@ public static function resolve($type, ?ClassReflection $classReflection): Type $typeClassName = (string) $type; $lowercasedClassName = strtolower($typeClassName); if ($classReflection !== null && in_array($lowercasedClassName, ['self', 'static'], true)) { + if ($lowercasedClassName === 'static') { + return new StaticType($classReflection); + } $typeClassName = $classReflection->getName(); } elseif ( $lowercasedClassName === 'parent' @@ -32,10 +35,6 @@ public static function resolve($type, ?ClassReflection $classReflection): Type $typeClassName = $classReflection->getParentClass()->getName(); } - if ($lowercasedClassName === 'static') { - return new StaticType($typeClassName); - } - return new ObjectType($typeClassName); } elseif ($type instanceof NullableType) { return TypeCombinator::addNull(self::resolve($type->type, $classReflection)); diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php index 8326b3a79e..e97f20fe1f 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -25,7 +25,7 @@ class StaticType implements TypeWithClassName use NonGenericTypeTrait; use UndecidedComparisonTypeTrait; - private ?ClassReflection $classReflection; + private ClassReflection $classReflection; private ?\PHPStan\Type\ObjectType $staticObjectType = null; @@ -33,24 +33,9 @@ class StaticType implements TypeWithClassName /** * @api - * @param string|ClassReflection $classReflection */ - public function __construct($classReflection) - { - if (is_string($classReflection)) { - $broker = Broker::getInstance(); - if ($broker->hasClass($classReflection)) { - $classReflection = $broker->getClass($classReflection); - $this->classReflection = $classReflection; - $this->baseClass = $classReflection->getName(); - return; - } - - $this->classReflection = null; - $this->baseClass = $classReflection; - return; - } - + public function __construct(ClassReflection $classReflection) + { $this->classReflection = $classReflection; $this->baseClass = $classReflection->getName(); } @@ -72,13 +57,18 @@ public function getAncestorWithClassName(string $className): ?TypeWithClassName return null; } - return $this->changeBaseClass($ancestor->getClassReflection() ?? $ancestor->getClassName()); + $classReflection = $ancestor->getClassReflection(); + if ($classReflection !== null) { + return $this->changeBaseClass($classReflection); + } + + return null; } public function getStaticObjectType(): ObjectType { if ($this->staticObjectType === null) { - if ($this->classReflection !== null && $this->classReflection->isGeneric()) { + if ($this->classReflection->isGeneric()) { $typeMap = $this->classReflection->getActiveTemplateTypeMap()->map(static function (string $name, Type $type): Type { return TemplateTypeHelper::toArgument($type); }); @@ -88,7 +78,7 @@ public function getStaticObjectType(): ObjectType ); } - return $this->staticObjectType = new ObjectType($this->baseClass, null, $this->classReflection); + return $this->staticObjectType = new ObjectType($this->classReflection->getName(), null, $this->classReflection); } return $this->staticObjectType; @@ -102,11 +92,6 @@ public function getReferencedClasses(): array return $this->getStaticObjectType()->getReferencedClasses(); } - public function getBaseClass(): string - { - return $this->baseClass; - } - public function accepts(Type $type, bool $strictTypes): TrinaryLogic { if ($type instanceof CompoundType) { @@ -247,9 +232,7 @@ private function transformStaticType(Type $type, ClassMemberAccessAnswerer $scop if ($type instanceof StaticType) { $classReflection = $this->classReflection; $isFinal = false; - if ($classReflection === null) { - $classReflection = $this->baseClass; - } elseif ($scope->isInClass()) { + if ($scope->isInClass()) { $classReflection = $scope->getClassReflection(); $isFinal = $classReflection->isFinal(); } @@ -280,11 +263,7 @@ public function getConstant(string $constantName): ConstantReflection return $this->getStaticObjectType()->getConstant($constantName); } - /** - * @param ClassReflection|string $classReflection - * @return self - */ - public function changeBaseClass($classReflection): self + public function changeBaseClass(ClassReflection $classReflection): self { return new self($classReflection); } @@ -409,7 +388,12 @@ public function traverse(callable $cb): Type */ public static function __set_state(array $properties): Type { - return new self($properties['baseClass']); + $broker = Broker::getInstance(); + if ($broker->hasClass($properties['baseClass'])) { + return new self($broker->getClass($properties['baseClass'])); + } + + return new ErrorType(); } } diff --git a/src/Type/ThisType.php b/src/Type/ThisType.php index 2d348f2abe..2d6d1e5adc 100644 --- a/src/Type/ThisType.php +++ b/src/Type/ThisType.php @@ -2,6 +2,7 @@ namespace PHPStan\Type; +use PHPStan\Broker\Broker; use PHPStan\Reflection\ClassReflection; /** @api */ @@ -10,18 +11,13 @@ class ThisType extends StaticType /** * @api - * @param string|ClassReflection $classReflection */ - public function __construct($classReflection) + public function __construct(ClassReflection $classReflection) { parent::__construct($classReflection); } - /** - * @param ClassReflection|string $classReflection - * @return self - */ - public function changeBaseClass($classReflection): StaticType + public function changeBaseClass(ClassReflection $classReflection): StaticType { return new self($classReflection); } @@ -31,4 +27,18 @@ public function describe(VerbosityLevel $level): string return sprintf('$this(%s)', $this->getClassName()); } + /** + * @param mixed[] $properties + * @return Type + */ + public static function __set_state(array $properties): Type + { + $broker = Broker::getInstance(); + if ($broker->hasClass($properties['baseClass'])) { + return new self($broker->getClass($properties['baseClass'])); + } + + return new ErrorType(); + } + } diff --git a/src/Type/TypehintHelper.php b/src/Type/TypehintHelper.php index abffa0ae92..5fe0ed14fa 100644 --- a/src/Type/TypehintHelper.php +++ b/src/Type/TypehintHelper.php @@ -49,7 +49,12 @@ private static function getTypeObjectFromTypehint(string $typeString, ?string $s } return new NonexistentParentClassType(); case 'static': - return $selfClass !== null ? new StaticType($selfClass) : new ErrorType(); + $broker = Broker::getInstance(); + if ($selfClass !== null && $broker->hasClass($selfClass)) { + return new StaticType($broker->getClass($selfClass)); + } + + return new ErrorType(); case 'null': return new NullType(); default: diff --git a/tests/PHPStan/Reflection/SignatureMap/SignatureMapParserTest.php b/tests/PHPStan/Reflection/SignatureMap/SignatureMapParserTest.php index d191e7c11a..78d73a9f57 100644 --- a/tests/PHPStan/Reflection/SignatureMap/SignatureMapParserTest.php +++ b/tests/PHPStan/Reflection/SignatureMap/SignatureMapParserTest.php @@ -23,6 +23,7 @@ class SignatureMapParserTest extends \PHPStan\Testing\PHPStanTestCase public function dataGetFunctions(): array { + $reflectionProvider = $this->createReflectionProvider(); return [ [ ['int', 'fp' => 'resource', 'fields' => 'array', 'delimiter=' => 'string', 'enclosure=' => 'string', 'escape_char=' => 'string'], @@ -323,7 +324,7 @@ public function dataGetFunctions(): array false ), ], - new StaticType(\DateTime::class), + new StaticType($reflectionProvider->getClass(\DateTime::class)), new MixedType(), false ), diff --git a/tests/PHPStan/Type/Constant/ConstantStringTypeTest.php b/tests/PHPStan/Type/Constant/ConstantStringTypeTest.php index 806148573c..a3ecf2825a 100644 --- a/tests/PHPStan/Type/Constant/ConstantStringTypeTest.php +++ b/tests/PHPStan/Type/Constant/ConstantStringTypeTest.php @@ -19,6 +19,7 @@ class ConstantStringTypeTest extends PHPStanTestCase public function dataIsSuperTypeOf(): array { + $reflectionProvider = $this->createReflectionProvider(); return [ 0 => [ new ConstantStringType(\Exception::class), @@ -112,17 +113,17 @@ public function dataIsSuperTypeOf(): array ], 13 => [ new ConstantStringType(\Exception::class), - new GenericClassStringType(new StaticType(\Exception::class)), + new GenericClassStringType(new StaticType($reflectionProvider->getClass(\Exception::class))), TrinaryLogic::createMaybe(), ], 14 => [ new ConstantStringType(\Exception::class), - new GenericClassStringType(new StaticType(\InvalidArgumentException::class)), + new GenericClassStringType(new StaticType($reflectionProvider->getClass(\InvalidArgumentException::class))), TrinaryLogic::createNo(), ], 15 => [ new ConstantStringType(\Exception::class), - new GenericClassStringType(new StaticType(\Throwable::class)), + new GenericClassStringType(new StaticType($reflectionProvider->getClass(\Throwable::class))), TrinaryLogic::createMaybe(), ], ]; diff --git a/tests/PHPStan/Type/Generic/GenericClassStringTypeTest.php b/tests/PHPStan/Type/Generic/GenericClassStringTypeTest.php index a281a78c70..5472a605e9 100644 --- a/tests/PHPStan/Type/Generic/GenericClassStringTypeTest.php +++ b/tests/PHPStan/Type/Generic/GenericClassStringTypeTest.php @@ -20,6 +20,8 @@ class GenericClassStringTypeTest extends \PHPStan\Testing\PHPStanTestCase public function dataIsSuperTypeOf(): array { + $reflectionProvider = $this->createReflectionProvider(); + return [ 0 => [ new GenericClassStringType(new ObjectType(\Exception::class)), @@ -122,17 +124,17 @@ public function dataIsSuperTypeOf(): array TrinaryLogic::createNo(), ], 15 => [ - new GenericClassStringType(new StaticType(\Exception::class)), + new GenericClassStringType(new StaticType($reflectionProvider->getClass(\Exception::class))), new ConstantStringType(\Exception::class), TrinaryLogic::createYes(), ], 16 => [ - new GenericClassStringType(new StaticType(\InvalidArgumentException::class)), + new GenericClassStringType(new StaticType($reflectionProvider->getClass(\InvalidArgumentException::class))), new ConstantStringType(\Exception::class), TrinaryLogic::createNo(), ], 17 => [ - new GenericClassStringType(new StaticType(\Throwable::class)), + new GenericClassStringType(new StaticType($reflectionProvider->getClass(\Throwable::class))), new ConstantStringType(\Exception::class), TrinaryLogic::createYes(), ], @@ -275,6 +277,8 @@ public function testAccepts( public function dataEquals(): array { + $reflectionProvider = $this->createReflectionProvider(); + return [ [ new GenericClassStringType(new ObjectType(\Exception::class)), @@ -287,13 +291,13 @@ public function dataEquals(): array false, ], [ - new GenericClassStringType(new StaticType(\Exception::class)), - new GenericClassStringType(new StaticType(\Exception::class)), + new GenericClassStringType(new StaticType($reflectionProvider->getClass(\Exception::class))), + new GenericClassStringType(new StaticType($reflectionProvider->getClass(\Exception::class))), true, ], [ - new GenericClassStringType(new StaticType(\Exception::class)), - new GenericClassStringType(new StaticType(\stdClass::class)), + new GenericClassStringType(new StaticType($reflectionProvider->getClass(\Exception::class))), + new GenericClassStringType(new StaticType($reflectionProvider->getClass(\stdClass::class))), false, ], ]; diff --git a/tests/PHPStan/Type/ObjectTypeTest.php b/tests/PHPStan/Type/ObjectTypeTest.php index 7ce6f37a13..fdf99391da 100644 --- a/tests/PHPStan/Type/ObjectTypeTest.php +++ b/tests/PHPStan/Type/ObjectTypeTest.php @@ -65,6 +65,8 @@ public function testIsCallable(ObjectType $type, TrinaryLogic $expectedResult): public function dataIsSuperTypeOf(): array { + $reflectionProvider = $this->createReflectionProvider(); + return [ 0 => [ new ObjectType('UnknownClassA'), @@ -139,48 +141,48 @@ public function dataIsSuperTypeOf(): array ], 13 => [ new ObjectType(\ArrayAccess::class), - new StaticType(\Traversable::class), + new StaticType($reflectionProvider->getClass(\Traversable::class)), TrinaryLogic::createMaybe(), ], 14 => [ new ObjectType(\Countable::class), - new StaticType(\Countable::class), + new StaticType($reflectionProvider->getClass(\Countable::class)), TrinaryLogic::createYes(), ], 15 => [ new ObjectType(\DateTimeImmutable::class), - new StaticType(\DateTimeImmutable::class), + new StaticType($reflectionProvider->getClass(\DateTimeImmutable::class)), TrinaryLogic::createYes(), ], 16 => [ new ObjectType(\Traversable::class), - new StaticType(\ArrayObject::class), + new StaticType($reflectionProvider->getClass(\ArrayObject::class)), TrinaryLogic::createYes(), ], 17 => [ new ObjectType(\Traversable::class), - new StaticType(\Iterator::class), + new StaticType($reflectionProvider->getClass(\Iterator::class)), TrinaryLogic::createYes(), ], 18 => [ new ObjectType(\ArrayObject::class), - new StaticType(\Traversable::class), + new StaticType($reflectionProvider->getClass(\Traversable::class)), TrinaryLogic::createMaybe(), ], 19 => [ new ObjectType(\Iterator::class), - new StaticType(\Traversable::class), + new StaticType($reflectionProvider->getClass(\Traversable::class)), TrinaryLogic::createMaybe(), ], 20 => [ new ObjectType(\ArrayObject::class), - new StaticType(\DateTimeImmutable::class), + new StaticType($reflectionProvider->getClass(\DateTimeImmutable::class)), TrinaryLogic::createNo(), ], 21 => [ new ObjectType(\DateTimeImmutable::class), new UnionType([ - new StaticType(\DateTimeImmutable::class), + new StaticType($reflectionProvider->getClass(\DateTimeImmutable::class)), new StringType(), ]), TrinaryLogic::createMaybe(), @@ -188,19 +190,19 @@ public function dataIsSuperTypeOf(): array 22 => [ new ObjectType(\DateTimeImmutable::class), new UnionType([ - new StaticType(\ArrayObject::class), + new StaticType($reflectionProvider->getClass(\ArrayObject::class)), new StringType(), ]), TrinaryLogic::createNo(), ], 23 => [ new ObjectType(\LogicException::class), - new StaticType(\InvalidArgumentException::class), + new StaticType($reflectionProvider->getClass(\InvalidArgumentException::class)), TrinaryLogic::createYes(), ], 24 => [ new ObjectType(\InvalidArgumentException::class), - new StaticType(\LogicException::class), + new StaticType($reflectionProvider->getClass(\LogicException::class)), TrinaryLogic::createMaybe(), ], 25 => [ diff --git a/tests/PHPStan/Type/StaticTypeTest.php b/tests/PHPStan/Type/StaticTypeTest.php index bf64cf800b..655c40052b 100644 --- a/tests/PHPStan/Type/StaticTypeTest.php +++ b/tests/PHPStan/Type/StaticTypeTest.php @@ -2,7 +2,6 @@ namespace PHPStan\Type; -use PHPStan\Broker\Broker; use PHPStan\TrinaryLogic; use StaticTypeTest\Base; use StaticTypeTest\Child; @@ -13,11 +12,12 @@ class StaticTypeTest extends \PHPStan\Testing\PHPStanTestCase public function dataIsIterable(): array { + $reflectionProvider = $this->createReflectionProvider(); + return [ - [new StaticType('ArrayObject'), TrinaryLogic::createYes()], - [new StaticType('Traversable'), TrinaryLogic::createYes()], - [new StaticType('Unknown'), TrinaryLogic::createMaybe()], - [new StaticType('DateTime'), TrinaryLogic::createNo()], + [new StaticType($reflectionProvider->getClass('ArrayObject')), TrinaryLogic::createYes()], + [new StaticType($reflectionProvider->getClass('Traversable')), TrinaryLogic::createYes()], + [new StaticType($reflectionProvider->getClass('DateTime')), TrinaryLogic::createNo()], ]; } @@ -38,10 +38,11 @@ public function testIsIterable(StaticType $type, TrinaryLogic $expectedResult): public function dataIsCallable(): array { + $reflectionProvider = $this->createReflectionProvider(); + return [ - [new StaticType('Closure'), TrinaryLogic::createYes()], - [new StaticType('Unknown'), TrinaryLogic::createMaybe()], - [new StaticType('DateTime'), TrinaryLogic::createMaybe()], + [new StaticType($reflectionProvider->getClass('Closure')), TrinaryLogic::createYes()], + [new StaticType($reflectionProvider->getClass('DateTime')), TrinaryLogic::createMaybe()], ]; } @@ -62,55 +63,50 @@ public function testIsCallable(StaticType $type, TrinaryLogic $expectedResult): public function dataIsSuperTypeOf(): array { - $broker = $this->createBroker(); + $reflectionProvider = $this->createReflectionProvider(); return [ - 0 => [ - new StaticType('UnknownClassA'), - new ObjectType('UnknownClassB'), - TrinaryLogic::createMaybe(), - ], 1 => [ - new StaticType(\ArrayAccess::class), + new StaticType($reflectionProvider->getClass(\ArrayAccess::class)), new ObjectType(\Traversable::class), TrinaryLogic::createMaybe(), ], 2 => [ - new StaticType(\Countable::class), + new StaticType($reflectionProvider->getClass(\Countable::class)), new ObjectType(\Countable::class), TrinaryLogic::createMaybe(), ], 3 => [ - new StaticType(\DateTimeImmutable::class), + new StaticType($reflectionProvider->getClass(\DateTimeImmutable::class)), new ObjectType(\DateTimeImmutable::class), TrinaryLogic::createMaybe(), ], 4 => [ - new StaticType(\Traversable::class), + new StaticType($reflectionProvider->getClass(\Traversable::class)), new ObjectType(\ArrayObject::class), TrinaryLogic::createMaybe(), ], 5 => [ - new StaticType(\Traversable::class), + new StaticType($reflectionProvider->getClass(\Traversable::class)), new ObjectType(\Iterator::class), TrinaryLogic::createMaybe(), ], 6 => [ - new StaticType(\ArrayObject::class), + new StaticType($reflectionProvider->getClass(\ArrayObject::class)), new ObjectType(\Traversable::class), TrinaryLogic::createMaybe(), ], 7 => [ - new StaticType(\Iterator::class), + new StaticType($reflectionProvider->getClass(\Iterator::class)), new ObjectType(\Traversable::class), TrinaryLogic::createMaybe(), ], 8 => [ - new StaticType(\ArrayObject::class), + new StaticType($reflectionProvider->getClass(\ArrayObject::class)), new ObjectType(\DateTimeImmutable::class), TrinaryLogic::createNo(), ], 9 => [ - new StaticType(\DateTimeImmutable::class), + new StaticType($reflectionProvider->getClass(\DateTimeImmutable::class)), new UnionType([ new ObjectType(\DateTimeImmutable::class), new StringType(), @@ -118,7 +114,7 @@ public function dataIsSuperTypeOf(): array TrinaryLogic::createMaybe(), ], 10 => [ - new StaticType(\DateTimeImmutable::class), + new StaticType($reflectionProvider->getClass(\DateTimeImmutable::class)), new UnionType([ new ObjectType(\ArrayObject::class), new StringType(), @@ -126,126 +122,126 @@ public function dataIsSuperTypeOf(): array TrinaryLogic::createNo(), ], 11 => [ - new StaticType(\LogicException::class), + new StaticType($reflectionProvider->getClass(\LogicException::class)), new ObjectType(\InvalidArgumentException::class), TrinaryLogic::createMaybe(), ], 12 => [ - new StaticType(\InvalidArgumentException::class), + new StaticType($reflectionProvider->getClass(\InvalidArgumentException::class)), new ObjectType(\LogicException::class), TrinaryLogic::createMaybe(), ], 13 => [ - new StaticType(\ArrayAccess::class), - new StaticType(\Traversable::class), + new StaticType($reflectionProvider->getClass(\ArrayAccess::class)), + new StaticType($reflectionProvider->getClass(\Traversable::class)), TrinaryLogic::createMaybe(), ], 14 => [ - new StaticType(\Countable::class), - new StaticType(\Countable::class), + new StaticType($reflectionProvider->getClass(\Countable::class)), + new StaticType($reflectionProvider->getClass(\Countable::class)), TrinaryLogic::createYes(), ], 15 => [ - new StaticType(\DateTimeImmutable::class), - new StaticType(\DateTimeImmutable::class), + new StaticType($reflectionProvider->getClass(\DateTimeImmutable::class)), + new StaticType($reflectionProvider->getClass(\DateTimeImmutable::class)), TrinaryLogic::createYes(), ], 16 => [ - new StaticType(\Traversable::class), - new StaticType(\ArrayObject::class), + new StaticType($reflectionProvider->getClass(\Traversable::class)), + new StaticType($reflectionProvider->getClass(\ArrayObject::class)), TrinaryLogic::createYes(), ], 17 => [ - new StaticType(\Traversable::class), - new StaticType(\Iterator::class), + new StaticType($reflectionProvider->getClass(\Traversable::class)), + new StaticType($reflectionProvider->getClass(\Iterator::class)), TrinaryLogic::createYes(), ], 18 => [ - new StaticType(\ArrayObject::class), - new StaticType(\Traversable::class), + new StaticType($reflectionProvider->getClass(\ArrayObject::class)), + new StaticType($reflectionProvider->getClass(\Traversable::class)), TrinaryLogic::createMaybe(), ], 19 => [ - new StaticType(\Iterator::class), - new StaticType(\Traversable::class), + new StaticType($reflectionProvider->getClass(\Iterator::class)), + new StaticType($reflectionProvider->getClass(\Traversable::class)), TrinaryLogic::createMaybe(), ], 20 => [ - new StaticType(\ArrayObject::class), - new StaticType(\DateTimeImmutable::class), + new StaticType($reflectionProvider->getClass(\ArrayObject::class)), + new StaticType($reflectionProvider->getClass(\DateTimeImmutable::class)), TrinaryLogic::createNo(), ], 21 => [ - new StaticType(\DateTimeImmutable::class), + new StaticType($reflectionProvider->getClass(\DateTimeImmutable::class)), new UnionType([ - new StaticType(\DateTimeImmutable::class), - new StaticType(\DateTimeImmutable::class), + new StaticType($reflectionProvider->getClass(\DateTimeImmutable::class)), + new StaticType($reflectionProvider->getClass(\DateTimeImmutable::class)), ]), TrinaryLogic::createYes(), ], 22 => [ - new StaticType(\DateTimeImmutable::class), + new StaticType($reflectionProvider->getClass(\DateTimeImmutable::class)), new UnionType([ - new StaticType(\DateTimeImmutable::class), + new StaticType($reflectionProvider->getClass(\DateTimeImmutable::class)), new StringType(), ]), TrinaryLogic::createMaybe(), ], 23 => [ - new StaticType(\DateTimeImmutable::class), + new StaticType($reflectionProvider->getClass(\DateTimeImmutable::class)), new UnionType([ - new StaticType(\ArrayObject::class), + new StaticType($reflectionProvider->getClass(\ArrayObject::class)), new StringType(), ]), TrinaryLogic::createNo(), ], 24 => [ - new StaticType(\LogicException::class), - new StaticType(\InvalidArgumentException::class), + new StaticType($reflectionProvider->getClass(\LogicException::class)), + new StaticType($reflectionProvider->getClass(\InvalidArgumentException::class)), TrinaryLogic::createYes(), ], 25 => [ - new StaticType(\InvalidArgumentException::class), - new StaticType(\LogicException::class), + new StaticType($reflectionProvider->getClass(\InvalidArgumentException::class)), + new StaticType($reflectionProvider->getClass(\LogicException::class)), TrinaryLogic::createMaybe(), ], 26 => [ - new StaticType(\stdClass::class), + new StaticType($reflectionProvider->getClass(\stdClass::class)), new ObjectWithoutClassType(), TrinaryLogic::createMaybe(), ], 27 => [ new ObjectWithoutClassType(), - new StaticType(\stdClass::class), + new StaticType($reflectionProvider->getClass(\stdClass::class)), TrinaryLogic::createYes(), ], 28 => [ - new ThisType($broker->getClass(\stdClass::class)), + new ThisType($reflectionProvider->getClass(\stdClass::class)), new ObjectWithoutClassType(), TrinaryLogic::createMaybe(), ], 29 => [ new ObjectWithoutClassType(), - new ThisType($broker->getClass(\stdClass::class)), + new ThisType($reflectionProvider->getClass(\stdClass::class)), TrinaryLogic::createYes(), ], [ - new StaticType(Base::class), + new StaticType($reflectionProvider->getClass(Base::class)), new ObjectType(Child::class), TrinaryLogic::createMaybe(), ], [ - new StaticType(Base::class), - new StaticType(FinalChild::class), + new StaticType($reflectionProvider->getClass(Base::class)), + new StaticType($reflectionProvider->getClass(FinalChild::class)), TrinaryLogic::createYes(), ], [ - new StaticType(Base::class), - new StaticType(Child::class), + new StaticType($reflectionProvider->getClass(Base::class)), + new StaticType($reflectionProvider->getClass(Child::class)), TrinaryLogic::createYes(), ], [ - new StaticType(Base::class), + new StaticType($reflectionProvider->getClass(Base::class)), new ObjectType(FinalChild::class), TrinaryLogic::createYes(), ], @@ -270,7 +266,7 @@ public function testIsSuperTypeOf(Type $type, Type $otherType, TrinaryLogic $exp public function dataEquals(): array { - $reflectionProvider = Broker::getInstance(); + $reflectionProvider = $this->createReflectionProvider(); return [ [ @@ -285,12 +281,12 @@ public function dataEquals(): array ], [ new ThisType($reflectionProvider->getClass(\Exception::class)), - new StaticType(\Exception::class), + new StaticType($reflectionProvider->getClass(\Exception::class)), false, ], [ new ThisType($reflectionProvider->getClass(\Exception::class)), - new StaticType(\InvalidArgumentException::class), + new StaticType($reflectionProvider->getClass(\InvalidArgumentException::class)), false, ], ]; diff --git a/tests/PHPStan/Type/TypeCombinatorTest.php b/tests/PHPStan/Type/TypeCombinatorTest.php index f6c4160113..61fb1db976 100644 --- a/tests/PHPStan/Type/TypeCombinatorTest.php +++ b/tests/PHPStan/Type/TypeCombinatorTest.php @@ -2,7 +2,6 @@ namespace PHPStan\Type; -use PHPStan\Broker\Broker; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\AccessoryNumericStringType; use PHPStan\Type\Accessory\HasMethodType; @@ -125,7 +124,8 @@ public function testUnionWithNull( public function dataRemoveNull(): array { - $reflectionProvider = Broker::getInstance(); + $reflectionProvider = $this->createReflectionProvider(); + return [ [ new MixedType(), @@ -193,7 +193,7 @@ public function dataRemoveNull(): array ], [ new UnionType([ - new ThisType(\Exception::class), + new ThisType($reflectionProvider->getClass(\Exception::class)), new NullType(), ]), ThisType::class, @@ -1911,6 +1911,8 @@ static function (Type $type): string { public function dataIntersect(): array { + $reflectionProvider = $this->createReflectionProvider(); + return [ [ [ @@ -1955,7 +1957,7 @@ public function dataIntersect(): array [ [ new ObjectType('Foo'), - new StaticType('Foo'), + new StaticType($reflectionProvider->getClass('Foo')), ], StaticType::class, 'static(Foo)', diff --git a/tests/PHPStan/Type/UnionTypeTest.php b/tests/PHPStan/Type/UnionTypeTest.php index 78233edb32..2226b18e20 100644 --- a/tests/PHPStan/Type/UnionTypeTest.php +++ b/tests/PHPStan/Type/UnionTypeTest.php @@ -78,7 +78,7 @@ public function testIsCallable(UnionType $type, TrinaryLogic $expectedResult): v public function dataSelfCompare(): \Iterator { - $broker = $this->createBroker(); + $reflectionProvider = $this->createReflectionProvider(); $integerType = new IntegerType(); $stringType = new StringType(); @@ -123,14 +123,14 @@ public function dataSelfCompare(): \Iterator yield [new ObjectType('Foo')]; yield [new ObjectWithoutClassType(new ObjectType('Foo'))]; yield [new ResourceType()]; - yield [new StaticType('Foo')]; + yield [new StaticType($reflectionProvider->getClass('Foo'))]; yield [new StrictMixedType()]; yield [new StringAlwaysAcceptingObjectWithToStringType()]; yield [$stringType]; yield [TemplateTypeFactory::create($templateTypeScope, 'T', null, TemplateTypeVariance::createInvariant())]; yield [TemplateTypeFactory::create($templateTypeScope, 'T', new ObjectType('Foo'), TemplateTypeVariance::createInvariant())]; yield [TemplateTypeFactory::create($templateTypeScope, 'T', new ObjectWithoutClassType(), TemplateTypeVariance::createInvariant())]; - yield [new ThisType($broker->getClass('Foo'))]; + yield [new ThisType($reflectionProvider->getClass('Foo'))]; yield [new UnionType([$integerType, $stringType])]; yield [new VoidType()]; } From f2f79f99f7094a40763fb77cbcc6e4d914ace244 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 14 Sep 2021 15:38:01 +0200 Subject: [PATCH 0287/1284] [BCB] PHPStanTestCase::getBroker() returns ReflectionProvider --- src/Broker/Broker.php | 6 +- src/Testing/PHPStanTestCase.php | 55 +++++++++--------- src/Testing/TypeInferenceTestCase.php | 18 ++---- .../Analyser/LegacyNodeScopeResolverTest.php | 56 ------------------- 4 files changed, 34 insertions(+), 101 deletions(-) diff --git a/src/Broker/Broker.php b/src/Broker/Broker.php index 4422e22e05..86dc087974 100644 --- a/src/Broker/Broker.php +++ b/src/Broker/Broker.php @@ -25,7 +25,7 @@ class Broker implements ReflectionProvider /** @var string[] */ private array $universalObjectCratesClasses; - private static ?\PHPStan\Broker\Broker $instance = null; + private static ?Broker $instance = null; /** * @param \PHPStan\Reflection\ReflectionProvider $reflectionProvider @@ -46,9 +46,9 @@ public function __construct( $this->universalObjectCratesClasses = $universalObjectCratesClasses; } - public static function registerInstance(Broker $reflectionProvider): void + public static function registerInstance(Broker $broker): void { - self::$instance = $reflectionProvider; + self::$instance = $broker; } public static function getInstance(): Broker diff --git a/src/Testing/PHPStanTestCase.php b/src/Testing/PHPStanTestCase.php index 22ab3fa362..3f65cdc23f 100644 --- a/src/Testing/PHPStanTestCase.php +++ b/src/Testing/PHPStanTestCase.php @@ -30,6 +30,8 @@ use PHPStan\DependencyInjection\Reflection\DirectClassReflectionExtensionRegistryProvider; use PHPStan\DependencyInjection\Type\DirectDynamicReturnTypeExtensionRegistryProvider; use PHPStan\DependencyInjection\Type\DirectOperatorTypeSpecifyingExtensionRegistryProvider; +use PHPStan\DependencyInjection\Type\DynamicReturnTypeExtensionRegistryProvider; +use PHPStan\DependencyInjection\Type\OperatorTypeSpecifyingExtensionRegistryProvider; use PHPStan\File\FileHelper; use PHPStan\File\SimpleRelativePathHelper; use PHPStan\Parser\CachedParser; @@ -143,24 +145,34 @@ public function getParser(): \PHPStan\Parser\Parser /** * @api - * @param \PHPStan\Type\DynamicMethodReturnTypeExtension[] $dynamicMethodReturnTypeExtensions - * @param \PHPStan\Type\DynamicStaticMethodReturnTypeExtension[] $dynamicStaticMethodReturnTypeExtensions - * @return \PHPStan\Broker\Broker + * @return \PHPStan\Reflection\ReflectionProvider */ - public function createBroker( - array $dynamicMethodReturnTypeExtensions = [], - array $dynamicStaticMethodReturnTypeExtensions = [] - ): Broker + public function createBroker(): ReflectionProvider { + return $this->createReflectionProvider(); + } + + /** @api */ + public function createReflectionProvider(): ReflectionProvider + { + $setterReflectionProviderProvider = new ReflectionProvider\SetterReflectionProviderProvider(); + $staticReflectionProvider = $this->createStaticReflectionProvider($setterReflectionProviderProvider); + $reflectionProvider = $this->createReflectionProviderByParameters( + $this->createRuntimeReflectionProvider($setterReflectionProviderProvider), + $staticReflectionProvider, + self::$useStaticReflectionProvider + ); + $setterReflectionProviderProvider->setReflectionProvider($reflectionProvider); + $dynamicReturnTypeExtensionRegistryProvider = new DirectDynamicReturnTypeExtensionRegistryProvider( - array_merge(self::getContainer()->getServicesByTag(BrokerFactory::DYNAMIC_METHOD_RETURN_TYPE_EXTENSION_TAG), $dynamicMethodReturnTypeExtensions, $this->getDynamicMethodReturnTypeExtensions()), - array_merge(self::getContainer()->getServicesByTag(BrokerFactory::DYNAMIC_STATIC_METHOD_RETURN_TYPE_EXTENSION_TAG), $dynamicStaticMethodReturnTypeExtensions, $this->getDynamicStaticMethodReturnTypeExtensions()), + array_merge(self::getContainer()->getServicesByTag(BrokerFactory::DYNAMIC_METHOD_RETURN_TYPE_EXTENSION_TAG), $this->getDynamicMethodReturnTypeExtensions()), + array_merge(self::getContainer()->getServicesByTag(BrokerFactory::DYNAMIC_STATIC_METHOD_RETURN_TYPE_EXTENSION_TAG), $this->getDynamicStaticMethodReturnTypeExtensions()), array_merge(self::getContainer()->getServicesByTag(BrokerFactory::DYNAMIC_FUNCTION_RETURN_TYPE_EXTENSION_TAG), $this->getDynamicFunctionReturnTypeExtensions()) ); $operatorTypeSpecifyingExtensionRegistryProvider = new DirectOperatorTypeSpecifyingExtensionRegistryProvider( $this->getOperatorTypeSpecifyingExtensions() ); - $reflectionProvider = $this->createReflectionProvider(); + $broker = new Broker( $reflectionProvider, $dynamicReturnTypeExtensionRegistryProvider, @@ -172,21 +184,6 @@ public function createBroker( $operatorTypeSpecifyingExtensionRegistryProvider->setBroker($broker); $this->getClassReflectionExtensionRegistryProvider()->setBroker($broker); - return $broker; - } - - /** @api */ - public function createReflectionProvider(): ReflectionProvider - { - $setterReflectionProviderProvider = new ReflectionProvider\SetterReflectionProviderProvider(); - $staticReflectionProvider = $this->createStaticReflectionProvider($setterReflectionProviderProvider); - $reflectionProvider = $this->createReflectionProviderByParameters( - $this->createRuntimeReflectionProvider($setterReflectionProviderProvider), - $staticReflectionProvider, - self::$useStaticReflectionProvider - ); - $setterReflectionProviderProvider->setReflectionProvider($reflectionProvider); - return $reflectionProvider; } @@ -563,15 +560,15 @@ public function getClassReflectionExtensionRegistryProvider(): DirectClassReflec return $this->classReflectionExtensionRegistryProvider; } - public function createScopeFactory(Broker $broker, TypeSpecifier $typeSpecifier): ScopeFactory + public function createScopeFactory(ReflectionProvider $reflectionProvider, TypeSpecifier $typeSpecifier): ScopeFactory { $container = self::getContainer(); return new DirectScopeFactory( MutatingScope::class, - $broker, - $broker->getDynamicReturnTypeExtensionRegistryProvider(), - $broker->getOperatorTypeSpecifyingExtensionRegistryProvider(), + $reflectionProvider, + $container->getByType(DynamicReturnTypeExtensionRegistryProvider::class), + $container->getByType(OperatorTypeSpecifyingExtensionRegistryProvider::class), new \PhpParser\PrettyPrinter\Standard(), $typeSpecifier, new PropertyReflectionFinder(), diff --git a/src/Testing/TypeInferenceTestCase.php b/src/Testing/TypeInferenceTestCase.php index 8225650da6..30bcdcbdb9 100644 --- a/src/Testing/TypeInferenceTestCase.php +++ b/src/Testing/TypeInferenceTestCase.php @@ -10,7 +10,6 @@ use PHPStan\Analyser\Scope; use PHPStan\Analyser\ScopeContext; use PHPStan\Broker\AnonymousClassNameHelper; -use PHPStan\Broker\Broker; use PHPStan\Cache\Cache; use PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider; use PHPStan\File\FileHelper; @@ -22,8 +21,6 @@ use PHPStan\PhpDoc\StubPhpDocProvider; use PHPStan\Reflection\ReflectionProvider\DirectReflectionProviderProvider; use PHPStan\TrinaryLogic; -use PHPStan\Type\DynamicMethodReturnTypeExtension; -use PHPStan\Type\DynamicStaticMethodReturnTypeExtension; use PHPStan\Type\FileTypeMapper; use PHPStan\Type\VerbosityLevel; @@ -34,8 +31,6 @@ abstract class TypeInferenceTestCase extends \PHPStan\Testing\PHPStanTestCase /** * @param string $file * @param callable(\PhpParser\Node, \PHPStan\Analyser\Scope): void $callback - * @param DynamicMethodReturnTypeExtension[] $dynamicMethodReturnTypeExtensions - * @param DynamicStaticMethodReturnTypeExtension[] $dynamicStaticMethodReturnTypeExtensions * @param \PHPStan\Type\MethodTypeSpecifyingExtension[] $methodTypeSpecifyingExtensions * @param \PHPStan\Type\StaticMethodTypeSpecifyingExtension[] $staticMethodTypeSpecifyingExtensions * @param string[] $dynamicConstantNames @@ -43,8 +38,6 @@ abstract class TypeInferenceTestCase extends \PHPStan\Testing\PHPStanTestCase public function processFile( string $file, callable $callback, - array $dynamicMethodReturnTypeExtensions = [], - array $dynamicStaticMethodReturnTypeExtensions = [], array $methodTypeSpecifyingExtensions = [], array $staticMethodTypeSpecifyingExtensions = [], array $dynamicConstantNames = [] @@ -54,15 +47,14 @@ public function processFile( $phpDocNodeResolver = self::getContainer()->getByType(PhpDocNodeResolver::class); $printer = new \PhpParser\PrettyPrinter\Standard(); - $broker = $this->createBroker($dynamicMethodReturnTypeExtensions, $dynamicStaticMethodReturnTypeExtensions); - Broker::registerInstance($broker); - $typeSpecifier = $this->createTypeSpecifier($printer, $broker, $methodTypeSpecifyingExtensions, $staticMethodTypeSpecifyingExtensions); + $reflectionProvider = $this->createReflectionProvider(); + $typeSpecifier = $this->createTypeSpecifier($printer, $reflectionProvider, $methodTypeSpecifyingExtensions, $staticMethodTypeSpecifyingExtensions); $currentWorkingDirectory = $this->getCurrentWorkingDirectory(); $fileHelper = new FileHelper($currentWorkingDirectory); - $fileTypeMapper = new FileTypeMapper(new DirectReflectionProviderProvider($broker), $this->getParser(), $phpDocStringResolver, $phpDocNodeResolver, $this->createMock(Cache::class), new AnonymousClassNameHelper($fileHelper, new SimpleRelativePathHelper($currentWorkingDirectory))); + $fileTypeMapper = new FileTypeMapper(new DirectReflectionProviderProvider($reflectionProvider), $this->getParser(), $phpDocStringResolver, $phpDocNodeResolver, $this->createMock(Cache::class), new AnonymousClassNameHelper($fileHelper, new SimpleRelativePathHelper($currentWorkingDirectory))); $phpDocInheritanceResolver = new PhpDocInheritanceResolver($fileTypeMapper); $resolver = new NodeScopeResolver( - $broker, + $reflectionProvider, self::getReflectors()[0], $this->getClassReflectionExtensionRegistryProvider(), $this->getParser(), @@ -83,7 +75,7 @@ public function processFile( return $fileHelper->normalizePath($file); }, array_merge([$file], $this->getAdditionalAnalysedFiles()))); - $scopeFactory = $this->createScopeFactory($broker, $typeSpecifier); + $scopeFactory = $this->createScopeFactory($reflectionProvider, $typeSpecifier); if (count($dynamicConstantNames) > 0) { $reflectionProperty = new \ReflectionProperty(DirectScopeFactory::class, 'dynamicConstantNames'); $reflectionProperty->setAccessible(true); diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 6b12af562f..05b93d6684 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -1466,8 +1466,6 @@ public function testVarAnnotations( $expression, [], [], - [], - [], 'die', [], false @@ -3434,8 +3432,6 @@ public function testLiteralArraysKeys( '$key', [], [], - [], - [], $evaluatedPointExpressionType ); } @@ -4188,8 +4184,6 @@ public function testSwitchGetClass( $expression, [], [], - [], - [], $evaluatedPointExpression ); } @@ -4587,8 +4581,6 @@ public function testOverwritingVariable( $expression, [], [], - [], - [], $evaluatedPointExpressionType ); } @@ -4931,8 +4923,6 @@ public function testForeachObjectType( $expression, [], [], - [], - [], $evaluatedPointExpression ); } @@ -6451,8 +6441,6 @@ public function testTypeSpecifyingExtensions( __DIR__ . '/data/type-specifying-extensions.php', $description, $expression, - [], - [], [new AssertionClassMethodTypeSpecifyingExtension($nullContext)], [new AssertionClassStaticMethodTypeSpecifyingExtension($nullContext)], 'die', @@ -6513,8 +6501,6 @@ public function testTypeSpecifyingExtensions2( __DIR__ . '/data/type-specifying-extensions2.php', $description, $expression, - [], - [], [new AssertionClassMethodTypeSpecifyingExtension($nullContext)], [new AssertionClassStaticMethodTypeSpecifyingExtension($nullContext)] ); @@ -6572,8 +6558,6 @@ public function testTypeSpecifyingExtensions3( __DIR__ . '/data/type-specifying-extensions3.php', $description, $expression, - [], - [], [new AssertionClassMethodTypeSpecifyingExtension($nullContext)], [new AssertionClassStaticMethodTypeSpecifyingExtension($nullContext)], 'die', @@ -7114,8 +7098,6 @@ public function testTypeElimination( $expression, [], [], - [], - [], $evaluatedPointExpression ); } @@ -7772,8 +7754,6 @@ public function testForeachLoopVariables( $expression, [], [], - [], - [], $evaluatedPointExpression ); } @@ -7797,8 +7777,6 @@ public function testWhileLoopVariables( $expression, [], [], - [], - [], $evaluatedPointExpression ); } @@ -7822,8 +7800,6 @@ public function testForLoopVariables( $expression, [], [], - [], - [], $evaluatedPointExpression ); } @@ -7938,8 +7914,6 @@ public function testDoWhileLoopVariables( $expression, [], [], - [], - [], $evaluatedPointExpression ); } @@ -7978,8 +7952,6 @@ public function testMultipleClassesInOneFile( $expression, [], [], - [], - [], $evaluatedPointExpression ); } @@ -8483,8 +8455,6 @@ public function testClosureWithUsePassedByReference( $expression, [], [], - [], - [], $evaluatedPointExpression ); } @@ -8587,8 +8557,6 @@ public function testClosureWithUsePassedByReferenceReturn( $expression, [], [], - [], - [], $evaluatedPointExpression ); } @@ -8623,8 +8591,6 @@ public function testClosureWithInferredTypehint( $expression, [], [], - [], - [], 'die', [], false @@ -8928,8 +8894,6 @@ public function testSpecifiedFunctionCall( $expression, [], [], - [], - [], $evaluatedPointExpression ); } @@ -9092,8 +9056,6 @@ public function testConstantTypeAfterDuplicateCondition( $expression, [], [], - [], - [], $evaluatedPointExpression ); } @@ -9152,8 +9114,6 @@ public function testAnonymousClassName( $expression, [], [], - [], - [], $evaluatedPointExpression ); } @@ -9227,8 +9187,6 @@ public function testDynamicConstants( $expression, [], [], - [], - [], 'die', [ 'DynamicConstants\\DynamicConstantClass::DYNAMIC_CONSTANT_IN_CLASS', @@ -9369,8 +9327,6 @@ public function testPropertyArrayAssignment( $expression, [], [], - [], - [], $evaluatedPointExpression ); } @@ -9506,8 +9462,6 @@ public function testGetParentClass( $expression, [], [], - [], - [], $evaluatedPointExpression ); } @@ -9546,8 +9500,6 @@ public function testIsCountable( $expression, [], [], - [], - [], $evaluatedPointExpression ); } @@ -10366,8 +10318,6 @@ public function testTryCatchScope( $expression, [], [], - [], - [], $evaluatedPointExpression, [], false @@ -10378,8 +10328,6 @@ public function testTryCatchScope( * @param string $file * @param string $description * @param string $expression - * @param DynamicMethodReturnTypeExtension[] $dynamicMethodReturnTypeExtensions - * @param DynamicStaticMethodReturnTypeExtension[] $dynamicStaticMethodReturnTypeExtensions * @param \PHPStan\Type\MethodTypeSpecifyingExtension[] $methodTypeSpecifyingExtensions * @param \PHPStan\Type\StaticMethodTypeSpecifyingExtension[] $staticMethodTypeSpecifyingExtensions * @param string $evaluatedPointExpression @@ -10390,8 +10338,6 @@ private function assertTypes( string $file, string $description, string $expression, - array $dynamicMethodReturnTypeExtensions = [], - array $dynamicStaticMethodReturnTypeExtensions = [], array $methodTypeSpecifyingExtensions = [], array $staticMethodTypeSpecifyingExtensions = [], string $evaluatedPointExpression = 'die', @@ -10429,8 +10375,6 @@ static function (\PhpParser\Node $node, Scope $scope) use ($file, $evaluatedPoin $assertType($scope); }, - $dynamicMethodReturnTypeExtensions, - $dynamicStaticMethodReturnTypeExtensions, $methodTypeSpecifyingExtensions, $staticMethodTypeSpecifyingExtensions, $dynamicConstantNames From db2f7fb9e7928de68665ca09a3e1002d8a329b33 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 14 Sep 2021 15:51:24 +0200 Subject: [PATCH 0288/1284] Deprecated BrokerAwareExtension --- src/Reflection/BrokerAwareExtension.php | 5 ++++- ...rsalObjectCratesClassReflectionExtension.php | 15 +++++---------- ...fyingFunctionsDynamicReturnTypeExtension.php | 17 ++++++----------- ...ObjectCratesClassReflectionExtensionTest.php | 17 +++++++---------- 4 files changed, 22 insertions(+), 32 deletions(-) diff --git a/src/Reflection/BrokerAwareExtension.php b/src/Reflection/BrokerAwareExtension.php index 58bc2aa3bb..9ca104d127 100644 --- a/src/Reflection/BrokerAwareExtension.php +++ b/src/Reflection/BrokerAwareExtension.php @@ -4,7 +4,10 @@ use PHPStan\Broker\Broker; -/** @api */ +/** + * @api + * @deprecated Inject PHPStan\Reflection\ReflectionProvider into the constructor instead + */ interface BrokerAwareExtension { diff --git a/src/Reflection/Php/UniversalObjectCratesClassReflectionExtension.php b/src/Reflection/Php/UniversalObjectCratesClassReflectionExtension.php index 8c0ec93652..f488b85b4d 100644 --- a/src/Reflection/Php/UniversalObjectCratesClassReflectionExtension.php +++ b/src/Reflection/Php/UniversalObjectCratesClassReflectionExtension.php @@ -2,7 +2,6 @@ namespace PHPStan\Reflection\Php; -use PHPStan\Broker\Broker; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Reflection\PropertyReflection; @@ -10,31 +9,27 @@ use PHPStan\Type\MixedType; class UniversalObjectCratesClassReflectionExtension - implements \PHPStan\Reflection\PropertiesClassReflectionExtension, \PHPStan\Reflection\BrokerAwareExtension + implements \PHPStan\Reflection\PropertiesClassReflectionExtension { /** @var string[] */ private array $classes; - private \PHPStan\Broker\Broker $broker; + private ReflectionProvider $reflectionProvider; /** * @param string[] $classes */ - public function __construct(array $classes) + public function __construct(ReflectionProvider $reflectionProvider, array $classes) { + $this->reflectionProvider = $reflectionProvider; $this->classes = $classes; } - public function setBroker(Broker $broker): void - { - $this->broker = $broker; - } - public function hasProperty(ClassReflection $classReflection, string $propertyName): bool { return self::isUniversalObjectCrate( - $this->broker, + $this->reflectionProvider, $this->classes, $classReflection ); diff --git a/src/Type/Php/TypeSpecifyingFunctionsDynamicReturnTypeExtension.php b/src/Type/Php/TypeSpecifyingFunctionsDynamicReturnTypeExtension.php index 9dc7d71a13..dfb5edd7d0 100644 --- a/src/Type/Php/TypeSpecifyingFunctionsDynamicReturnTypeExtension.php +++ b/src/Type/Php/TypeSpecifyingFunctionsDynamicReturnTypeExtension.php @@ -6,36 +6,31 @@ use PHPStan\Analyser\Scope; use PHPStan\Analyser\TypeSpecifier; use PHPStan\Analyser\TypeSpecifierAwareExtension; -use PHPStan\Broker\Broker; -use PHPStan\Reflection\BrokerAwareExtension; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\ParametersAcceptorSelector; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\Comparison\ImpossibleCheckTypeHelper; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\Type; -class TypeSpecifyingFunctionsDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension, TypeSpecifierAwareExtension, BrokerAwareExtension +class TypeSpecifyingFunctionsDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension, TypeSpecifierAwareExtension { private bool $treatPhpDocTypesAsCertain; - private \PHPStan\Broker\Broker $broker; + private ReflectionProvider $reflectionProvider; private \PHPStan\Analyser\TypeSpecifier $typeSpecifier; private ?\PHPStan\Rules\Comparison\ImpossibleCheckTypeHelper $helper = null; - public function __construct(bool $treatPhpDocTypesAsCertain) + public function __construct(ReflectionProvider $reflectionProvider, bool $treatPhpDocTypesAsCertain) { + $this->reflectionProvider = $reflectionProvider; $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; } - public function setBroker(Broker $broker): void - { - $this->broker = $broker; - } - public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void { $this->typeSpecifier = $typeSpecifier; @@ -89,7 +84,7 @@ public function getTypeFromFunctionCall( private function getHelper(): ImpossibleCheckTypeHelper { if ($this->helper === null) { - $this->helper = new ImpossibleCheckTypeHelper($this->broker, $this->typeSpecifier, $this->broker->getUniversalObjectCratesClasses(), $this->treatPhpDocTypesAsCertain); + $this->helper = new ImpossibleCheckTypeHelper($this->reflectionProvider, $this->typeSpecifier, $this->reflectionProvider->getUniversalObjectCratesClasses(), $this->treatPhpDocTypesAsCertain); } return $this->helper; diff --git a/tests/PHPStan/Reflection/Php/UniversalObjectCratesClassReflectionExtensionTest.php b/tests/PHPStan/Reflection/Php/UniversalObjectCratesClassReflectionExtensionTest.php index db97736a8b..448619dee9 100644 --- a/tests/PHPStan/Reflection/Php/UniversalObjectCratesClassReflectionExtensionTest.php +++ b/tests/PHPStan/Reflection/Php/UniversalObjectCratesClassReflectionExtensionTest.php @@ -2,7 +2,6 @@ namespace PHPStan\Reflection\Php; -use PHPStan\Broker\Broker; use PHPStan\Type\ObjectType; use PHPStan\Type\StringType; @@ -11,35 +10,33 @@ class UniversalObjectCratesClassReflectionExtensionTest extends \PHPStan\Testing public function testNonexistentClass(): void { - $broker = self::getContainer()->getByType(Broker::class); - $extension = new UniversalObjectCratesClassReflectionExtension([ + $reflectionProvider = $this->createReflectionProvider(); + $extension = new UniversalObjectCratesClassReflectionExtension($reflectionProvider, [ 'NonexistentClass', 'stdClass', ]); - $extension->setBroker($broker); - $this->assertTrue($extension->hasProperty($broker->getClass(\stdClass::class), 'foo')); + $this->assertTrue($extension->hasProperty($reflectionProvider->getClass(\stdClass::class), 'foo')); } public function testDifferentGetSetType(): void { require_once __DIR__ . '/data/universal-object-crates.php'; - $broker = self::getContainer()->getByType(Broker::class); - $extension = new UniversalObjectCratesClassReflectionExtension([ + $reflectionProvider = $this->createReflectionProvider(); + $extension = new UniversalObjectCratesClassReflectionExtension($reflectionProvider, [ 'UniversalObjectCreates\DifferentGetSetTypes', ]); - $extension->setBroker($broker); $this->assertEquals( new ObjectType('UniversalObjectCreates\DifferentGetSetTypesValue'), $extension - ->getProperty($broker->getClass('UniversalObjectCreates\DifferentGetSetTypes'), 'foo') + ->getProperty($reflectionProvider->getClass('UniversalObjectCreates\DifferentGetSetTypes'), 'foo') ->getReadableType() ); $this->assertEquals( new StringType(), $extension - ->getProperty($broker->getClass('UniversalObjectCreates\DifferentGetSetTypes'), 'foo') + ->getProperty($reflectionProvider->getClass('UniversalObjectCreates\DifferentGetSetTypes'), 'foo') ->getWritableType() ); } From 65efd93f764e9db076851ef262179fe670f1ab38 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 14 Sep 2021 16:01:53 +0200 Subject: [PATCH 0289/1284] [BCB] PHPStanTestCase - extensions can no longer be provided by overriding methods from PHPStanTestCase. Use getAdditionalConfigFiles() instead. --- src/Testing/PHPStanTestCase.php | 42 ++----------------- .../IfConstantConditionRuleTest.php | 29 ------------- .../Comparison/data/function-definition.php | 1 + 3 files changed, 5 insertions(+), 67 deletions(-) diff --git a/src/Testing/PHPStanTestCase.php b/src/Testing/PHPStanTestCase.php index 3f65cdc23f..82b58ba4f4 100644 --- a/src/Testing/PHPStanTestCase.php +++ b/src/Testing/PHPStanTestCase.php @@ -165,13 +165,11 @@ public function createReflectionProvider(): ReflectionProvider $setterReflectionProviderProvider->setReflectionProvider($reflectionProvider); $dynamicReturnTypeExtensionRegistryProvider = new DirectDynamicReturnTypeExtensionRegistryProvider( - array_merge(self::getContainer()->getServicesByTag(BrokerFactory::DYNAMIC_METHOD_RETURN_TYPE_EXTENSION_TAG), $this->getDynamicMethodReturnTypeExtensions()), - array_merge(self::getContainer()->getServicesByTag(BrokerFactory::DYNAMIC_STATIC_METHOD_RETURN_TYPE_EXTENSION_TAG), $this->getDynamicStaticMethodReturnTypeExtensions()), - array_merge(self::getContainer()->getServicesByTag(BrokerFactory::DYNAMIC_FUNCTION_RETURN_TYPE_EXTENSION_TAG), $this->getDynamicFunctionReturnTypeExtensions()) - ); - $operatorTypeSpecifyingExtensionRegistryProvider = new DirectOperatorTypeSpecifyingExtensionRegistryProvider( - $this->getOperatorTypeSpecifyingExtensions() + self::getContainer()->getServicesByTag(BrokerFactory::DYNAMIC_METHOD_RETURN_TYPE_EXTENSION_TAG), + self::getContainer()->getServicesByTag(BrokerFactory::DYNAMIC_STATIC_METHOD_RETURN_TYPE_EXTENSION_TAG), + self::getContainer()->getServicesByTag(BrokerFactory::DYNAMIC_FUNCTION_RETURN_TYPE_EXTENSION_TAG) ); + $operatorTypeSpecifyingExtensionRegistryProvider = new DirectOperatorTypeSpecifyingExtensionRegistryProvider([]); $broker = new Broker( $reflectionProvider, @@ -605,38 +603,6 @@ public function getCurrentWorkingDirectory(): string return $this->getFileHelper()->normalizePath(__DIR__ . '/../..'); } - /** - * @return \PHPStan\Type\DynamicMethodReturnTypeExtension[] - */ - public function getDynamicMethodReturnTypeExtensions(): array - { - return []; - } - - /** - * @return \PHPStan\Type\DynamicStaticMethodReturnTypeExtension[] - */ - public function getDynamicStaticMethodReturnTypeExtensions(): array - { - return []; - } - - /** - * @return \PHPStan\Type\DynamicFunctionReturnTypeExtension[] - */ - public function getDynamicFunctionReturnTypeExtensions(): array - { - return []; - } - - /** - * @return \PHPStan\Type\OperatorTypeSpecifyingExtension[] - */ - public function getOperatorTypeSpecifyingExtensions(): array - { - return []; - } - /** * @param \PhpParser\PrettyPrinter\Standard $printer * @param \PHPStan\Reflection\ReflectionProvider $reflectionProvider diff --git a/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php index 4ad516f3ce..4623df6293 100644 --- a/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php @@ -2,13 +2,6 @@ namespace PHPStan\Rules\Comparison; -use PhpParser\Node\Expr\FuncCall; -use PHPStan\Analyser\Scope; -use PHPStan\Reflection\FunctionReflection; -use PHPStan\Type\Constant\ConstantBooleanType; -use PHPStan\Type\DynamicFunctionReturnTypeExtension; -use PHPStan\Type\Type; - /** * @extends \PHPStan\Testing\RuleTestCase */ @@ -39,28 +32,6 @@ protected function shouldTreatPhpDocTypesAsCertain(): bool return $this->treatPhpDocTypesAsCertain; } - /** - * @return DynamicFunctionReturnTypeExtension[] - */ - public function getDynamicFunctionReturnTypeExtensions(): array - { - return [ - new class implements DynamicFunctionReturnTypeExtension { - - public function isFunctionSupported(FunctionReflection $functionReflection): bool - { - return $functionReflection->getName() === 'always_true'; - } - - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type - { - return new ConstantBooleanType(true); - } - - }, - ]; - } - public function testRule(): void { $this->treatPhpDocTypesAsCertain = true; diff --git a/tests/PHPStan/Rules/Comparison/data/function-definition.php b/tests/PHPStan/Rules/Comparison/data/function-definition.php index c4ef947394..e65c199f14 100644 --- a/tests/PHPStan/Rules/Comparison/data/function-definition.php +++ b/tests/PHPStan/Rules/Comparison/data/function-definition.php @@ -1,5 +1,6 @@ Date: Tue, 14 Sep 2021 16:13:33 +0200 Subject: [PATCH 0290/1284] [BCB] Removed some unused internal helper methods from Broker --- conf/config.neon | 1 + src/Broker/Broker.php | 66 --------------- src/Broker/BrokerFactory.php | 4 - ...micReturnTypeExtensionRegistryProvider.php | 83 ------------------- ...ypeSpecifyingExtensionRegistryProvider.php | 38 --------- src/Testing/PHPStanTestCase.php | 15 ---- ...ingFunctionsDynamicReturnTypeExtension.php | 13 ++- tests/PHPStan/Broker/BrokerTest.php | 8 -- 8 files changed, 12 insertions(+), 216 deletions(-) delete mode 100644 src/DependencyInjection/Type/DirectDynamicReturnTypeExtensionRegistryProvider.php delete mode 100644 src/DependencyInjection/Type/DirectOperatorTypeSpecifyingExtensionRegistryProvider.php diff --git a/conf/config.neon b/conf/config.neon index 4ad60e9b8b..9a43172fbb 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1433,6 +1433,7 @@ services: class: PHPStan\Type\Php\TypeSpecifyingFunctionsDynamicReturnTypeExtension arguments: treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% + universalObjectCratesClasses: %universalObjectCratesClasses% tags: - phpstan.broker.dynamicFunctionReturnTypeExtension diff --git a/src/Broker/Broker.php b/src/Broker/Broker.php index 86dc087974..800414eabc 100644 --- a/src/Broker/Broker.php +++ b/src/Broker/Broker.php @@ -3,14 +3,10 @@ namespace PHPStan\Broker; use PHPStan\Analyser\Scope; -use PHPStan\DependencyInjection\Type\DynamicReturnTypeExtensionRegistryProvider; -use PHPStan\DependencyInjection\Type\OperatorTypeSpecifyingExtensionRegistryProvider; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\GlobalConstantReflection; use PHPStan\Reflection\ReflectionProvider; -use PHPStan\Type\OperatorTypeSpecifyingExtension; -use PHPStan\Type\Type; /** @api */ class Broker implements ReflectionProvider @@ -18,10 +14,6 @@ class Broker implements ReflectionProvider private ReflectionProvider $reflectionProvider; - private DynamicReturnTypeExtensionRegistryProvider $dynamicReturnTypeExtensionRegistryProvider; - - private \PHPStan\DependencyInjection\Type\OperatorTypeSpecifyingExtensionRegistryProvider $operatorTypeSpecifyingExtensionRegistryProvider; - /** @var string[] */ private array $universalObjectCratesClasses; @@ -29,20 +21,14 @@ class Broker implements ReflectionProvider /** * @param \PHPStan\Reflection\ReflectionProvider $reflectionProvider - * @param \PHPStan\DependencyInjection\Type\DynamicReturnTypeExtensionRegistryProvider $dynamicReturnTypeExtensionRegistryProvider - * @param \PHPStan\DependencyInjection\Type\OperatorTypeSpecifyingExtensionRegistryProvider $operatorTypeSpecifyingExtensionRegistryProvider * @param string[] $universalObjectCratesClasses */ public function __construct( ReflectionProvider $reflectionProvider, - DynamicReturnTypeExtensionRegistryProvider $dynamicReturnTypeExtensionRegistryProvider, - OperatorTypeSpecifyingExtensionRegistryProvider $operatorTypeSpecifyingExtensionRegistryProvider, array $universalObjectCratesClasses ) { $this->reflectionProvider = $reflectionProvider; - $this->dynamicReturnTypeExtensionRegistryProvider = $dynamicReturnTypeExtensionRegistryProvider; - $this->operatorTypeSpecifyingExtensionRegistryProvider = $operatorTypeSpecifyingExtensionRegistryProvider; $this->universalObjectCratesClasses = $universalObjectCratesClasses; } @@ -122,56 +108,4 @@ public function getUniversalObjectCratesClasses(): array return $this->universalObjectCratesClasses; } - /** - * @param string $className - * @return \PHPStan\Type\DynamicMethodReturnTypeExtension[] - */ - public function getDynamicMethodReturnTypeExtensionsForClass(string $className): array - { - return $this->dynamicReturnTypeExtensionRegistryProvider->getRegistry()->getDynamicMethodReturnTypeExtensionsForClass($className); - } - - /** - * @param string $className - * @return \PHPStan\Type\DynamicStaticMethodReturnTypeExtension[] - */ - public function getDynamicStaticMethodReturnTypeExtensionsForClass(string $className): array - { - return $this->dynamicReturnTypeExtensionRegistryProvider->getRegistry()->getDynamicStaticMethodReturnTypeExtensionsForClass($className); - } - - /** - * @return OperatorTypeSpecifyingExtension[] - */ - public function getOperatorTypeSpecifyingExtensions(string $operator, Type $leftType, Type $rightType): array - { - return $this->operatorTypeSpecifyingExtensionRegistryProvider->getRegistry()->getOperatorTypeSpecifyingExtensions($operator, $leftType, $rightType); - } - - /** - * @return \PHPStan\Type\DynamicFunctionReturnTypeExtension[] - */ - public function getDynamicFunctionReturnTypeExtensions(): array - { - return $this->dynamicReturnTypeExtensionRegistryProvider->getRegistry()->getDynamicFunctionReturnTypeExtensions(); - } - - /** - * @internal - * @return DynamicReturnTypeExtensionRegistryProvider - */ - public function getDynamicReturnTypeExtensionRegistryProvider(): DynamicReturnTypeExtensionRegistryProvider - { - return $this->dynamicReturnTypeExtensionRegistryProvider; - } - - /** - * @internal - * @return \PHPStan\DependencyInjection\Type\OperatorTypeSpecifyingExtensionRegistryProvider - */ - public function getOperatorTypeSpecifyingExtensionRegistryProvider(): OperatorTypeSpecifyingExtensionRegistryProvider - { - return $this->operatorTypeSpecifyingExtensionRegistryProvider; - } - } diff --git a/src/Broker/BrokerFactory.php b/src/Broker/BrokerFactory.php index 18eba1875c..f8a937c20a 100644 --- a/src/Broker/BrokerFactory.php +++ b/src/Broker/BrokerFactory.php @@ -3,8 +3,6 @@ namespace PHPStan\Broker; use PHPStan\DependencyInjection\Container; -use PHPStan\DependencyInjection\Type\DynamicReturnTypeExtensionRegistryProvider; -use PHPStan\DependencyInjection\Type\OperatorTypeSpecifyingExtensionRegistryProvider; use PHPStan\Reflection\ReflectionProvider; class BrokerFactory @@ -28,8 +26,6 @@ public function create(): Broker { return new Broker( $this->container->getByType(ReflectionProvider::class), - $this->container->getByType(DynamicReturnTypeExtensionRegistryProvider::class), - $this->container->getByType(OperatorTypeSpecifyingExtensionRegistryProvider::class), $this->container->getParameter('universalObjectCratesClasses') ); } diff --git a/src/DependencyInjection/Type/DirectDynamicReturnTypeExtensionRegistryProvider.php b/src/DependencyInjection/Type/DirectDynamicReturnTypeExtensionRegistryProvider.php deleted file mode 100644 index 3759ec24ed..0000000000 --- a/src/DependencyInjection/Type/DirectDynamicReturnTypeExtensionRegistryProvider.php +++ /dev/null @@ -1,83 +0,0 @@ -dynamicMethodReturnTypeExtensions = $dynamicMethodReturnTypeExtensions; - $this->dynamicStaticMethodReturnTypeExtensions = $dynamicStaticMethodReturnTypeExtensions; - $this->dynamicFunctionReturnTypeExtensions = $dynamicFunctionReturnTypeExtensions; - } - - public function setBroker(Broker $broker): void - { - $this->broker = $broker; - } - - public function setReflectionProvider(ReflectionProvider $reflectionProvider): void - { - $this->reflectionProvider = $reflectionProvider; - } - - public function addDynamicMethodReturnTypeExtension(DynamicMethodReturnTypeExtension $extension): void - { - $this->dynamicMethodReturnTypeExtensions[] = $extension; - } - - public function addDynamicStaticMethodReturnTypeExtension(DynamicStaticMethodReturnTypeExtension $extension): void - { - $this->dynamicStaticMethodReturnTypeExtensions[] = $extension; - } - - public function addDynamicFunctionReturnTypeExtension(DynamicFunctionReturnTypeExtension $extension): void - { - $this->dynamicFunctionReturnTypeExtensions[] = $extension; - } - - public function getRegistry(): DynamicReturnTypeExtensionRegistry - { - return new DynamicReturnTypeExtensionRegistry( - $this->broker, - $this->reflectionProvider, - $this->dynamicMethodReturnTypeExtensions, - $this->dynamicStaticMethodReturnTypeExtensions, - $this->dynamicFunctionReturnTypeExtensions - ); - } - -} diff --git a/src/DependencyInjection/Type/DirectOperatorTypeSpecifyingExtensionRegistryProvider.php b/src/DependencyInjection/Type/DirectOperatorTypeSpecifyingExtensionRegistryProvider.php deleted file mode 100644 index 80f73d7b5a..0000000000 --- a/src/DependencyInjection/Type/DirectOperatorTypeSpecifyingExtensionRegistryProvider.php +++ /dev/null @@ -1,38 +0,0 @@ -extensions = $extensions; - } - - public function setBroker(Broker $broker): void - { - $this->broker = $broker; - } - - public function getRegistry(): OperatorTypeSpecifyingExtensionRegistry - { - return new OperatorTypeSpecifyingExtensionRegistry( - $this->broker, - $this->extensions - ); - } - -} diff --git a/src/Testing/PHPStanTestCase.php b/src/Testing/PHPStanTestCase.php index 82b58ba4f4..86782a3809 100644 --- a/src/Testing/PHPStanTestCase.php +++ b/src/Testing/PHPStanTestCase.php @@ -22,14 +22,11 @@ use PHPStan\BetterReflection\SourceLocator\Type\PhpInternalSourceLocator; use PHPStan\Broker\AnonymousClassNameHelper; use PHPStan\Broker\Broker; -use PHPStan\Broker\BrokerFactory; use PHPStan\Cache\Cache; use PHPStan\Cache\MemoryCacheStorage; use PHPStan\DependencyInjection\Container; use PHPStan\DependencyInjection\ContainerFactory; use PHPStan\DependencyInjection\Reflection\DirectClassReflectionExtensionRegistryProvider; -use PHPStan\DependencyInjection\Type\DirectDynamicReturnTypeExtensionRegistryProvider; -use PHPStan\DependencyInjection\Type\DirectOperatorTypeSpecifyingExtensionRegistryProvider; use PHPStan\DependencyInjection\Type\DynamicReturnTypeExtensionRegistryProvider; use PHPStan\DependencyInjection\Type\OperatorTypeSpecifyingExtensionRegistryProvider; use PHPStan\File\FileHelper; @@ -164,22 +161,10 @@ public function createReflectionProvider(): ReflectionProvider ); $setterReflectionProviderProvider->setReflectionProvider($reflectionProvider); - $dynamicReturnTypeExtensionRegistryProvider = new DirectDynamicReturnTypeExtensionRegistryProvider( - self::getContainer()->getServicesByTag(BrokerFactory::DYNAMIC_METHOD_RETURN_TYPE_EXTENSION_TAG), - self::getContainer()->getServicesByTag(BrokerFactory::DYNAMIC_STATIC_METHOD_RETURN_TYPE_EXTENSION_TAG), - self::getContainer()->getServicesByTag(BrokerFactory::DYNAMIC_FUNCTION_RETURN_TYPE_EXTENSION_TAG) - ); - $operatorTypeSpecifyingExtensionRegistryProvider = new DirectOperatorTypeSpecifyingExtensionRegistryProvider([]); - $broker = new Broker( $reflectionProvider, - $dynamicReturnTypeExtensionRegistryProvider, - $operatorTypeSpecifyingExtensionRegistryProvider, self::getContainer()->getParameter('universalObjectCratesClasses') ); - $dynamicReturnTypeExtensionRegistryProvider->setBroker($broker); - $dynamicReturnTypeExtensionRegistryProvider->setReflectionProvider($reflectionProvider); - $operatorTypeSpecifyingExtensionRegistryProvider->setBroker($broker); $this->getClassReflectionExtensionRegistryProvider()->setBroker($broker); return $reflectionProvider; diff --git a/src/Type/Php/TypeSpecifyingFunctionsDynamicReturnTypeExtension.php b/src/Type/Php/TypeSpecifyingFunctionsDynamicReturnTypeExtension.php index dfb5edd7d0..df691ad698 100644 --- a/src/Type/Php/TypeSpecifyingFunctionsDynamicReturnTypeExtension.php +++ b/src/Type/Php/TypeSpecifyingFunctionsDynamicReturnTypeExtension.php @@ -25,10 +25,19 @@ class TypeSpecifyingFunctionsDynamicReturnTypeExtension implements DynamicFuncti private ?\PHPStan\Rules\Comparison\ImpossibleCheckTypeHelper $helper = null; - public function __construct(ReflectionProvider $reflectionProvider, bool $treatPhpDocTypesAsCertain) + /** @var string[] */ + private array $universalObjectCratesClasses; + + /** + * @param ReflectionProvider $reflectionProvider + * @param bool $treatPhpDocTypesAsCertain + * @param string[] $universalObjectCratesClasses + */ + public function __construct(ReflectionProvider $reflectionProvider, bool $treatPhpDocTypesAsCertain, array $universalObjectCratesClasses) { $this->reflectionProvider = $reflectionProvider; $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; + $this->universalObjectCratesClasses = $universalObjectCratesClasses; } public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void @@ -84,7 +93,7 @@ public function getTypeFromFunctionCall( private function getHelper(): ImpossibleCheckTypeHelper { if ($this->helper === null) { - $this->helper = new ImpossibleCheckTypeHelper($this->reflectionProvider, $this->typeSpecifier, $this->reflectionProvider->getUniversalObjectCratesClasses(), $this->treatPhpDocTypesAsCertain); + $this->helper = new ImpossibleCheckTypeHelper($this->reflectionProvider, $this->typeSpecifier, $this->universalObjectCratesClasses, $this->treatPhpDocTypesAsCertain); } return $this->helper; diff --git a/tests/PHPStan/Broker/BrokerTest.php b/tests/PHPStan/Broker/BrokerTest.php index 91bdc7ca13..86d2ad406c 100644 --- a/tests/PHPStan/Broker/BrokerTest.php +++ b/tests/PHPStan/Broker/BrokerTest.php @@ -7,8 +7,6 @@ use PHPStan\BetterReflection\SourceLocator\SourceStubber\PhpStormStubsSourceStubber; use PHPStan\Cache\Cache; use PHPStan\DependencyInjection\Reflection\DirectClassReflectionExtensionRegistryProvider; -use PHPStan\DependencyInjection\Type\DirectDynamicReturnTypeExtensionRegistryProvider; -use PHPStan\DependencyInjection\Type\DirectOperatorTypeSpecifyingExtensionRegistryProvider; use PHPStan\File\FileHelper; use PHPStan\File\SimpleRelativePathHelper; use PHPStan\Php\PhpVersion; @@ -39,8 +37,6 @@ protected function setUp(): void $anonymousClassNameHelper = new AnonymousClassNameHelper($fileHelper, $relativePathHelper); $classReflectionExtensionRegistryProvider = new DirectClassReflectionExtensionRegistryProvider([], []); - $dynamicReturnTypeExtensionRegistryProvider = new DirectDynamicReturnTypeExtensionRegistryProvider([], [], []); - $operatorTypeSpecifyingExtensionRegistryProvider = new DirectOperatorTypeSpecifyingExtensionRegistryProvider([]); $setterReflectionProviderProvider = new SetterReflectionProviderProvider(); $reflectionProvider = new RuntimeReflectionProvider( @@ -57,13 +53,9 @@ protected function setUp(): void $setterReflectionProviderProvider->setReflectionProvider($reflectionProvider); $this->broker = new Broker( $reflectionProvider, - $dynamicReturnTypeExtensionRegistryProvider, - $operatorTypeSpecifyingExtensionRegistryProvider, [] ); $classReflectionExtensionRegistryProvider->setBroker($this->broker); - $dynamicReturnTypeExtensionRegistryProvider->setBroker($this->broker); - $operatorTypeSpecifyingExtensionRegistryProvider->setBroker($this->broker); } public function testClassNotFound(): void From f8ecb1933d2acc711e311f05aee9060460e8fd93 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 14 Sep 2021 16:25:41 +0200 Subject: [PATCH 0291/1284] Test cases cleanup --- build/baseline-7.4.neon | 14 - src/Testing/PHPStanTestCase.php | 371 +----------------------- src/Testing/RuleTestCase.php | 39 +-- src/Testing/TestCase.neon | 6 + src/Testing/TypeInferenceTestCase.php | 20 +- tests/PHPStan/Analyser/AnalyserTest.php | 13 +- 6 files changed, 23 insertions(+), 440 deletions(-) create mode 100644 src/Testing/TestCase.neon diff --git a/build/baseline-7.4.neon b/build/baseline-7.4.neon index 0d3f438771..db957bbc2f 100644 --- a/build/baseline-7.4.neon +++ b/build/baseline-7.4.neon @@ -8,20 +8,6 @@ parameters: message: "#^Class PHPStan\\\\DependencyInjection\\\\Reflection\\\\DirectClassReflectionExtensionRegistryProvider has an uninitialized property \\$broker\\. Give it default value or assign it in the constructor\\.$#" count: 1 path: ../src/DependencyInjection/Reflection/DirectClassReflectionExtensionRegistryProvider.php - - - message: "#^Class PHPStan\\\\DependencyInjection\\\\Type\\\\DirectDynamicReturnTypeExtensionRegistryProvider has an uninitialized property \\$broker\\. Give it default value or assign it in the constructor\\.$#" - count: 1 - path: ../src/DependencyInjection/Type/DirectDynamicReturnTypeExtensionRegistryProvider.php - - - - message: "#^Class PHPStan\\\\DependencyInjection\\\\Type\\\\DirectDynamicReturnTypeExtensionRegistryProvider has an uninitialized property \\$reflectionProvider\\. Give it default value or assign it in the constructor\\.$#" - count: 1 - path: ../src/DependencyInjection/Type/DirectDynamicReturnTypeExtensionRegistryProvider.php - - - - message: "#^Class PHPStan\\\\DependencyInjection\\\\Type\\\\DirectOperatorTypeSpecifyingExtensionRegistryProvider has an uninitialized property \\$broker\\. Give it default value or assign it in the constructor\\.$#" - count: 1 - path: ../src/DependencyInjection/Type/DirectOperatorTypeSpecifyingExtensionRegistryProvider.php - message: "#^Class PHPStan\\\\Parallel\\\\ParallelAnalyser has an uninitialized property \\$processPool\\. Give it default value or assign it in the constructor\\.$#" diff --git a/src/Testing/PHPStanTestCase.php b/src/Testing/PHPStanTestCase.php index 86782a3809..f149be64ae 100644 --- a/src/Testing/PHPStanTestCase.php +++ b/src/Testing/PHPStanTestCase.php @@ -20,58 +20,24 @@ use PHPStan\BetterReflection\SourceLocator\Type\EvaledCodeSourceLocator; use PHPStan\BetterReflection\SourceLocator\Type\MemoizingSourceLocator; use PHPStan\BetterReflection\SourceLocator\Type\PhpInternalSourceLocator; -use PHPStan\Broker\AnonymousClassNameHelper; -use PHPStan\Broker\Broker; -use PHPStan\Cache\Cache; -use PHPStan\Cache\MemoryCacheStorage; use PHPStan\DependencyInjection\Container; use PHPStan\DependencyInjection\ContainerFactory; -use PHPStan\DependencyInjection\Reflection\DirectClassReflectionExtensionRegistryProvider; +use PHPStan\DependencyInjection\Reflection\ClassReflectionExtensionRegistryProvider; use PHPStan\DependencyInjection\Type\DynamicReturnTypeExtensionRegistryProvider; use PHPStan\DependencyInjection\Type\OperatorTypeSpecifyingExtensionRegistryProvider; use PHPStan\File\FileHelper; -use PHPStan\File\SimpleRelativePathHelper; use PHPStan\Parser\CachedParser; -use PHPStan\Parser\FunctionCallStatementFinder; -use PHPStan\Parser\Parser; use PHPStan\Parser\PhpParserDecorator; -use PHPStan\Php\PhpVersion; -use PHPStan\PhpDoc\PhpDocInheritanceResolver; -use PHPStan\PhpDoc\PhpDocNodeResolver; -use PHPStan\PhpDoc\PhpDocStringResolver; -use PHPStan\PhpDoc\StubPhpDocProvider; use PHPStan\PhpDoc\TypeNodeResolver; use PHPStan\PhpDoc\TypeStringResolver; -use PHPStan\Reflection\Annotations\AnnotationsMethodsClassReflectionExtension; -use PHPStan\Reflection\Annotations\AnnotationsPropertiesClassReflectionExtension; -use PHPStan\Reflection\BetterReflection\BetterReflectionProvider; use PHPStan\Reflection\BetterReflection\Reflector\MemoizingClassReflector; use PHPStan\Reflection\BetterReflection\Reflector\MemoizingConstantReflector; use PHPStan\Reflection\BetterReflection\Reflector\MemoizingFunctionReflector; use PHPStan\Reflection\BetterReflection\SourceLocator\AutoloadSourceLocator; use PHPStan\Reflection\BetterReflection\SourceLocator\ComposerJsonAndInstalledJsonSourceLocatorMaker; use PHPStan\Reflection\BetterReflection\SourceLocator\FileNodesFetcher; -use PHPStan\Reflection\ClassReflection; -use PHPStan\Reflection\FunctionReflectionFactory; -use PHPStan\Reflection\Mixin\MixinMethodsClassReflectionExtension; -use PHPStan\Reflection\Mixin\MixinPropertiesClassReflectionExtension; -use PHPStan\Reflection\Php\PhpClassReflectionExtension; -use PHPStan\Reflection\Php\PhpFunctionReflection; -use PHPStan\Reflection\Php\PhpMethodReflection; -use PHPStan\Reflection\Php\PhpMethodReflectionFactory; -use PHPStan\Reflection\Php\Soap\SoapClientMethodsClassReflectionExtension; -use PHPStan\Reflection\Php\UniversalObjectCratesClassReflectionExtension; use PHPStan\Reflection\ReflectionProvider; -use PHPStan\Reflection\ReflectionProvider\ClassBlacklistReflectionProvider; -use PHPStan\Reflection\ReflectionProvider\ReflectionProviderFactory; -use PHPStan\Reflection\Runtime\RuntimeReflectionProvider; -use PHPStan\Reflection\SignatureMap\NativeFunctionReflectionProvider; -use PHPStan\Reflection\SignatureMap\SignatureMapProvider; use PHPStan\Rules\Properties\PropertyReflectionFinder; -use PHPStan\Type\FileTypeMapper; -use PHPStan\Type\Generic\TemplateTypeMap; -use PHPStan\Type\Php\SimpleXMLElementClassPropertyReflectionExtension; -use PHPStan\Type\Type; use PHPStan\Type\TypeAliasResolver; /** @api */ @@ -84,8 +50,6 @@ abstract class PHPStanTestCase extends \PHPUnit\Framework\TestCase /** @var array */ private static array $containers = []; - private ?DirectClassReflectionExtensionRegistryProvider $classReflectionExtensionRegistryProvider = null; - /** @var array{ClassReflector, FunctionReflector, ConstantReflector}|null */ private static $reflectors; @@ -96,6 +60,10 @@ abstract class PHPStanTestCase extends \PHPUnit\Framework\TestCase public static function getContainer(): Container { $additionalConfigFiles = static::getAdditionalConfigFiles(); + $additionalConfigFiles[] = __DIR__ . '/TestCase.neon'; + if (self::$useStaticReflectionProvider) { + $additionalConfigFiles[] = __DIR__ . '/TestCase-staticReflection.neon'; + } $cacheKey = sha1(implode("\n", $additionalConfigFiles)); if (!isset(self::$containers[$cacheKey])) { @@ -104,10 +72,6 @@ public static function getContainer(): Container self::fail(sprintf('Cannot create temp directory %s', $tmpDir)); } - if (self::$useStaticReflectionProvider) { - $additionalConfigFiles[] = __DIR__ . '/TestCase-staticReflection.neon'; - } - $rootDir = __DIR__ . '/../..'; $containerFactory = new ContainerFactory($rootDir); $container = $containerFactory->create($tmpDir, array_merge([ @@ -142,7 +106,6 @@ public function getParser(): \PHPStan\Parser\Parser /** * @api - * @return \PHPStan\Reflection\ReflectionProvider */ public function createBroker(): ReflectionProvider { @@ -152,37 +115,7 @@ public function createBroker(): ReflectionProvider /** @api */ public function createReflectionProvider(): ReflectionProvider { - $setterReflectionProviderProvider = new ReflectionProvider\SetterReflectionProviderProvider(); - $staticReflectionProvider = $this->createStaticReflectionProvider($setterReflectionProviderProvider); - $reflectionProvider = $this->createReflectionProviderByParameters( - $this->createRuntimeReflectionProvider($setterReflectionProviderProvider), - $staticReflectionProvider, - self::$useStaticReflectionProvider - ); - $setterReflectionProviderProvider->setReflectionProvider($reflectionProvider); - - $broker = new Broker( - $reflectionProvider, - self::getContainer()->getParameter('universalObjectCratesClasses') - ); - $this->getClassReflectionExtensionRegistryProvider()->setBroker($broker); - - return $reflectionProvider; - } - - private function createReflectionProviderByParameters( - ReflectionProvider $runtimeReflectionProvider, - ReflectionProvider $staticReflectionProvider, - bool $disableRuntimeReflectionProvider - ): ReflectionProvider - { - $reflectionProviderFactory = new ReflectionProviderFactory( - $runtimeReflectionProvider, - $staticReflectionProvider, - $disableRuntimeReflectionProvider - ); - - return $reflectionProviderFactory->create(); + return self::getContainer()->getByType(ReflectionProvider::class); } private static function getPhpStormStubsSourceStubber(): PhpStormStubsSourceStubber @@ -194,212 +127,6 @@ private static function getPhpStormStubsSourceStubber(): PhpStormStubsSourceStub return self::$phpStormStubsSourceStubber; } - private function createRuntimeReflectionProvider( - ReflectionProvider\ReflectionProviderProvider $reflectionProviderProvider - ): ReflectionProvider - { - $functionCallStatementFinder = new FunctionCallStatementFinder(); - $parser = $this->getParser(); - $cache = new Cache(new MemoryCacheStorage()); - $phpDocStringResolver = self::getContainer()->getByType(PhpDocStringResolver::class); - $phpDocNodeResolver = self::getContainer()->getByType(PhpDocNodeResolver::class); - $currentWorkingDirectory = $this->getCurrentWorkingDirectory(); - $fileHelper = new FileHelper($currentWorkingDirectory); - $anonymousClassNameHelper = new AnonymousClassNameHelper(new FileHelper($currentWorkingDirectory), new SimpleRelativePathHelper($fileHelper->normalizePath($currentWorkingDirectory, '/'))); - $fileTypeMapper = new FileTypeMapper($reflectionProviderProvider, $parser, $phpDocStringResolver, $phpDocNodeResolver, $cache, $anonymousClassNameHelper); - $classReflectionExtensionRegistryProvider = $this->getClassReflectionExtensionRegistryProvider(); - $functionReflectionFactory = $this->getFunctionReflectionFactory( - $functionCallStatementFinder, - $cache - ); - $reflectionProvider = new ClassBlacklistReflectionProvider( - new RuntimeReflectionProvider( - $reflectionProviderProvider, - $classReflectionExtensionRegistryProvider, - $functionReflectionFactory, - $fileTypeMapper, - self::getContainer()->getByType(PhpDocInheritanceResolver::class), - self::getContainer()->getByType(PhpVersion::class), - self::getContainer()->getByType(NativeFunctionReflectionProvider::class), - self::getContainer()->getByType(StubPhpDocProvider::class), - self::getContainer()->getByType(PhpStormStubsSourceStubber::class) - ), - self::getPhpStormStubsSourceStubber(), - [ - '#^PhpParser\\\\#', - '#^PHPStan\\\\#', - '#^Hoa\\\\#', - ], - null - ); - $this->setUpReflectionProvider( - $reflectionProviderProvider, - $classReflectionExtensionRegistryProvider, - $functionCallStatementFinder, - $parser, - $cache, - $fileTypeMapper - ); - - return $reflectionProvider; - } - - private function setUpReflectionProvider( - ReflectionProvider\ReflectionProviderProvider $reflectionProviderProvider, - DirectClassReflectionExtensionRegistryProvider $classReflectionExtensionRegistryProvider, - FunctionCallStatementFinder $functionCallStatementFinder, - \PHPStan\Parser\Parser $parser, - Cache $cache, - FileTypeMapper $fileTypeMapper - ): void - { - $methodReflectionFactory = new class($parser, $functionCallStatementFinder, $cache, $reflectionProviderProvider) implements PhpMethodReflectionFactory { - - private \PHPStan\Parser\Parser $parser; - - private \PHPStan\Parser\FunctionCallStatementFinder $functionCallStatementFinder; - - private \PHPStan\Cache\Cache $cache; - - private ReflectionProvider\ReflectionProviderProvider $reflectionProviderProvider; - - public function __construct( - Parser $parser, - FunctionCallStatementFinder $functionCallStatementFinder, - Cache $cache, - ReflectionProvider\ReflectionProviderProvider $reflectionProviderProvider - ) - { - $this->parser = $parser; - $this->functionCallStatementFinder = $functionCallStatementFinder; - $this->cache = $cache; - $this->reflectionProviderProvider = $reflectionProviderProvider; - } - - /** - * @param ClassReflection $declaringClass - * @param ClassReflection|null $declaringTrait - * @param \PHPStan\Reflection\Php\BuiltinMethodReflection $reflection - * @param TemplateTypeMap $templateTypeMap - * @param Type[] $phpDocParameterTypes - * @param Type|null $phpDocReturnType - * @param Type|null $phpDocThrowType - * @param string|null $deprecatedDescription - * @param bool $isDeprecated - * @param bool $isInternal - * @param bool $isFinal - * @param string|null $stubPhpDocString - * @param bool|null $isPure - * @return PhpMethodReflection - */ - public function create( - ClassReflection $declaringClass, - ?ClassReflection $declaringTrait, - \PHPStan\Reflection\Php\BuiltinMethodReflection $reflection, - TemplateTypeMap $templateTypeMap, - array $phpDocParameterTypes, - ?Type $phpDocReturnType, - ?Type $phpDocThrowType, - ?string $deprecatedDescription, - bool $isDeprecated, - bool $isInternal, - bool $isFinal, - ?string $stubPhpDocString, - ?bool $isPure = null - ): PhpMethodReflection - { - return new PhpMethodReflection( - $declaringClass, - $declaringTrait, - $reflection, - $this->reflectionProviderProvider->getReflectionProvider(), - $this->parser, - $this->functionCallStatementFinder, - $this->cache, - $templateTypeMap, - $phpDocParameterTypes, - $phpDocReturnType, - $phpDocThrowType, - $deprecatedDescription, - $isDeprecated, - $isInternal, - $isFinal, - $stubPhpDocString, - $isPure - ); - } - - }; - $phpDocInheritanceResolver = new PhpDocInheritanceResolver($fileTypeMapper); - $annotationsMethodsClassReflectionExtension = new AnnotationsMethodsClassReflectionExtension(); - $annotationsPropertiesClassReflectionExtension = new AnnotationsPropertiesClassReflectionExtension(); - $signatureMapProvider = self::getContainer()->getByType(SignatureMapProvider::class); - $phpExtension = new PhpClassReflectionExtension(self::getContainer()->getByType(ScopeFactory::class), self::getContainer()->getByType(NodeScopeResolver::class), $methodReflectionFactory, $phpDocInheritanceResolver, $annotationsMethodsClassReflectionExtension, $annotationsPropertiesClassReflectionExtension, $signatureMapProvider, $parser, self::getContainer()->getByType(StubPhpDocProvider::class), $reflectionProviderProvider, $fileTypeMapper, true, []); - $classReflectionExtensionRegistryProvider->addPropertiesClassReflectionExtension($phpExtension); - $classReflectionExtensionRegistryProvider->addPropertiesClassReflectionExtension(new UniversalObjectCratesClassReflectionExtension([\stdClass::class])); - $classReflectionExtensionRegistryProvider->addPropertiesClassReflectionExtension(new MixinPropertiesClassReflectionExtension([])); - $classReflectionExtensionRegistryProvider->addPropertiesClassReflectionExtension(new SimpleXMLElementClassPropertyReflectionExtension()); - $classReflectionExtensionRegistryProvider->addPropertiesClassReflectionExtension($annotationsPropertiesClassReflectionExtension); - $classReflectionExtensionRegistryProvider->addMethodsClassReflectionExtension($phpExtension); - $classReflectionExtensionRegistryProvider->addMethodsClassReflectionExtension(new MixinMethodsClassReflectionExtension([])); - $classReflectionExtensionRegistryProvider->addMethodsClassReflectionExtension($annotationsMethodsClassReflectionExtension); - $classReflectionExtensionRegistryProvider->addMethodsClassReflectionExtension(new SoapClientMethodsClassReflectionExtension()); - } - - private function createStaticReflectionProvider( - ReflectionProvider\ReflectionProviderProvider $reflectionProviderProvider - ): ReflectionProvider - { - $parser = $this->getParser(); - $phpDocStringResolver = self::getContainer()->getByType(PhpDocStringResolver::class); - $phpDocNodeResolver = self::getContainer()->getByType(PhpDocNodeResolver::class); - $currentWorkingDirectory = $this->getCurrentWorkingDirectory(); - $cache = new Cache(new MemoryCacheStorage()); - $fileHelper = new FileHelper($currentWorkingDirectory); - $relativePathHelper = new SimpleRelativePathHelper($currentWorkingDirectory); - $anonymousClassNameHelper = new AnonymousClassNameHelper($fileHelper, new SimpleRelativePathHelper($fileHelper->normalizePath($currentWorkingDirectory, '/'))); - $fileTypeMapper = new FileTypeMapper($reflectionProviderProvider, $parser, $phpDocStringResolver, $phpDocNodeResolver, $cache, $anonymousClassNameHelper); - $functionCallStatementFinder = new FunctionCallStatementFinder(); - $functionReflectionFactory = $this->getFunctionReflectionFactory( - $functionCallStatementFinder, - $cache - ); - - [$classReflector, $functionReflector, $constantReflector] = self::getReflectors(); - - $classReflectionExtensionRegistryProvider = $this->getClassReflectionExtensionRegistryProvider(); - - $reflectionProvider = new BetterReflectionProvider( - $reflectionProviderProvider, - $classReflectionExtensionRegistryProvider, - $classReflector, - $fileTypeMapper, - self::getContainer()->getByType(PhpDocInheritanceResolver::class), - self::getContainer()->getByType(PhpVersion::class), - self::getContainer()->getByType(NativeFunctionReflectionProvider::class), - self::getContainer()->getByType(StubPhpDocProvider::class), - $functionReflectionFactory, - $relativePathHelper, - $anonymousClassNameHelper, - self::getContainer()->getByType(Standard::class), - $fileHelper, - $functionReflector, - $constantReflector, - self::getPhpStormStubsSourceStubber() - ); - - $this->setUpReflectionProvider( - $reflectionProviderProvider, - $classReflectionExtensionRegistryProvider, - $functionCallStatementFinder, - $parser, - $cache, - $fileTypeMapper - ); - - return $reflectionProvider; - } - /** * @return array{ClassReflector, FunctionReflector, ConstantReflector} */ @@ -461,86 +188,9 @@ public static function getReflectors(): array return self::$reflectors; } - private function getFunctionReflectionFactory( - FunctionCallStatementFinder $functionCallStatementFinder, - Cache $cache - ): FunctionReflectionFactory + public function getClassReflectionExtensionRegistryProvider(): ClassReflectionExtensionRegistryProvider { - return new class($this->getParser(), $functionCallStatementFinder, $cache) implements FunctionReflectionFactory { - - private \PHPStan\Parser\Parser $parser; - - private \PHPStan\Parser\FunctionCallStatementFinder $functionCallStatementFinder; - - private \PHPStan\Cache\Cache $cache; - - public function __construct( - Parser $parser, - FunctionCallStatementFinder $functionCallStatementFinder, - Cache $cache - ) - { - $this->parser = $parser; - $this->functionCallStatementFinder = $functionCallStatementFinder; - $this->cache = $cache; - } - - /** - * @param \ReflectionFunction $function - * @param TemplateTypeMap $templateTypeMap - * @param Type[] $phpDocParameterTypes - * @param Type|null $phpDocReturnType - * @param Type|null $phpDocThrowType - * @param string|null $deprecatedDescription - * @param bool $isDeprecated - * @param bool $isInternal - * @param bool $isFinal - * @param string|false $filename - * @param bool|null $isPure - * @return PhpFunctionReflection - */ - public function create( - \ReflectionFunction $function, - TemplateTypeMap $templateTypeMap, - array $phpDocParameterTypes, - ?Type $phpDocReturnType, - ?Type $phpDocThrowType, - ?string $deprecatedDescription, - bool $isDeprecated, - bool $isInternal, - bool $isFinal, - $filename, - ?bool $isPure = null - ): PhpFunctionReflection - { - return new PhpFunctionReflection( - $function, - $this->parser, - $this->functionCallStatementFinder, - $this->cache, - $templateTypeMap, - $phpDocParameterTypes, - $phpDocReturnType, - $phpDocThrowType, - $deprecatedDescription, - $isDeprecated, - $isInternal, - $isFinal, - $filename, - $isPure - ); - } - - }; - } - - public function getClassReflectionExtensionRegistryProvider(): DirectClassReflectionExtensionRegistryProvider - { - if ($this->classReflectionExtensionRegistryProvider === null) { - $this->classReflectionExtensionRegistryProvider = new DirectClassReflectionExtensionRegistryProvider([], []); - } - - return $this->classReflectionExtensionRegistryProvider; + return self::getContainer()->getByType(ClassReflectionExtensionRegistryProvider::class); } public function createScopeFactory(ReflectionProvider $reflectionProvider, TypeSpecifier $typeSpecifier): ScopeFactory @@ -583,11 +233,6 @@ protected function shouldTreatPhpDocTypesAsCertain(): bool return true; } - public function getCurrentWorkingDirectory(): string - { - return $this->getFileHelper()->normalizePath(__DIR__ . '/../..'); - } - /** * @param \PhpParser\PrettyPrinter\Standard $printer * @param \PHPStan\Reflection\ReflectionProvider $reflectionProvider diff --git a/src/Testing/RuleTestCase.php b/src/Testing/RuleTestCase.php index 3c10a0d4de..23cd4b6a29 100644 --- a/src/Testing/RuleTestCase.php +++ b/src/Testing/RuleTestCase.php @@ -7,19 +7,12 @@ use PHPStan\Analyser\FileAnalyser; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\TypeSpecifier; -use PHPStan\Broker\AnonymousClassNameHelper; -use PHPStan\Cache\Cache; use PHPStan\Dependency\DependencyResolver; -use PHPStan\Dependency\ExportedNodeResolver; use PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider; use PHPStan\File\FileHelper; -use PHPStan\File\SimpleRelativePathHelper; use PHPStan\Php\PhpVersion; use PHPStan\PhpDoc\PhpDocInheritanceResolver; -use PHPStan\PhpDoc\PhpDocNodeResolver; -use PHPStan\PhpDoc\PhpDocStringResolver; use PHPStan\PhpDoc\StubPhpDocProvider; -use PHPStan\Reflection\ReflectionProvider\DirectReflectionProviderProvider; use PHPStan\Rules\Registry; use PHPStan\Rules\Rule; use PHPStan\Type\FileTypeMapper; @@ -64,24 +57,16 @@ private function getAnalyser(): Analyser $this->getMethodTypeSpecifyingExtensions(), $this->getStaticMethodTypeSpecifyingExtensions() ); - $currentWorkingDirectory = $this->getCurrentWorkingDirectory(); - $fileHelper = new FileHelper($currentWorkingDirectory); - $currentWorkingDirectory = $fileHelper->normalizePath($currentWorkingDirectory, '/'); - $fileHelper = new FileHelper($currentWorkingDirectory); - $relativePathHelper = new SimpleRelativePathHelper($currentWorkingDirectory); - $anonymousClassNameHelper = new AnonymousClassNameHelper($fileHelper, $relativePathHelper); - $fileTypeMapper = new FileTypeMapper(new DirectReflectionProviderProvider($broker), $this->getParser(), self::getContainer()->getByType(PhpDocStringResolver::class), self::getContainer()->getByType(PhpDocNodeResolver::class), $this->createMock(Cache::class), $anonymousClassNameHelper); - $phpDocInheritanceResolver = new PhpDocInheritanceResolver($fileTypeMapper); $nodeScopeResolver = new NodeScopeResolver( $broker, self::getReflectors()[0], $this->getClassReflectionExtensionRegistryProvider(), $this->getParser(), - $fileTypeMapper, + self::getContainer()->getByType(FileTypeMapper::class), self::getContainer()->getByType(StubPhpDocProvider::class), self::getContainer()->getByType(PhpVersion::class), - $phpDocInheritanceResolver, - $fileHelper, + self::getContainer()->getByType(PhpDocInheritanceResolver::class), + self::getContainer()->getByType(FileHelper::class), $typeSpecifier, self::getContainer()->getByType(DynamicThrowTypeExtensionProvider::class), $this->shouldPolluteScopeWithLoopInitialAssignments(), @@ -94,7 +79,7 @@ private function getAnalyser(): Analyser $this->createScopeFactory($broker, $typeSpecifier), $nodeScopeResolver, $this->getParser(), - new DependencyResolver($fileHelper, $broker, new ExportedNodeResolver($fileTypeMapper, $printer)), + self::getContainer()->getByType(DependencyResolver::class), true ); $this->analyser = new Analyser( @@ -108,22 +93,6 @@ private function getAnalyser(): Analyser return $this->analyser; } - /** - * @return \PHPStan\Type\MethodTypeSpecifyingExtension[] - */ - protected function getMethodTypeSpecifyingExtensions(): array - { - return []; - } - - /** - * @return \PHPStan\Type\StaticMethodTypeSpecifyingExtension[] - */ - protected function getStaticMethodTypeSpecifyingExtensions(): array - { - return []; - } - /** * @param string[] $files * @param mixed[] $expectedErrors diff --git a/src/Testing/TestCase.neon b/src/Testing/TestCase.neon new file mode 100644 index 0000000000..923de028e2 --- /dev/null +++ b/src/Testing/TestCase.neon @@ -0,0 +1,6 @@ +parameters: + inferPrivatePropertyTypeFromConstructor: true +services: + cacheStorage: + class: PHPStan\Cache\MemoryCacheStorage + arguments!: [] diff --git a/src/Testing/TypeInferenceTestCase.php b/src/Testing/TypeInferenceTestCase.php index 30bcdcbdb9..cda75c8c43 100644 --- a/src/Testing/TypeInferenceTestCase.php +++ b/src/Testing/TypeInferenceTestCase.php @@ -9,17 +9,11 @@ use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\ScopeContext; -use PHPStan\Broker\AnonymousClassNameHelper; -use PHPStan\Cache\Cache; use PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider; use PHPStan\File\FileHelper; -use PHPStan\File\SimpleRelativePathHelper; use PHPStan\Php\PhpVersion; use PHPStan\PhpDoc\PhpDocInheritanceResolver; -use PHPStan\PhpDoc\PhpDocNodeResolver; -use PHPStan\PhpDoc\PhpDocStringResolver; use PHPStan\PhpDoc\StubPhpDocProvider; -use PHPStan\Reflection\ReflectionProvider\DirectReflectionProviderProvider; use PHPStan\TrinaryLogic; use PHPStan\Type\FileTypeMapper; use PHPStan\Type\VerbosityLevel; @@ -43,26 +37,20 @@ public function processFile( array $dynamicConstantNames = [] ): void { - $phpDocStringResolver = self::getContainer()->getByType(PhpDocStringResolver::class); - $phpDocNodeResolver = self::getContainer()->getByType(PhpDocNodeResolver::class); - $printer = new \PhpParser\PrettyPrinter\Standard(); $reflectionProvider = $this->createReflectionProvider(); $typeSpecifier = $this->createTypeSpecifier($printer, $reflectionProvider, $methodTypeSpecifyingExtensions, $staticMethodTypeSpecifyingExtensions); - $currentWorkingDirectory = $this->getCurrentWorkingDirectory(); - $fileHelper = new FileHelper($currentWorkingDirectory); - $fileTypeMapper = new FileTypeMapper(new DirectReflectionProviderProvider($reflectionProvider), $this->getParser(), $phpDocStringResolver, $phpDocNodeResolver, $this->createMock(Cache::class), new AnonymousClassNameHelper($fileHelper, new SimpleRelativePathHelper($currentWorkingDirectory))); - $phpDocInheritanceResolver = new PhpDocInheritanceResolver($fileTypeMapper); + $fileHelper = self::getContainer()->getByType(FileHelper::class); $resolver = new NodeScopeResolver( $reflectionProvider, self::getReflectors()[0], $this->getClassReflectionExtensionRegistryProvider(), $this->getParser(), - $fileTypeMapper, + self::getContainer()->getByType(FileTypeMapper::class), self::getContainer()->getByType(StubPhpDocProvider::class), self::getContainer()->getByType(PhpVersion::class), - $phpDocInheritanceResolver, - $fileHelper, + self::getContainer()->getByType(PhpDocInheritanceResolver::class), + self::getContainer()->getByType(FileHelper::class), $typeSpecifier, self::getContainer()->getByType(DynamicThrowTypeExtensionProvider::class), true, diff --git a/tests/PHPStan/Analyser/AnalyserTest.php b/tests/PHPStan/Analyser/AnalyserTest.php index e74fd612e2..15b72d9bd3 100644 --- a/tests/PHPStan/Analyser/AnalyserTest.php +++ b/tests/PHPStan/Analyser/AnalyserTest.php @@ -3,22 +3,15 @@ namespace PHPStan\Analyser; use PhpParser\NodeVisitor\NodeConnectingVisitor; -use PHPStan\Broker\AnonymousClassNameHelper; -use PHPStan\Cache\Cache; use PHPStan\Command\IgnoredRegexValidator; use PHPStan\Dependency\DependencyResolver; use PHPStan\Dependency\ExportedNodeResolver; use PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider; -use PHPStan\File\RelativePathHelper; use PHPStan\NodeVisitor\StatementOrderVisitor; use PHPStan\Parser\RichParser; use PHPStan\Php\PhpVersion; use PHPStan\PhpDoc\PhpDocInheritanceResolver; -use PHPStan\PhpDoc\PhpDocNodeResolver; -use PHPStan\PhpDoc\PhpDocStringResolver; - use PHPStan\PhpDoc\StubPhpDocProvider; -use PHPStan\Reflection\ReflectionProvider\DirectReflectionProviderProvider; use PHPStan\Rules\AlwaysFailRule; use PHPStan\Rules\Registry; use PHPStan\Type\FileTypeMapper; @@ -494,12 +487,8 @@ private function createAnalyser(bool $reportUnmatchedIgnoredErrors): \PHPStan\An $printer = new \PhpParser\PrettyPrinter\Standard(); $fileHelper = $this->getFileHelper(); - /** @var RelativePathHelper $relativePathHelper */ - $relativePathHelper = self::getContainer()->getService('simpleRelativePathHelper'); - $phpDocStringResolver = self::getContainer()->getByType(PhpDocStringResolver::class); - $phpDocNodeResolver = self::getContainer()->getByType(PhpDocNodeResolver::class); $typeSpecifier = $this->createTypeSpecifier($printer, $broker); - $fileTypeMapper = new FileTypeMapper(new DirectReflectionProviderProvider($broker), $this->getParser(), $phpDocStringResolver, $phpDocNodeResolver, $this->createMock(Cache::class), new AnonymousClassNameHelper($fileHelper, $relativePathHelper)); + $fileTypeMapper = self::getContainer()->getByType(FileTypeMapper::class); $phpDocInheritanceResolver = new PhpDocInheritanceResolver($fileTypeMapper); $nodeScopeResolver = new NodeScopeResolver( From 239291a91089da6684d8c52e58b9a77ceb178071 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 14 Sep 2021 17:00:51 +0200 Subject: [PATCH 0292/1284] [BCB] PHPStanTestCase - TypeSpecifier extensions can no longer be provided by overriding methods from PHPStanTestCase. Use getAdditionalConfigFiles() instead. --- src/Testing/PHPStanTestCase.php | 113 +----------------- src/Testing/RuleTestCase.php | 15 +-- src/Testing/TypeInferenceTestCase.php | 7 +- tests/PHPStan/Analyser/AnalyserTest.php | 2 +- .../Analyser/LegacyNodeScopeResolverTest.php | 50 -------- tests/PHPStan/Analyser/TypeSpecifierTest.php | 2 +- .../ImpossibleCheckTypeMethodCallRuleTest.php | 1 + 7 files changed, 10 insertions(+), 180 deletions(-) diff --git a/src/Testing/PHPStanTestCase.php b/src/Testing/PHPStanTestCase.php index f149be64ae..0b6446b50d 100644 --- a/src/Testing/PHPStanTestCase.php +++ b/src/Testing/PHPStanTestCase.php @@ -2,24 +2,14 @@ namespace PHPStan\Testing; -use Composer\Autoload\ClassLoader; -use PhpParser\PrettyPrinter\Standard; use PHPStan\Analyser\DirectScopeFactory; use PHPStan\Analyser\MutatingScope; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\ScopeFactory; use PHPStan\Analyser\TypeSpecifier; -use PHPStan\Analyser\TypeSpecifierFactory; use PHPStan\BetterReflection\Reflector\ClassReflector; use PHPStan\BetterReflection\Reflector\ConstantReflector; use PHPStan\BetterReflection\Reflector\FunctionReflector; -use PHPStan\BetterReflection\SourceLocator\Ast\Locator; -use PHPStan\BetterReflection\SourceLocator\SourceStubber\PhpStormStubsSourceStubber; -use PHPStan\BetterReflection\SourceLocator\SourceStubber\ReflectionSourceStubber; -use PHPStan\BetterReflection\SourceLocator\Type\AggregateSourceLocator; -use PHPStan\BetterReflection\SourceLocator\Type\EvaledCodeSourceLocator; -use PHPStan\BetterReflection\SourceLocator\Type\MemoizingSourceLocator; -use PHPStan\BetterReflection\SourceLocator\Type\PhpInternalSourceLocator; use PHPStan\DependencyInjection\Container; use PHPStan\DependencyInjection\ContainerFactory; use PHPStan\DependencyInjection\Reflection\ClassReflectionExtensionRegistryProvider; @@ -27,15 +17,8 @@ use PHPStan\DependencyInjection\Type\OperatorTypeSpecifyingExtensionRegistryProvider; use PHPStan\File\FileHelper; use PHPStan\Parser\CachedParser; -use PHPStan\Parser\PhpParserDecorator; use PHPStan\PhpDoc\TypeNodeResolver; use PHPStan\PhpDoc\TypeStringResolver; -use PHPStan\Reflection\BetterReflection\Reflector\MemoizingClassReflector; -use PHPStan\Reflection\BetterReflection\Reflector\MemoizingConstantReflector; -use PHPStan\Reflection\BetterReflection\Reflector\MemoizingFunctionReflector; -use PHPStan\Reflection\BetterReflection\SourceLocator\AutoloadSourceLocator; -use PHPStan\Reflection\BetterReflection\SourceLocator\ComposerJsonAndInstalledJsonSourceLocatorMaker; -use PHPStan\Reflection\BetterReflection\SourceLocator\FileNodesFetcher; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\Properties\PropertyReflectionFinder; use PHPStan\Type\TypeAliasResolver; @@ -50,12 +33,6 @@ abstract class PHPStanTestCase extends \PHPUnit\Framework\TestCase /** @var array */ private static array $containers = []; - /** @var array{ClassReflector, FunctionReflector, ConstantReflector}|null */ - private static $reflectors; - - /** @var PhpStormStubsSourceStubber|null */ - private static $phpStormStubsSourceStubber; - /** @api */ public static function getContainer(): Container { @@ -118,74 +95,16 @@ public function createReflectionProvider(): ReflectionProvider return self::getContainer()->getByType(ReflectionProvider::class); } - private static function getPhpStormStubsSourceStubber(): PhpStormStubsSourceStubber - { - if (self::$phpStormStubsSourceStubber === null) { - self::$phpStormStubsSourceStubber = self::getContainer()->getByType(PhpStormStubsSourceStubber::class); - } - - return self::$phpStormStubsSourceStubber; - } - /** * @return array{ClassReflector, FunctionReflector, ConstantReflector} */ public static function getReflectors(): array { - if (self::$reflectors !== null) { - return self::$reflectors; - } - - if (!class_exists(ClassLoader::class)) { - self::fail('Composer ClassLoader is unknown'); - } - - $classLoaderReflection = new \ReflectionClass(ClassLoader::class); - if ($classLoaderReflection->getFileName() === false) { - self::fail('Unknown ClassLoader filename'); - } - - $composerProjectPath = dirname($classLoaderReflection->getFileName(), 3); - if (!is_file($composerProjectPath . '/composer.json')) { - self::fail(sprintf('composer.json not found in directory %s', $composerProjectPath)); - } - - $composerJsonAndInstalledJsonSourceLocatorMaker = self::getContainer()->getByType(ComposerJsonAndInstalledJsonSourceLocatorMaker::class); - $composerSourceLocator = $composerJsonAndInstalledJsonSourceLocatorMaker->create($composerProjectPath); - if ($composerSourceLocator === null) { - self::fail('Could not create composer source locator'); - } - - // these need to be synced with TestCase-staticReflection.neon file and TestCaseSourceLocatorFactory - - $locators = [ - $composerSourceLocator, + return [ + self::getContainer()->getService('betterReflectionClassReflector'), + self::getContainer()->getService('betterReflectionFunctionReflector'), + self::getContainer()->getService('betterReflectionConstantReflector'), ]; - - $phpParser = new PhpParserDecorator(self::getContainer()->getByType(CachedParser::class)); - - /** @var FunctionReflector $functionReflector */ - $functionReflector = null; - $astLocator = new Locator($phpParser, static function () use (&$functionReflector): FunctionReflector { - return $functionReflector; - }); - $astPhp8Locator = new Locator(self::getContainer()->getService('php8PhpParser'), static function () use (&$functionReflector): FunctionReflector { - return $functionReflector; - }); - $reflectionSourceStubber = new ReflectionSourceStubber(); - $locators[] = new PhpInternalSourceLocator($astPhp8Locator, self::getPhpStormStubsSourceStubber()); - $locators[] = new AutoloadSourceLocator(self::getContainer()->getByType(FileNodesFetcher::class)); - $locators[] = new PhpInternalSourceLocator($astLocator, $reflectionSourceStubber); - $locators[] = new EvaledCodeSourceLocator($astLocator, $reflectionSourceStubber); - $sourceLocator = new MemoizingSourceLocator(new AggregateSourceLocator($locators)); - - $classReflector = new MemoizingClassReflector($sourceLocator); - $functionReflector = new MemoizingFunctionReflector($sourceLocator, $classReflector); - $constantReflector = new MemoizingConstantReflector($sourceLocator, $classReflector); - - self::$reflectors = [$classReflector, $functionReflector, $constantReflector]; - - return self::$reflectors; } public function getClassReflectionExtensionRegistryProvider(): ClassReflectionExtensionRegistryProvider @@ -233,30 +152,6 @@ protected function shouldTreatPhpDocTypesAsCertain(): bool return true; } - /** - * @param \PhpParser\PrettyPrinter\Standard $printer - * @param \PHPStan\Reflection\ReflectionProvider $reflectionProvider - * @param \PHPStan\Type\MethodTypeSpecifyingExtension[] $methodTypeSpecifyingExtensions - * @param \PHPStan\Type\StaticMethodTypeSpecifyingExtension[] $staticMethodTypeSpecifyingExtensions - * @return \PHPStan\Analyser\TypeSpecifier - */ - public function createTypeSpecifier( - Standard $printer, - ReflectionProvider $reflectionProvider, - array $methodTypeSpecifyingExtensions = [], - array $staticMethodTypeSpecifyingExtensions = [] - ): TypeSpecifier - { - return new TypeSpecifier( - $printer, - $reflectionProvider, - true, - self::getContainer()->getServicesByTag(TypeSpecifierFactory::FUNCTION_TYPE_SPECIFYING_EXTENSION_TAG), - array_merge($methodTypeSpecifyingExtensions, self::getContainer()->getServicesByTag(TypeSpecifierFactory::METHOD_TYPE_SPECIFYING_EXTENSION_TAG)), - array_merge($staticMethodTypeSpecifyingExtensions, self::getContainer()->getServicesByTag(TypeSpecifierFactory::STATIC_METHOD_TYPE_SPECIFYING_EXTENSION_TAG)) - ); - } - public function getFileHelper(): FileHelper { return self::getContainer()->getByType(FileHelper::class); diff --git a/src/Testing/RuleTestCase.php b/src/Testing/RuleTestCase.php index 23cd4b6a29..059f409081 100644 --- a/src/Testing/RuleTestCase.php +++ b/src/Testing/RuleTestCase.php @@ -34,12 +34,7 @@ abstract protected function getRule(): Rule; protected function getTypeSpecifier(): TypeSpecifier { - return $this->createTypeSpecifier( - new \PhpParser\PrettyPrinter\Standard(), - $this->createReflectionProvider(), - $this->getMethodTypeSpecifyingExtensions(), - $this->getStaticMethodTypeSpecifyingExtensions() - ); + return self::getContainer()->getService('typeSpecifier'); } private function getAnalyser(): Analyser @@ -50,13 +45,7 @@ private function getAnalyser(): Analyser ]); $broker = $this->createBroker(); - $printer = new \PhpParser\PrettyPrinter\Standard(); - $typeSpecifier = $this->createTypeSpecifier( - $printer, - $broker, - $this->getMethodTypeSpecifyingExtensions(), - $this->getStaticMethodTypeSpecifyingExtensions() - ); + $typeSpecifier = $this->getTypeSpecifier(); $nodeScopeResolver = new NodeScopeResolver( $broker, self::getReflectors()[0], diff --git a/src/Testing/TypeInferenceTestCase.php b/src/Testing/TypeInferenceTestCase.php index cda75c8c43..9b59fd06b0 100644 --- a/src/Testing/TypeInferenceTestCase.php +++ b/src/Testing/TypeInferenceTestCase.php @@ -25,21 +25,16 @@ abstract class TypeInferenceTestCase extends \PHPStan\Testing\PHPStanTestCase /** * @param string $file * @param callable(\PhpParser\Node, \PHPStan\Analyser\Scope): void $callback - * @param \PHPStan\Type\MethodTypeSpecifyingExtension[] $methodTypeSpecifyingExtensions - * @param \PHPStan\Type\StaticMethodTypeSpecifyingExtension[] $staticMethodTypeSpecifyingExtensions * @param string[] $dynamicConstantNames */ public function processFile( string $file, callable $callback, - array $methodTypeSpecifyingExtensions = [], - array $staticMethodTypeSpecifyingExtensions = [], array $dynamicConstantNames = [] ): void { - $printer = new \PhpParser\PrettyPrinter\Standard(); $reflectionProvider = $this->createReflectionProvider(); - $typeSpecifier = $this->createTypeSpecifier($printer, $reflectionProvider, $methodTypeSpecifyingExtensions, $staticMethodTypeSpecifyingExtensions); + $typeSpecifier = self::getContainer()->getService('typeSpecifier'); $fileHelper = self::getContainer()->getByType(FileHelper::class); $resolver = new NodeScopeResolver( $reflectionProvider, diff --git a/tests/PHPStan/Analyser/AnalyserTest.php b/tests/PHPStan/Analyser/AnalyserTest.php index 15b72d9bd3..81fbeccb53 100644 --- a/tests/PHPStan/Analyser/AnalyserTest.php +++ b/tests/PHPStan/Analyser/AnalyserTest.php @@ -487,7 +487,7 @@ private function createAnalyser(bool $reportUnmatchedIgnoredErrors): \PHPStan\An $printer = new \PhpParser\PrettyPrinter\Standard(); $fileHelper = $this->getFileHelper(); - $typeSpecifier = $this->createTypeSpecifier($printer, $broker); + $typeSpecifier = self::getContainer()->getService('typeSpecifier'); $fileTypeMapper = self::getContainer()->getByType(FileTypeMapper::class); $phpDocInheritanceResolver = new PhpDocInheritanceResolver($fileTypeMapper); diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 05b93d6684..8f8f4ba1b9 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -1464,8 +1464,6 @@ public function testVarAnnotations( __DIR__ . '/data/var-annotations.php', $description, $expression, - [], - [], 'die', [], false @@ -3430,8 +3428,6 @@ public function testLiteralArraysKeys( __DIR__ . '/data/literal-arrays-keys.php', $description, '$key', - [], - [], $evaluatedPointExpressionType ); } @@ -4182,8 +4178,6 @@ public function testSwitchGetClass( __DIR__ . '/data/switch-get-class.php', $description, $expression, - [], - [], $evaluatedPointExpression ); } @@ -4579,8 +4573,6 @@ public function testOverwritingVariable( __DIR__ . '/data/overwritingVariable.php', $description, $expression, - [], - [], $evaluatedPointExpressionType ); } @@ -4921,8 +4913,6 @@ public function testForeachObjectType( $file, $description, $expression, - [], - [], $evaluatedPointExpression ); } @@ -7096,8 +7086,6 @@ public function testTypeElimination( __DIR__ . '/data/type-elimination.php', $description, $expression, - [], - [], $evaluatedPointExpression ); } @@ -7752,8 +7740,6 @@ public function testForeachLoopVariables( __DIR__ . '/data/foreach-loop-variables.php', $description, $expression, - [], - [], $evaluatedPointExpression ); } @@ -7775,8 +7761,6 @@ public function testWhileLoopVariables( __DIR__ . '/data/while-loop-variables.php', $description, $expression, - [], - [], $evaluatedPointExpression ); } @@ -7798,8 +7782,6 @@ public function testForLoopVariables( __DIR__ . '/data/for-loop-variables.php', $description, $expression, - [], - [], $evaluatedPointExpression ); } @@ -7912,8 +7894,6 @@ public function testDoWhileLoopVariables( __DIR__ . '/data/do-while-loop-variables.php', $description, $expression, - [], - [], $evaluatedPointExpression ); } @@ -7950,8 +7930,6 @@ public function testMultipleClassesInOneFile( __DIR__ . '/data/multiple-classes-per-file.php', $description, $expression, - [], - [], $evaluatedPointExpression ); } @@ -8453,8 +8431,6 @@ public function testClosureWithUsePassedByReference( __DIR__ . '/data/closure-passed-by-reference.php', $description, $expression, - [], - [], $evaluatedPointExpression ); } @@ -8555,8 +8531,6 @@ public function testClosureWithUsePassedByReferenceReturn( __DIR__ . '/data/closure-passed-by-reference-return.php', $description, $expression, - [], - [], $evaluatedPointExpression ); } @@ -8589,8 +8563,6 @@ public function testClosureWithInferredTypehint( __DIR__ . '/data/closure-inferred-typehint.php', $description, $expression, - [], - [], 'die', [], false @@ -8892,8 +8864,6 @@ public function testSpecifiedFunctionCall( __DIR__ . '/data/specified-function-call.php', $description, $expression, - [], - [], $evaluatedPointExpression ); } @@ -9054,8 +9024,6 @@ public function testConstantTypeAfterDuplicateCondition( __DIR__ . '/data/constant-types-duplicate-condition.php', $description, $expression, - [], - [], $evaluatedPointExpression ); } @@ -9112,8 +9080,6 @@ public function testAnonymousClassName( __DIR__ . '/data/anonymous-class-name.php', $description, $expression, - [], - [], $evaluatedPointExpression ); } @@ -9185,8 +9151,6 @@ public function testDynamicConstants( __DIR__ . '/data/dynamic-constant.php', $description, $expression, - [], - [], 'die', [ 'DynamicConstants\\DynamicConstantClass::DYNAMIC_CONSTANT_IN_CLASS', @@ -9325,8 +9289,6 @@ public function testPropertyArrayAssignment( __DIR__ . '/data/property-array.php', $description, $expression, - [], - [], $evaluatedPointExpression ); } @@ -9460,8 +9422,6 @@ public function testGetParentClass( __DIR__ . '/data/get-parent-class.php', $description, $expression, - [], - [], $evaluatedPointExpression ); } @@ -9498,8 +9458,6 @@ public function testIsCountable( __DIR__ . '/data/is_countable.php', $description, $expression, - [], - [], $evaluatedPointExpression ); } @@ -10316,8 +10274,6 @@ public function testTryCatchScope( __DIR__ . '/data/try-catch-scope.php', $description, $expression, - [], - [], $evaluatedPointExpression, [], false @@ -10328,8 +10284,6 @@ public function testTryCatchScope( * @param string $file * @param string $description * @param string $expression - * @param \PHPStan\Type\MethodTypeSpecifyingExtension[] $methodTypeSpecifyingExtensions - * @param \PHPStan\Type\StaticMethodTypeSpecifyingExtension[] $staticMethodTypeSpecifyingExtensions * @param string $evaluatedPointExpression * @param string[] $dynamicConstantNames * @param bool $useCache @@ -10338,8 +10292,6 @@ private function assertTypes( string $file, string $description, string $expression, - array $methodTypeSpecifyingExtensions = [], - array $staticMethodTypeSpecifyingExtensions = [], string $evaluatedPointExpression = 'die', array $dynamicConstantNames = [], bool $useCache = true @@ -10375,8 +10327,6 @@ static function (\PhpParser\Node $node, Scope $scope) use ($file, $evaluatedPoin $assertType($scope); }, - $methodTypeSpecifyingExtensions, - $staticMethodTypeSpecifyingExtensions, $dynamicConstantNames ); } diff --git a/tests/PHPStan/Analyser/TypeSpecifierTest.php b/tests/PHPStan/Analyser/TypeSpecifierTest.php index 9f2644b2b4..cf8c1bd687 100644 --- a/tests/PHPStan/Analyser/TypeSpecifierTest.php +++ b/tests/PHPStan/Analyser/TypeSpecifierTest.php @@ -48,7 +48,7 @@ protected function setUp(): void { $broker = $this->createBroker(); $this->printer = new \PhpParser\PrettyPrinter\Standard(); - $this->typeSpecifier = $this->createTypeSpecifier($this->printer, $broker); + $this->typeSpecifier = self::getContainer()->getService('typeSpecifier'); $this->scope = $this->createScopeFactory($broker, $this->typeSpecifier)->create(ScopeContext::create('')); $this->scope = $this->scope->enterClass($broker->getClass('DateTime')); $this->scope = $this->scope->assignVariable('bar', new ObjectType('Bar')); diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php index 633a48b728..edd1b1a726 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php @@ -45,6 +45,7 @@ protected function shouldTreatPhpDocTypesAsCertain(): bool */ protected function getMethodTypeSpecifyingExtensions(): array { + // todo remove return [ new AssertionClassMethodTypeSpecifyingExtension(null), new class() implements MethodTypeSpecifyingExtension, From 3f40f5c7d828db17f9cd302eb0945736e06c7da6 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 15 Sep 2021 09:40:48 +0200 Subject: [PATCH 0293/1284] Reworked TypeSpecifyingExtension tests --- .../Analyser/LegacyNodeScopeResolverTest.php | 179 ------------------ .../TypeSpecifyingExtension-false.neon | 13 ++ .../TypeSpecifyingExtension-null.neon | 13 ++ .../TypeSpecifyingExtension-true.neon | 13 ++ ...cifyingExtensionTypeInferenceFalseTest.php | 39 ++++ ...ecifyingExtensionTypeInferenceNullTest.php | 39 ++++ ...ecifyingExtensionTypeInferenceTrueTest.php | 39 ++++ .../type-specifying-extensions-1-false.php | 14 ++ ... => type-specifying-extensions-1-null.php} | 5 +- .../type-specifying-extensions-1-true.php | 14 ++ .../type-specifying-extensions-2-false.php | 14 ++ .../type-specifying-extensions-2-null.php | 14 ++ .../type-specifying-extensions-2-true.php | 14 ++ ...=> type-specifying-extensions-3-false.php} | 6 +- ... => type-specifying-extensions-3-null.php} | 5 +- .../type-specifying-extensions-3-true.php | 13 ++ .../ImpossibleCheckTypeMethodCallRuleTest.php | 164 +--------------- ...sibleCheckTypeStaticMethodCallRuleTest.php | 21 +- .../TestMethodTypeSpecifyingExtensions.php | 151 +++++++++++++++ .../impossible-check-type-method-call.neon | 19 ++ ...ossible-check-type-static-method-call.neon | 9 + 21 files changed, 444 insertions(+), 354 deletions(-) create mode 100644 tests/PHPStan/Analyser/TypeSpecifyingExtension-false.neon create mode 100644 tests/PHPStan/Analyser/TypeSpecifyingExtension-null.neon create mode 100644 tests/PHPStan/Analyser/TypeSpecifyingExtension-true.neon create mode 100644 tests/PHPStan/Analyser/TypeSpecifyingExtensionTypeInferenceFalseTest.php create mode 100644 tests/PHPStan/Analyser/TypeSpecifyingExtensionTypeInferenceNullTest.php create mode 100644 tests/PHPStan/Analyser/TypeSpecifyingExtensionTypeInferenceTrueTest.php create mode 100644 tests/PHPStan/Analyser/data/type-specifying-extensions-1-false.php rename tests/PHPStan/Analyser/data/{type-specifying-extensions.php => type-specifying-extensions-1-null.php} (68%) create mode 100644 tests/PHPStan/Analyser/data/type-specifying-extensions-1-true.php create mode 100644 tests/PHPStan/Analyser/data/type-specifying-extensions-2-false.php create mode 100644 tests/PHPStan/Analyser/data/type-specifying-extensions-2-null.php create mode 100644 tests/PHPStan/Analyser/data/type-specifying-extensions-2-true.php rename tests/PHPStan/Analyser/data/{type-specifying-extensions2.php => type-specifying-extensions-3-false.php} (68%) rename tests/PHPStan/Analyser/data/{type-specifying-extensions3.php => type-specifying-extensions-3-null.php} (67%) create mode 100644 tests/PHPStan/Analyser/data/type-specifying-extensions-3-true.php create mode 100644 tests/PHPStan/Rules/Comparison/data/TestMethodTypeSpecifyingExtensions.php create mode 100644 tests/PHPStan/Rules/Comparison/impossible-check-type-method-call.neon create mode 100644 tests/PHPStan/Rules/Comparison/impossible-check-type-static-method-call.neon diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 8f8f4ba1b9..6dd892f4b7 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -10,8 +10,6 @@ use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Testing\TypeInferenceTestCase; -use PHPStan\Tests\AssertionClassMethodTypeSpecifyingExtension; -use PHPStan\Tests\AssertionClassStaticMethodTypeSpecifyingExtension; use PHPStan\TrinaryLogic; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantFloatType; @@ -6379,183 +6377,6 @@ public function testSpecifiedTypesUsingIsFunctions( ); } - public function dataTypeSpecifyingExtensions(): array - { - return [ - [ - 'string', - '$foo', - true, - ], - [ - 'int', - '$bar', - true, - ], - [ - 'string|null', - '$foo', - false, - ], - [ - 'int|null', - '$bar', - false, - ], - [ - 'string', - '$foo', - null, - ], - [ - 'int', - '$bar', - null, - ], - ]; - } - - /** - * @dataProvider dataTypeSpecifyingExtensions - * @param string $description - * @param string $expression - * @param bool|null $nullContext - */ - public function testTypeSpecifyingExtensions( - string $description, - string $expression, - ?bool $nullContext - ): void - { - $this->assertTypes( - __DIR__ . '/data/type-specifying-extensions.php', - $description, - $expression, - [new AssertionClassMethodTypeSpecifyingExtension($nullContext)], - [new AssertionClassStaticMethodTypeSpecifyingExtension($nullContext)], - 'die', - [], - false - ); - } - - public function dataTypeSpecifyingExtensions2(): array - { - return [ - [ - 'string|null', - '$foo', - true, - ], - [ - 'int|null', - '$bar', - true, - ], - [ - 'string|null', - '$foo', - false, - ], - [ - 'int|null', - '$bar', - false, - ], - [ - 'string|null', - '$foo', - null, - ], - [ - 'int|null', - '$bar', - null, - ], - ]; - } - - /** - * @dataProvider dataTypeSpecifyingExtensions2 - * @param string $description - * @param string $expression - * @param bool|null $nullContext - */ - public function testTypeSpecifyingExtensions2( - string $description, - string $expression, - ?bool $nullContext - ): void - { - $this->assertTypes( - __DIR__ . '/data/type-specifying-extensions2.php', - $description, - $expression, - [new AssertionClassMethodTypeSpecifyingExtension($nullContext)], - [new AssertionClassStaticMethodTypeSpecifyingExtension($nullContext)] - ); - } - - public function dataTypeSpecifyingExtensions3(): array - { - return [ - [ - 'string', - '$foo', - false, - ], - [ - 'int', - '$bar', - false, - ], - [ - 'string|null', - '$foo', - true, - ], - [ - 'int|null', - '$bar', - true, - ], - [ - 'string', - '$foo', - null, - ], - [ - 'int', - '$bar', - null, - ], - ]; - } - - /** - * @dataProvider dataTypeSpecifyingExtensions3 - * @param string $description - * @param string $expression - * @param bool|null $nullContext - */ - public function testTypeSpecifyingExtensions3( - string $description, - string $expression, - ?bool $nullContext - ): void - { - $this->assertTypes( - __DIR__ . '/data/type-specifying-extensions3.php', - $description, - $expression, - [new AssertionClassMethodTypeSpecifyingExtension($nullContext)], - [new AssertionClassStaticMethodTypeSpecifyingExtension($nullContext)], - 'die', - [], - false - ); - } - public function dataIterable(): array { return [ diff --git a/tests/PHPStan/Analyser/TypeSpecifyingExtension-false.neon b/tests/PHPStan/Analyser/TypeSpecifyingExtension-false.neon new file mode 100644 index 0000000000..094662df08 --- /dev/null +++ b/tests/PHPStan/Analyser/TypeSpecifyingExtension-false.neon @@ -0,0 +1,13 @@ +services: + - + class: PHPStan\Tests\AssertionClassMethodTypeSpecifyingExtension + arguments: + nullContext: false + tags: + - phpstan.typeSpecifier.methodTypeSpecifyingExtension + - + class: PHPStan\Tests\AssertionClassStaticMethodTypeSpecifyingExtension + arguments: + nullContext: false + tags: + - phpstan.typeSpecifier.staticMethodTypeSpecifyingExtension diff --git a/tests/PHPStan/Analyser/TypeSpecifyingExtension-null.neon b/tests/PHPStan/Analyser/TypeSpecifyingExtension-null.neon new file mode 100644 index 0000000000..d208833ec3 --- /dev/null +++ b/tests/PHPStan/Analyser/TypeSpecifyingExtension-null.neon @@ -0,0 +1,13 @@ +services: + - + class: PHPStan\Tests\AssertionClassMethodTypeSpecifyingExtension + arguments: + nullContext: null + tags: + - phpstan.typeSpecifier.methodTypeSpecifyingExtension + - + class: PHPStan\Tests\AssertionClassStaticMethodTypeSpecifyingExtension + arguments: + nullContext: null + tags: + - phpstan.typeSpecifier.staticMethodTypeSpecifyingExtension diff --git a/tests/PHPStan/Analyser/TypeSpecifyingExtension-true.neon b/tests/PHPStan/Analyser/TypeSpecifyingExtension-true.neon new file mode 100644 index 0000000000..cd413d0751 --- /dev/null +++ b/tests/PHPStan/Analyser/TypeSpecifyingExtension-true.neon @@ -0,0 +1,13 @@ +services: + - + class: PHPStan\Tests\AssertionClassMethodTypeSpecifyingExtension + arguments: + nullContext: true + tags: + - phpstan.typeSpecifier.methodTypeSpecifyingExtension + - + class: PHPStan\Tests\AssertionClassStaticMethodTypeSpecifyingExtension + arguments: + nullContext: true + tags: + - phpstan.typeSpecifier.staticMethodTypeSpecifyingExtension diff --git a/tests/PHPStan/Analyser/TypeSpecifyingExtensionTypeInferenceFalseTest.php b/tests/PHPStan/Analyser/TypeSpecifyingExtensionTypeInferenceFalseTest.php new file mode 100644 index 0000000000..e04154b9e2 --- /dev/null +++ b/tests/PHPStan/Analyser/TypeSpecifyingExtensionTypeInferenceFalseTest.php @@ -0,0 +1,39 @@ +gatherAssertTypes(__DIR__ . '/data/type-specifying-extensions-1-false.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/type-specifying-extensions-2-false.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/type-specifying-extensions-3-false.php'); + } + + /** + * @dataProvider dataTypeSpecifyingExtensionsFalse + * @param string $assertType + * @param string $file + * @param mixed ...$args + */ + public function testTypeSpecifyingExtensionsFalse( + string $assertType, + string $file, + ...$args + ): void + { + $this->assertFileAsserts($assertType, $file, ...$args); + } + + public static function getAdditionalConfigFiles(): array + { + return [ + __DIR__ . '/TypeSpecifyingExtension-false.neon', + ]; + } + +} diff --git a/tests/PHPStan/Analyser/TypeSpecifyingExtensionTypeInferenceNullTest.php b/tests/PHPStan/Analyser/TypeSpecifyingExtensionTypeInferenceNullTest.php new file mode 100644 index 0000000000..2084a79701 --- /dev/null +++ b/tests/PHPStan/Analyser/TypeSpecifyingExtensionTypeInferenceNullTest.php @@ -0,0 +1,39 @@ +gatherAssertTypes(__DIR__ . '/data/type-specifying-extensions-1-null.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/type-specifying-extensions-2-null.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/type-specifying-extensions-3-null.php'); + } + + /** + * @dataProvider dataTypeSpecifyingExtensionsNull + * @param string $assertType + * @param string $file + * @param mixed ...$args + */ + public function testTypeSpecifyingExtensionsNull( + string $assertType, + string $file, + ...$args + ): void + { + $this->assertFileAsserts($assertType, $file, ...$args); + } + + public static function getAdditionalConfigFiles(): array + { + return [ + __DIR__ . '/TypeSpecifyingExtension-null.neon', + ]; + } + +} diff --git a/tests/PHPStan/Analyser/TypeSpecifyingExtensionTypeInferenceTrueTest.php b/tests/PHPStan/Analyser/TypeSpecifyingExtensionTypeInferenceTrueTest.php new file mode 100644 index 0000000000..6f0cf48bfb --- /dev/null +++ b/tests/PHPStan/Analyser/TypeSpecifyingExtensionTypeInferenceTrueTest.php @@ -0,0 +1,39 @@ +gatherAssertTypes(__DIR__ . '/data/type-specifying-extensions-1-true.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/type-specifying-extensions-2-true.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/type-specifying-extensions-3-true.php'); + } + + /** + * @dataProvider dataTypeSpecifyingExtensionsTrue + * @param string $assertType + * @param string $file + * @param mixed ...$args + */ + public function testTypeSpecifyingExtensionsTrue( + string $assertType, + string $file, + ...$args + ): void + { + $this->assertFileAsserts($assertType, $file, ...$args); + } + + public static function getAdditionalConfigFiles(): array + { + return [ + __DIR__ . '/TypeSpecifyingExtension-true.neon', + ]; + } + +} diff --git a/tests/PHPStan/Analyser/data/type-specifying-extensions-1-false.php b/tests/PHPStan/Analyser/data/type-specifying-extensions-1-false.php new file mode 100644 index 0000000000..2e4d900db3 --- /dev/null +++ b/tests/PHPStan/Analyser/data/type-specifying-extensions-1-false.php @@ -0,0 +1,14 @@ +assertString($foo); +$test = \PHPStan\Tests\AssertionClass::assertInt($bar); + +assertType('string|null', $foo); +assertType('int|null', $bar); diff --git a/tests/PHPStan/Analyser/data/type-specifying-extensions.php b/tests/PHPStan/Analyser/data/type-specifying-extensions-1-null.php similarity index 68% rename from tests/PHPStan/Analyser/data/type-specifying-extensions.php rename to tests/PHPStan/Analyser/data/type-specifying-extensions-1-null.php index 512fd10f87..5a9326d3f4 100644 --- a/tests/PHPStan/Analyser/data/type-specifying-extensions.php +++ b/tests/PHPStan/Analyser/data/type-specifying-extensions-1-null.php @@ -1,5 +1,7 @@ assertString($foo); $test = \PHPStan\Tests\AssertionClass::assertInt($bar); -die; +assertType('string', $foo); +assertType('int', $bar); diff --git a/tests/PHPStan/Analyser/data/type-specifying-extensions-1-true.php b/tests/PHPStan/Analyser/data/type-specifying-extensions-1-true.php new file mode 100644 index 0000000000..5a9326d3f4 --- /dev/null +++ b/tests/PHPStan/Analyser/data/type-specifying-extensions-1-true.php @@ -0,0 +1,14 @@ +assertString($foo); +$test = \PHPStan\Tests\AssertionClass::assertInt($bar); + +assertType('string', $foo); +assertType('int', $bar); diff --git a/tests/PHPStan/Analyser/data/type-specifying-extensions-2-false.php b/tests/PHPStan/Analyser/data/type-specifying-extensions-2-false.php new file mode 100644 index 0000000000..8016cee235 --- /dev/null +++ b/tests/PHPStan/Analyser/data/type-specifying-extensions-2-false.php @@ -0,0 +1,14 @@ +assertString($foo) && \PHPStan\Tests\AssertionClass::assertInt($bar)) { +} + +assertType('string|null', $foo); +assertType('int|null', $bar); diff --git a/tests/PHPStan/Analyser/data/type-specifying-extensions-2-null.php b/tests/PHPStan/Analyser/data/type-specifying-extensions-2-null.php new file mode 100644 index 0000000000..8016cee235 --- /dev/null +++ b/tests/PHPStan/Analyser/data/type-specifying-extensions-2-null.php @@ -0,0 +1,14 @@ +assertString($foo) && \PHPStan\Tests\AssertionClass::assertInt($bar)) { +} + +assertType('string|null', $foo); +assertType('int|null', $bar); diff --git a/tests/PHPStan/Analyser/data/type-specifying-extensions-2-true.php b/tests/PHPStan/Analyser/data/type-specifying-extensions-2-true.php new file mode 100644 index 0000000000..8016cee235 --- /dev/null +++ b/tests/PHPStan/Analyser/data/type-specifying-extensions-2-true.php @@ -0,0 +1,14 @@ +assertString($foo) && \PHPStan\Tests\AssertionClass::assertInt($bar)) { +} + +assertType('string|null', $foo); +assertType('int|null', $bar); diff --git a/tests/PHPStan/Analyser/data/type-specifying-extensions2.php b/tests/PHPStan/Analyser/data/type-specifying-extensions-3-false.php similarity index 68% rename from tests/PHPStan/Analyser/data/type-specifying-extensions2.php rename to tests/PHPStan/Analyser/data/type-specifying-extensions-3-false.php index bda2e87e55..af253cc5f8 100644 --- a/tests/PHPStan/Analyser/data/type-specifying-extensions2.php +++ b/tests/PHPStan/Analyser/data/type-specifying-extensions-3-false.php @@ -1,11 +1,13 @@ assertString($foo) && \PHPStan\Tests\AssertionClass::assertInt($bar)) { + assertType('string', $foo); + assertType('int', $bar); } - -die; diff --git a/tests/PHPStan/Analyser/data/type-specifying-extensions3.php b/tests/PHPStan/Analyser/data/type-specifying-extensions-3-null.php similarity index 67% rename from tests/PHPStan/Analyser/data/type-specifying-extensions3.php rename to tests/PHPStan/Analyser/data/type-specifying-extensions-3-null.php index 37a1033c2d..af253cc5f8 100644 --- a/tests/PHPStan/Analyser/data/type-specifying-extensions3.php +++ b/tests/PHPStan/Analyser/data/type-specifying-extensions-3-null.php @@ -1,10 +1,13 @@ assertString($foo) && \PHPStan\Tests\AssertionClass::assertInt($bar)) { - die; + assertType('string', $foo); + assertType('int', $bar); } diff --git a/tests/PHPStan/Analyser/data/type-specifying-extensions-3-true.php b/tests/PHPStan/Analyser/data/type-specifying-extensions-3-true.php new file mode 100644 index 0000000000..92e26fdccd --- /dev/null +++ b/tests/PHPStan/Analyser/data/type-specifying-extensions-3-true.php @@ -0,0 +1,13 @@ +assertString($foo) && \PHPStan\Tests\AssertionClass::assertInt($bar)) { + assertType('string|null', $foo); + assertType('int|null', $bar); +} diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php index edd1b1a726..bd5c278609 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php @@ -2,16 +2,6 @@ namespace PHPStan\Rules\Comparison; -use PhpParser\Node\Expr\MethodCall; -use PHPStan\Analyser\Scope; -use PHPStan\Analyser\SpecifiedTypes; -use PHPStan\Analyser\TypeSpecifier; -use PHPStan\Analyser\TypeSpecifierAwareExtension; -use PHPStan\Analyser\TypeSpecifierContext; -use PHPStan\Reflection\MethodReflection; -use PHPStan\Tests\AssertionClassMethodTypeSpecifyingExtension; -use PHPStan\Type\MethodTypeSpecifyingExtension; - /** * @extends \PHPStan\Testing\RuleTestCase */ @@ -40,153 +30,6 @@ protected function shouldTreatPhpDocTypesAsCertain(): bool return $this->treatPhpDocTypesAsCertain; } - /** - * @return MethodTypeSpecifyingExtension[] - */ - protected function getMethodTypeSpecifyingExtensions(): array - { - // todo remove - return [ - new AssertionClassMethodTypeSpecifyingExtension(null), - new class() implements MethodTypeSpecifyingExtension, - TypeSpecifierAwareExtension { - - /** @var TypeSpecifier */ - private $typeSpecifier; - - public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void - { - $this->typeSpecifier = $typeSpecifier; - } - - public function getClass(): string - { - return \PHPStan\Tests\AssertionClass::class; - } - - public function isMethodSupported( - MethodReflection $methodReflection, - MethodCall $node, - TypeSpecifierContext $context - ): bool - { - return $methodReflection->getName() === 'assertNotInt' - && count($node->args) > 0; - } - - public function specifyTypes( - MethodReflection $methodReflection, - MethodCall $node, - Scope $scope, - TypeSpecifierContext $context - ): SpecifiedTypes - { - return $this->typeSpecifier->specifyTypesInCondition( - $scope, - new \PhpParser\Node\Expr\BooleanNot( - new \PhpParser\Node\Expr\FuncCall( - new \PhpParser\Node\Name('is_int'), - [ - $node->args[0], - ] - ) - ), - TypeSpecifierContext::createTruthy() - ); - } - - }, - new class() implements MethodTypeSpecifyingExtension, - TypeSpecifierAwareExtension { - - /** @var TypeSpecifier */ - private $typeSpecifier; - - public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void - { - $this->typeSpecifier = $typeSpecifier; - } - - public function getClass(): string - { - return \ImpossibleMethodCall\Foo::class; - } - - public function isMethodSupported( - MethodReflection $methodReflection, - MethodCall $node, - TypeSpecifierContext $context - ): bool - { - return $methodReflection->getName() === 'isSame' - && count($node->args) >= 2; - } - - public function specifyTypes( - MethodReflection $methodReflection, - MethodCall $node, - Scope $scope, - TypeSpecifierContext $context - ): SpecifiedTypes - { - return $this->typeSpecifier->specifyTypesInCondition( - $scope, - new \PhpParser\Node\Expr\BinaryOp\Identical( - $node->args[0]->value, - $node->args[1]->value - ), - TypeSpecifierContext::createTruthy() - ); - } - - }, - new class() implements MethodTypeSpecifyingExtension, - TypeSpecifierAwareExtension { - - /** @var TypeSpecifier */ - private $typeSpecifier; - - public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void - { - $this->typeSpecifier = $typeSpecifier; - } - - public function getClass(): string - { - return \ImpossibleMethodCall\Foo::class; - } - - public function isMethodSupported( - MethodReflection $methodReflection, - MethodCall $node, - TypeSpecifierContext $context - ): bool - { - return $methodReflection->getName() === 'isNotSame' - && count($node->args) >= 2; - } - - public function specifyTypes( - MethodReflection $methodReflection, - MethodCall $node, - Scope $scope, - TypeSpecifierContext $context - ): SpecifiedTypes - { - return $this->typeSpecifier->specifyTypesInCondition( - $scope, - new \PhpParser\Node\Expr\BinaryOp\NotIdentical( - $node->args[0]->value, - $node->args[1]->value - ), - TypeSpecifierContext::createTruthy() - ); - } - - }, - ]; - } - public function testRule(): void { $this->treatPhpDocTypesAsCertain = true; @@ -265,4 +108,11 @@ public function testReportPhpDoc(): void ]); } + public static function getAdditionalConfigFiles(): array + { + return [ + __DIR__ . '/impossible-check-type-method-call.neon', + ]; + } + } diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRuleTest.php index 64079703c4..fcaf5e3fea 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRuleTest.php @@ -2,9 +2,6 @@ namespace PHPStan\Rules\Comparison; -use PHPStan\Tests\AssertionClassStaticMethodTypeSpecifyingExtension; -use PHPStan\Type\PHPUnit\Assert\AssertStaticMethodTypeSpecifyingExtension; - /** * @extends \PHPStan\Testing\RuleTestCase */ @@ -33,17 +30,6 @@ protected function shouldTreatPhpDocTypesAsCertain(): bool return $this->treatPhpDocTypesAsCertain; } - /** - * @return \PHPStan\Type\StaticMethodTypeSpecifyingExtension[] - */ - protected function getStaticMethodTypeSpecifyingExtensions(): array - { - return [ - new AssertionClassStaticMethodTypeSpecifyingExtension(null), - new AssertStaticMethodTypeSpecifyingExtension(), - ]; - } - public function testRule(): void { $this->treatPhpDocTypesAsCertain = true; @@ -110,4 +96,11 @@ public function testReportPhpDocs(): void ]); } + public static function getAdditionalConfigFiles(): array + { + return [ + __DIR__ . '/impossible-check-type-static-method-call.neon', + ]; + } + } diff --git a/tests/PHPStan/Rules/Comparison/data/TestMethodTypeSpecifyingExtensions.php b/tests/PHPStan/Rules/Comparison/data/TestMethodTypeSpecifyingExtensions.php new file mode 100644 index 0000000000..7d8e0f298f --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/TestMethodTypeSpecifyingExtensions.php @@ -0,0 +1,151 @@ +typeSpecifier = $typeSpecifier; + } + + public function getClass(): string + { + return \PHPStan\Tests\AssertionClass::class; + } + + public function isMethodSupported( + MethodReflection $methodReflection, + MethodCall $node, + TypeSpecifierContext $context + ): bool + { + return $methodReflection->getName() === 'assertNotInt' + && count($node->args) > 0; + } + + public function specifyTypes( + MethodReflection $methodReflection, + MethodCall $node, + Scope $scope, + TypeSpecifierContext $context + ): SpecifiedTypes + { + return $this->typeSpecifier->specifyTypesInCondition( + $scope, + new \PhpParser\Node\Expr\BooleanNot( + new \PhpParser\Node\Expr\FuncCall( + new \PhpParser\Node\Name('is_int'), + [ + $node->args[0], + ] + ) + ), + TypeSpecifierContext::createTruthy() + ); + } + +} + +class FooIsSame implements MethodTypeSpecifyingExtension, TypeSpecifierAwareExtension +{ + + /** @var TypeSpecifier */ + private $typeSpecifier; + + public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void + { + $this->typeSpecifier = $typeSpecifier; + } + + public function getClass(): string + { + return \ImpossibleMethodCall\Foo::class; + } + + public function isMethodSupported( + MethodReflection $methodReflection, + MethodCall $node, + TypeSpecifierContext $context + ): bool + { + return $methodReflection->getName() === 'isSame' + && count($node->args) >= 2; + } + + public function specifyTypes( + MethodReflection $methodReflection, + MethodCall $node, + Scope $scope, + TypeSpecifierContext $context + ): SpecifiedTypes + { + return $this->typeSpecifier->specifyTypesInCondition( + $scope, + new \PhpParser\Node\Expr\BinaryOp\Identical( + $node->args[0]->value, + $node->args[1]->value + ), + TypeSpecifierContext::createTruthy() + ); + } + +} + +class FooIsNotSame implements MethodTypeSpecifyingExtension, + TypeSpecifierAwareExtension { + + /** @var TypeSpecifier */ + private $typeSpecifier; + + public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void + { + $this->typeSpecifier = $typeSpecifier; + } + + public function getClass(): string + { + return \ImpossibleMethodCall\Foo::class; + } + + public function isMethodSupported( + MethodReflection $methodReflection, + MethodCall $node, + TypeSpecifierContext $context + ): bool + { + return $methodReflection->getName() === 'isNotSame' + && count($node->args) >= 2; + } + + public function specifyTypes( + MethodReflection $methodReflection, + MethodCall $node, + Scope $scope, + TypeSpecifierContext $context + ): SpecifiedTypes + { + return $this->typeSpecifier->specifyTypesInCondition( + $scope, + new \PhpParser\Node\Expr\BinaryOp\NotIdentical( + $node->args[0]->value, + $node->args[1]->value + ), + TypeSpecifierContext::createTruthy() + ); + } + +} diff --git a/tests/PHPStan/Rules/Comparison/impossible-check-type-method-call.neon b/tests/PHPStan/Rules/Comparison/impossible-check-type-method-call.neon new file mode 100644 index 0000000000..ffe9789942 --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/impossible-check-type-method-call.neon @@ -0,0 +1,19 @@ +services: + - + class: PHPStan\Tests\AssertionClassMethodTypeSpecifyingExtension + arguments: + nullContext: null + tags: + - phpstan.typeSpecifier.methodTypeSpecifyingExtension + - + class: PHPStan\Rules\Comparison\AssertNotInt + tags: + - phpstan.typeSpecifier.methodTypeSpecifyingExtension + - + class: PHPStan\Rules\Comparison\FooIsSame + tags: + - phpstan.typeSpecifier.methodTypeSpecifyingExtension + - + class: PHPStan\Rules\Comparison\FooIsNotSame + tags: + - phpstan.typeSpecifier.methodTypeSpecifyingExtension diff --git a/tests/PHPStan/Rules/Comparison/impossible-check-type-static-method-call.neon b/tests/PHPStan/Rules/Comparison/impossible-check-type-static-method-call.neon new file mode 100644 index 0000000000..edbb77f3df --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/impossible-check-type-static-method-call.neon @@ -0,0 +1,9 @@ +services: + - + class: PHPStan\Tests\AssertionClassStaticMethodTypeSpecifyingExtension + tags: + - phpstan.typeSpecifier.staticMethodTypeSpecifyingExtension + - + class: PHPStan\Type\PHPUnit\Assert\AssertStaticMethodTypeSpecifyingExtension + tags: + - phpstan.typeSpecifier.staticMethodTypeSpecifyingExtension From 971f58e1bd8d864b3dc3b21a9d0dfde773387891 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 15 Sep 2021 10:22:47 +0200 Subject: [PATCH 0294/1284] Reworked dynamic return type extension tests --- ...icReturnTypeExtensionTypeInferenceTest.php | 38 +++ .../Analyser/LegacyNodeScopeResolverTest.php | 308 ------------------ .../data/TestDynamicReturnTypeExtensions.php | 191 +++++++++++ .../dynamic-method-return-compound-types.php | 5 +- .../data/dynamic-method-return-types.php | 27 +- .../PHPStan/Analyser/dynamic-return-type.neon | 29 ++ 6 files changed, 288 insertions(+), 310 deletions(-) create mode 100644 tests/PHPStan/Analyser/DynamicReturnTypeExtensionTypeInferenceTest.php create mode 100644 tests/PHPStan/Analyser/data/TestDynamicReturnTypeExtensions.php create mode 100644 tests/PHPStan/Analyser/dynamic-return-type.neon diff --git a/tests/PHPStan/Analyser/DynamicReturnTypeExtensionTypeInferenceTest.php b/tests/PHPStan/Analyser/DynamicReturnTypeExtensionTypeInferenceTest.php new file mode 100644 index 0000000000..c1d5a736d3 --- /dev/null +++ b/tests/PHPStan/Analyser/DynamicReturnTypeExtensionTypeInferenceTest.php @@ -0,0 +1,38 @@ +gatherAssertTypes(__DIR__ . '/data/dynamic-method-return-types.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/dynamic-method-return-compound-types.php'); + } + + /** + * @dataProvider dataAsserts + * @param string $assertType + * @param string $file + * @param mixed ...$args + */ + public function testAsserts( + string $assertType, + string $file, + ...$args + ): void + { + $this->assertFileAsserts($assertType, $file, ...$args); + } + + public static function getAdditionalConfigFiles(): array + { + return [ + __DIR__ . '/dynamic-return-type.neon', + ]; + } + +} diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 6dd892f4b7..94ae6a41df 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -4,21 +4,13 @@ use Generator; use PhpParser\Node\Expr\Exit_; -use PhpParser\Node\Expr\MethodCall; -use PhpParser\Node\Expr\StaticCall; use PHPStan\Node\VirtualNode; -use PHPStan\Reflection\MethodReflection; -use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Testing\TypeInferenceTestCase; use PHPStan\TrinaryLogic; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantFloatType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; -use PHPStan\Type\DynamicMethodReturnTypeExtension; -use PHPStan\Type\DynamicStaticMethodReturnTypeExtension; -use PHPStan\Type\ObjectType; -use PHPStan\Type\ObjectWithoutClassType; use PHPStan\Type\Type; use PHPStan\Type\VerbosityLevel; use SomeNodeScopeResolverNamespace\Foo; @@ -4234,306 +4226,6 @@ public function testSwitchTypeElimination( ); } - public function dataDynamicMethodReturnTypeExtensions(): array - { - return [ - [ - '*ERROR*', - '$em->getByFoo($foo)', - ], - [ - 'DynamicMethodReturnTypesNamespace\Entity', - '$em->getByPrimary()', - ], - [ - 'DynamicMethodReturnTypesNamespace\Entity', - '$em->getByPrimary($foo)', - ], - [ - 'DynamicMethodReturnTypesNamespace\Foo', - '$em->getByPrimary(DynamicMethodReturnTypesNamespace\Foo::class)', - ], - [ - '*ERROR*', - '$iem->getByFoo($foo)', - ], - [ - 'DynamicMethodReturnTypesNamespace\Entity', - '$iem->getByPrimary()', - ], - [ - 'DynamicMethodReturnTypesNamespace\Entity', - '$iem->getByPrimary($foo)', - ], - [ - 'DynamicMethodReturnTypesNamespace\Foo', - '$iem->getByPrimary(DynamicMethodReturnTypesNamespace\Foo::class)', - ], - [ - '*ERROR*', - 'EntityManager::getByFoo($foo)', - ], - [ - 'DynamicMethodReturnTypesNamespace\EntityManager', - '\DynamicMethodReturnTypesNamespace\EntityManager::createManagerForEntity()', - ], - [ - 'DynamicMethodReturnTypesNamespace\EntityManager', - '\DynamicMethodReturnTypesNamespace\EntityManager::createManagerForEntity($foo)', - ], - [ - 'DynamicMethodReturnTypesNamespace\Foo', - '\DynamicMethodReturnTypesNamespace\EntityManager::createManagerForEntity(DynamicMethodReturnTypesNamespace\Foo::class)', - ], - [ - '*ERROR*', - '\DynamicMethodReturnTypesNamespace\InheritedEntityManager::getByFoo($foo)', - ], - [ - 'DynamicMethodReturnTypesNamespace\EntityManager', - '\DynamicMethodReturnTypesNamespace\InheritedEntityManager::createManagerForEntity()', - ], - [ - 'DynamicMethodReturnTypesNamespace\EntityManager', - '\DynamicMethodReturnTypesNamespace\InheritedEntityManager::createManagerForEntity($foo)', - ], - [ - 'DynamicMethodReturnTypesNamespace\Foo', - '\DynamicMethodReturnTypesNamespace\InheritedEntityManager::createManagerForEntity(DynamicMethodReturnTypesNamespace\Foo::class)', - ], - [ - 'DynamicMethodReturnTypesNamespace\Foo', - '$container[\DynamicMethodReturnTypesNamespace\Foo::class]', - ], - [ - 'object', - 'new \DynamicMethodReturnTypesNamespace\Foo()', - ], - [ - 'object', - 'new \DynamicMethodReturnTypesNamespace\FooWithoutConstructor()', - ], - ]; - } - - /** - * @dataProvider dataDynamicMethodReturnTypeExtensions - * @param string $description - * @param string $expression - */ - public function testDynamicMethodReturnTypeExtensions( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/dynamic-method-return-types.php', - $description, - $expression, - [ - new class() implements DynamicMethodReturnTypeExtension { - - public function getClass(): string - { - return \DynamicMethodReturnTypesNamespace\EntityManager::class; - } - - public function isMethodSupported(MethodReflection $methodReflection): bool - { - return in_array($methodReflection->getName(), ['getByPrimary'], true); - } - - public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): \PHPStan\Type\Type - { - $args = $methodCall->args; - if (count($args) === 0) { - return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); - } - - $arg = $args[0]->value; - if (!($arg instanceof \PhpParser\Node\Expr\ClassConstFetch)) { - return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); - } - - if (!($arg->class instanceof \PhpParser\Node\Name)) { - return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); - } - - return new ObjectType((string) $arg->class); - } - - }, - new class() implements DynamicMethodReturnTypeExtension { - - public function getClass(): string - { - return \DynamicMethodReturnTypesNamespace\ComponentContainer::class; - } - - public function isMethodSupported(MethodReflection $methodReflection): bool - { - return $methodReflection->getName() === 'offsetGet'; - } - - public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type - { - $args = $methodCall->args; - if (count($args) === 0) { - return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); - } - - $argType = $scope->getType($args[0]->value); - if (!$argType instanceof ConstantStringType) { - return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); - } - - return new ObjectType($argType->getValue()); - } - - }, - ], - [ - new class() implements DynamicStaticMethodReturnTypeExtension { - - public function getClass(): string - { - return \DynamicMethodReturnTypesNamespace\EntityManager::class; - } - - public function isStaticMethodSupported(MethodReflection $methodReflection): bool - { - return in_array($methodReflection->getName(), ['createManagerForEntity'], true); - } - - public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): \PHPStan\Type\Type - { - $args = $methodCall->args; - if (count($args) === 0) { - return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); - } - - $arg = $args[0]->value; - if (!($arg instanceof \PhpParser\Node\Expr\ClassConstFetch)) { - return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); - } - - if (!($arg->class instanceof \PhpParser\Node\Name)) { - return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); - } - - return new ObjectType((string) $arg->class); - } - - }, - new class() implements DynamicStaticMethodReturnTypeExtension { - - public function getClass(): string - { - return \DynamicMethodReturnTypesNamespace\Foo::class; - } - - public function isStaticMethodSupported(MethodReflection $methodReflection): bool - { - return $methodReflection->getName() === '__construct'; - } - - public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): \PHPStan\Type\Type - { - return new ObjectWithoutClassType(); - } - - }, - new class() implements DynamicStaticMethodReturnTypeExtension { - - public function getClass(): string - { - return \DynamicMethodReturnTypesNamespace\FooWithoutConstructor::class; - } - - public function isStaticMethodSupported(MethodReflection $methodReflection): bool - { - return $methodReflection->getName() === '__construct'; - } - - public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): \PHPStan\Type\Type - { - return new ObjectWithoutClassType(); - } - - }, - ] - ); - } - - public function dataDynamicReturnTypeExtensionsOnCompoundTypes(): array - { - return [ - [ - 'DynamicMethodReturnCompoundTypes\Collection', - '$collection->getSelf()', - ], - [ - 'DynamicMethodReturnCompoundTypes\Collection|DynamicMethodReturnCompoundTypes\Foo', - '$collectionOrFoo->getSelf()', - ], - ]; - } - - /** - * @dataProvider dataDynamicReturnTypeExtensionsOnCompoundTypes - * @param string $description - * @param string $expression - */ - public function testDynamicReturnTypeExtensionsOnCompoundTypes( - string $description, - string $expression - ): void - { - $this->assertTypes( - __DIR__ . '/data/dynamic-method-return-compound-types.php', - $description, - $expression, - [ - new class () implements DynamicMethodReturnTypeExtension { - - public function getClass(): string - { - return \DynamicMethodReturnCompoundTypes\Collection::class; - } - - public function isMethodSupported(MethodReflection $methodReflection): bool - { - return $methodReflection->getName() === 'getSelf'; - } - - public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type - { - return new ObjectType(\DynamicMethodReturnCompoundTypes\Collection::class); - } - - }, - new class () implements DynamicMethodReturnTypeExtension { - - public function getClass(): string - { - return \DynamicMethodReturnCompoundTypes\Foo::class; - } - - public function isMethodSupported(MethodReflection $methodReflection): bool - { - return $methodReflection->getName() === 'getSelf'; - } - - public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type - { - return new ObjectType(\DynamicMethodReturnCompoundTypes\Foo::class); - } - - }, - ] - ); - } - public function dataOverwritingVariable(): array { return [ diff --git a/tests/PHPStan/Analyser/data/TestDynamicReturnTypeExtensions.php b/tests/PHPStan/Analyser/data/TestDynamicReturnTypeExtensions.php new file mode 100644 index 0000000000..3fd9f8b0b4 --- /dev/null +++ b/tests/PHPStan/Analyser/data/TestDynamicReturnTypeExtensions.php @@ -0,0 +1,191 @@ +getName(), ['getByPrimary'], true); + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): \PHPStan\Type\Type + { + $args = $methodCall->args; + if (count($args) === 0) { + return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + } + + $arg = $args[0]->value; + if (!($arg instanceof \PhpParser\Node\Expr\ClassConstFetch)) { + return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + } + + if (!($arg->class instanceof \PhpParser\Node\Name)) { + return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + } + + return new ObjectType((string) $arg->class); + } + +} + +class OffsetGetDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension +{ + + public function getClass(): string + { + return \DynamicMethodReturnTypesNamespace\ComponentContainer::class; + } + + public function isMethodSupported(MethodReflection $methodReflection): bool + { + return $methodReflection->getName() === 'offsetGet'; + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type + { + $args = $methodCall->args; + if (count($args) === 0) { + return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + } + + $argType = $scope->getType($args[0]->value); + if (!$argType instanceof ConstantStringType) { + return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + } + + return new ObjectType($argType->getValue()); + } + +} + +class CreateManagerForEntityDynamicReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension +{ + + public function getClass(): string + { + return \DynamicMethodReturnTypesNamespace\EntityManager::class; + } + + public function isStaticMethodSupported(MethodReflection $methodReflection): bool + { + return in_array($methodReflection->getName(), ['createManagerForEntity'], true); + } + + public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): \PHPStan\Type\Type + { + $args = $methodCall->args; + if (count($args) === 0) { + return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + } + + $arg = $args[0]->value; + if (!($arg instanceof \PhpParser\Node\Expr\ClassConstFetch)) { + return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + } + + if (!($arg->class instanceof \PhpParser\Node\Name)) { + return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + } + + return new ObjectType((string) $arg->class); + } + +} + +class ConstructDynamicReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension +{ + + public function getClass(): string + { + return \DynamicMethodReturnTypesNamespace\Foo::class; + } + + public function isStaticMethodSupported(MethodReflection $methodReflection): bool + { + return $methodReflection->getName() === '__construct'; + } + + public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): \PHPStan\Type\Type + { + return new ObjectWithoutClassType(); + } + +} + +class ConstructWithoutConstructor implements DynamicStaticMethodReturnTypeExtension +{ + + public function getClass(): string + { + return \DynamicMethodReturnTypesNamespace\FooWithoutConstructor::class; + } + + public function isStaticMethodSupported(MethodReflection $methodReflection): bool + { + return $methodReflection->getName() === '__construct'; + } + + public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): \PHPStan\Type\Type + { + return new ObjectWithoutClassType(); + } + +} + +class GetSelfDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension { + + public function getClass(): string + { + return \DynamicMethodReturnCompoundTypes\Collection::class; + } + + public function isMethodSupported(MethodReflection $methodReflection): bool + { + return $methodReflection->getName() === 'getSelf'; + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type + { + return new ObjectType(\DynamicMethodReturnCompoundTypes\Collection::class); + } + +} + +class FooGetSelf implements DynamicMethodReturnTypeExtension { + + public function getClass(): string + { + return \DynamicMethodReturnCompoundTypes\Foo::class; + } + + public function isMethodSupported(MethodReflection $methodReflection): bool + { + return $methodReflection->getName() === 'getSelf'; + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type + { + return new ObjectType(\DynamicMethodReturnCompoundTypes\Foo::class); + } + +} diff --git a/tests/PHPStan/Analyser/data/dynamic-method-return-compound-types.php b/tests/PHPStan/Analyser/data/dynamic-method-return-compound-types.php index 2c35369686..5d163a1eda 100644 --- a/tests/PHPStan/Analyser/data/dynamic-method-return-compound-types.php +++ b/tests/PHPStan/Analyser/data/dynamic-method-return-compound-types.php @@ -2,6 +2,8 @@ namespace DynamicMethodReturnCompoundTypes; +use function PHPStan\Testing\assertType; + interface Collection extends \Traversable { @@ -23,7 +25,8 @@ public function getSelf() */ public function doFoo($collection, $collectionOrFoo) { - die; + assertType('DynamicMethodReturnCompoundTypes\Collection', $collection->getSelf()); + assertType('DynamicMethodReturnCompoundTypes\Collection|DynamicMethodReturnCompoundTypes\Foo', $collectionOrFoo->getSelf()); } } diff --git a/tests/PHPStan/Analyser/data/dynamic-method-return-types.php b/tests/PHPStan/Analyser/data/dynamic-method-return-types.php index e32d36e329..56be77284a 100644 --- a/tests/PHPStan/Analyser/data/dynamic-method-return-types.php +++ b/tests/PHPStan/Analyser/data/dynamic-method-return-types.php @@ -2,6 +2,8 @@ namespace DynamicMethodReturnTypesNamespace; +use function PHPStan\Testing\assertType; + class EntityManager { @@ -59,7 +61,30 @@ public function doFoo() $em = new EntityManager(); $iem = new InheritedEntityManager(); $container = new ComponentContainer(); - die; + + assertType('*ERROR*', $em->getByFoo($foo)); + assertType('DynamicMethodReturnTypesNamespace\Entity', $em->getByPrimary()); + assertType('DynamicMethodReturnTypesNamespace\Entity', $em->getByPrimary($foo)); + assertType('DynamicMethodReturnTypesNamespace\Foo', $em->getByPrimary(\DynamicMethodReturnTypesNamespace\Foo::class)); + + assertType('*ERROR*', $iem->getByFoo($foo)); + assertType('DynamicMethodReturnTypesNamespace\Entity', $iem->getByPrimary()); + assertType('DynamicMethodReturnTypesNamespace\Entity', $iem->getByPrimary($foo)); + assertType('DynamicMethodReturnTypesNamespace\Foo', $iem->getByPrimary(\DynamicMethodReturnTypesNamespace\Foo::class)); + + assertType('*ERROR*', EntityManager::getByFoo($foo)); + assertType('DynamicMethodReturnTypesNamespace\EntityManager', \DynamicMethodReturnTypesNamespace\EntityManager::createManagerForEntity()); + assertType('DynamicMethodReturnTypesNamespace\EntityManager', \DynamicMethodReturnTypesNamespace\EntityManager::createManagerForEntity($foo)); + assertType('DynamicMethodReturnTypesNamespace\Foo', \DynamicMethodReturnTypesNamespace\EntityManager::createManagerForEntity(\DynamicMethodReturnTypesNamespace\Foo::class)); + + assertType('*ERROR*', InheritedEntityManager::getByFoo($foo)); + assertType('DynamicMethodReturnTypesNamespace\EntityManager', \DynamicMethodReturnTypesNamespace\InheritedEntityManager::createManagerForEntity()); + assertType('DynamicMethodReturnTypesNamespace\EntityManager', \DynamicMethodReturnTypesNamespace\InheritedEntityManager::createManagerForEntity($foo)); + assertType('DynamicMethodReturnTypesNamespace\Foo', \DynamicMethodReturnTypesNamespace\InheritedEntityManager::createManagerForEntity(\DynamicMethodReturnTypesNamespace\Foo::class)); + + assertType('DynamicMethodReturnTypesNamespace\Foo', $container[\DynamicMethodReturnTypesNamespace\Foo::class]); + assertType('object', new \DynamicMethodReturnTypesNamespace\Foo()); + assertType('object', new \DynamicMethodReturnTypesNamespace\FooWithoutConstructor()); } } diff --git a/tests/PHPStan/Analyser/dynamic-return-type.neon b/tests/PHPStan/Analyser/dynamic-return-type.neon new file mode 100644 index 0000000000..b8bd815fb4 --- /dev/null +++ b/tests/PHPStan/Analyser/dynamic-return-type.neon @@ -0,0 +1,29 @@ +services: + - + class: PHPStan\Tests\GetByPrimaryDynamicReturnTypeExtension + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + - + class: PHPStan\Tests\OffsetGetDynamicReturnTypeExtension + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + - + class: PHPStan\Tests\CreateManagerForEntityDynamicReturnTypeExtension + tags: + - phpstan.broker.dynamicStaticMethodReturnTypeExtension + - + class: PHPStan\Tests\ConstructDynamicReturnTypeExtension + tags: + - phpstan.broker.dynamicStaticMethodReturnTypeExtension + - + class: PHPStan\Tests\ConstructWithoutConstructor + tags: + - phpstan.broker.dynamicStaticMethodReturnTypeExtension + - + class: PHPStan\Tests\GetSelfDynamicReturnTypeExtension + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + - + class: PHPStan\Tests\FooGetSelf + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension From c0a11088228e76d313ef3ee57f687d6ca2fc4a2b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 15 Sep 2021 11:27:46 +0200 Subject: [PATCH 0295/1284] PHPStanTestCase - fix current working directory --- src/Testing/PHPStanTestCase.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Testing/PHPStanTestCase.php b/src/Testing/PHPStanTestCase.php index 0b6446b50d..251a3b7c4c 100644 --- a/src/Testing/PHPStanTestCase.php +++ b/src/Testing/PHPStanTestCase.php @@ -50,6 +50,8 @@ public static function getContainer(): Container } $rootDir = __DIR__ . '/../..'; + $fileHelper = new FileHelper($rootDir); + $rootDir = $fileHelper->normalizePath($rootDir); $containerFactory = new ContainerFactory($rootDir); $container = $containerFactory->create($tmpDir, array_merge([ $containerFactory->getConfigDirectory() . '/config.level8.neon', From 192841fe48a60af0ff876b61aac6c5818c8078d9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 15 Sep 2021 11:56:06 +0200 Subject: [PATCH 0296/1284] Quickfix for Composer bug --- .github/workflows/compiler-tests.yml | 1 + .github/workflows/phar.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/compiler-tests.yml b/.github/workflows/compiler-tests.yml index ca0ffc885a..b4967a7695 100644 --- a/.github/workflows/compiler-tests.yml +++ b/.github/workflows/compiler-tests.yml @@ -27,6 +27,7 @@ jobs: with: coverage: "none" php-version: "8.0" + tools: composer:2.1.6 - name: "Install dependencies" run: "composer install --no-dev --no-interaction --no-progress --no-suggest" diff --git a/.github/workflows/phar.yml b/.github/workflows/phar.yml index a0de139f4d..40977ad9a9 100644 --- a/.github/workflows/phar.yml +++ b/.github/workflows/phar.yml @@ -26,6 +26,7 @@ jobs: with: coverage: "none" php-version: "8.0" + tools: composer:2.1.6 - name: "Install dependencies" run: "composer install --no-interaction --no-progress --no-suggest" From 7d91391352ff4464428fb52c07c3976d853295b9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 15 Sep 2021 15:05:49 +0200 Subject: [PATCH 0297/1284] Revert Quickfix for Composer bug This reverts commit 192841fe48a60af0ff876b61aac6c5818c8078d9. --- .github/workflows/compiler-tests.yml | 1 - .github/workflows/phar.yml | 1 - 2 files changed, 2 deletions(-) diff --git a/.github/workflows/compiler-tests.yml b/.github/workflows/compiler-tests.yml index b4967a7695..ca0ffc885a 100644 --- a/.github/workflows/compiler-tests.yml +++ b/.github/workflows/compiler-tests.yml @@ -27,7 +27,6 @@ jobs: with: coverage: "none" php-version: "8.0" - tools: composer:2.1.6 - name: "Install dependencies" run: "composer install --no-dev --no-interaction --no-progress --no-suggest" diff --git a/.github/workflows/phar.yml b/.github/workflows/phar.yml index 40977ad9a9..a0de139f4d 100644 --- a/.github/workflows/phar.yml +++ b/.github/workflows/phar.yml @@ -26,7 +26,6 @@ jobs: with: coverage: "none" php-version: "8.0" - tools: composer:2.1.6 - name: "Install dependencies" run: "composer install --no-interaction --no-progress --no-suggest" From 72b8d9ecca9fca1040011f74e5ef74104bafc5a3 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 15 Sep 2021 15:06:49 +0200 Subject: [PATCH 0298/1284] PHPStanTestCase::createBroker() returns Broker again --- src/Testing/PHPStanTestCase.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Testing/PHPStanTestCase.php b/src/Testing/PHPStanTestCase.php index 251a3b7c4c..bbb470feab 100644 --- a/src/Testing/PHPStanTestCase.php +++ b/src/Testing/PHPStanTestCase.php @@ -10,6 +10,7 @@ use PHPStan\BetterReflection\Reflector\ClassReflector; use PHPStan\BetterReflection\Reflector\ConstantReflector; use PHPStan\BetterReflection\Reflector\FunctionReflector; +use PHPStan\Broker\Broker; use PHPStan\DependencyInjection\Container; use PHPStan\DependencyInjection\ContainerFactory; use PHPStan\DependencyInjection\Reflection\ClassReflectionExtensionRegistryProvider; @@ -86,9 +87,9 @@ public function getParser(): \PHPStan\Parser\Parser /** * @api */ - public function createBroker(): ReflectionProvider + public function createBroker(): Broker { - return $this->createReflectionProvider(); + return self::getContainer()->getByType(Broker::class); } /** @api */ From b943ba186376d6b5cdf391f870c3162ce720b3bd Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 15 Sep 2021 15:55:43 +0200 Subject: [PATCH 0299/1284] OptimizedDirectorySourceLocator - copy fixes from Composer See https://github.com/composer/composer/commit/8cd2d0e54193a11753d47cff665a447580f42a13 --- .../OptimizedDirectorySourceLocator.php | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php b/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php index 03dfdd4fa3..f95853719b 100644 --- a/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php +++ b/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php @@ -202,7 +202,32 @@ private function findSymbols(string $file): array } // strip heredocs/nowdocs - $contents = preg_replace('{<<<[ \t]*([\'"]?)(\w+)\\1(?:\r\n|\n|\r)(?:.*(?=[\r\n]+[ \t]*\\2))[\r\n]+[ \t]*\\2(?=\s*[;,.)])}s', 'null', $contents); + $heredocRegex = '{ + # opening heredoc/nowdoc delimiter (word-chars) + <<<[ \t]*+([\'"]?)(\w++)\\1 + # needs to be followed by a newline + (?:\r\n|\n|\r) + # the meat of it, matching line by line until end delimiter + (?: + # a valid line is optional white-space (possessive match) not followed by the end delimiter, then anything goes for the rest of the line + [\t ]*+(?!\\2 \b)[^\r\n]*+ + # end of line(s) + [\r\n]++ + )* + # end delimiter + [\t ]*+ \\2 (?=\b) + }x'; + + // run first assuming the file is valid unicode + $contentWithoutHeredoc = preg_replace($heredocRegex . 'u', 'null', $contents); + if ($contentWithoutHeredoc === null) { + // run again without unicode support if the file failed to be parsed + $contents = preg_replace($heredocRegex, 'null', $contents); + } else { + $contents = $contentWithoutHeredoc; + } + unset($contentWithoutHeredoc); + if ($contents === null) { return ['classes' => [], 'functions' => []]; } @@ -212,7 +237,7 @@ private function findSymbols(string $file): array return ['classes' => [], 'functions' => []]; } // strip leading non-php code if needed - if (substr($contents, 0, 2) !== ' [], 'functions' => []]; From 0c4e234f7ccf28231bb3e4f4c83a32d3c177057a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 15 Sep 2021 16:08:50 +0200 Subject: [PATCH 0300/1284] Fix --- src/Analyser/MutatingScope.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 51debd0376..2f2bd0a110 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -2641,14 +2641,14 @@ public function resolveTypeByName(Name $name): TypeWithClassName $originalClass = $this->resolveName($name); if ($this->isInClass()) { - if ($this->inClosureBindScopeClass !== null && $this->inClosureBindScopeClass !== 'static') { + if ($this->inClosureBindScopeClass !== null && $this->inClosureBindScopeClass !== 'static' && $originalClass === $this->getClassReflection()->getName()) { if ($this->reflectionProvider->hasClass($this->inClosureBindScopeClass)) { return new ThisType($this->reflectionProvider->getClass($this->inClosureBindScopeClass)); } - $thisType = new ThisType($this->getClassReflection()); - } else { - $thisType = new ThisType($this->getClassReflection()); + return new ObjectType($this->inClosureBindScopeClass); } + + $thisType = new ThisType($this->getClassReflection()); $ancestor = $thisType->getAncestorWithClassName($originalClass); if ($ancestor !== null) { return $ancestor; From 8e30adb65389ea336cfa352b267314008ecfebf4 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 15 Sep 2021 17:19:35 +0200 Subject: [PATCH 0301/1284] Fix --- src/Testing/PHPStanTestCase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Testing/PHPStanTestCase.php b/src/Testing/PHPStanTestCase.php index bbb470feab..2cac14ec1a 100644 --- a/src/Testing/PHPStanTestCase.php +++ b/src/Testing/PHPStanTestCase.php @@ -52,7 +52,7 @@ public static function getContainer(): Container $rootDir = __DIR__ . '/../..'; $fileHelper = new FileHelper($rootDir); - $rootDir = $fileHelper->normalizePath($rootDir); + $rootDir = $fileHelper->normalizePath($rootDir, '/'); $containerFactory = new ContainerFactory($rootDir); $container = $containerFactory->create($tmpDir, array_merge([ $containerFactory->getConfigDirectory() . '/config.level8.neon', From bbb8d9e0dfb080a5b7c96bed9c143d42cc33e22d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 15 Sep 2021 17:35:08 +0200 Subject: [PATCH 0302/1284] Fix --- .../SourceLocator/OptimizedDirectorySourceLocatorTest.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/PHPStan/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorTest.php b/tests/PHPStan/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorTest.php index 616e2617a2..55bffa92dd 100644 --- a/tests/PHPStan/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorTest.php +++ b/tests/PHPStan/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorTest.php @@ -137,12 +137,16 @@ public function testFunctionDoesNotExist(string $functionName): void public function testBug5525(): void { + if (PHP_VERSION_ID < 70300) { + self::markTestSkipped('This test needs at least PHP 7.3 because of different PCRE engine'); + } + $factory = self::getContainer()->getByType(OptimizedDirectorySourceLocatorFactory::class); $locator = $factory->createByFiles([__DIR__ . '/data/bug-5525.php']); $classReflector = new ClassReflector($locator); - $class = $classReflector->reflect(\Faker\Provider\nl_BE\Text::class); - $this->assertSame(\Faker\Provider\nl_BE\Text::class, $class->getName()); + $class = $classReflector->reflect('Faker\\Provider\\nl_BE\\Text'); + $this->assertSame('Faker\\Provider\\nl_BE\\Text', $class->getName()); } } From 1df5214dc23d68d7fc5cbce09e36b570cc469e31 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 15 Sep 2021 17:39:40 +0200 Subject: [PATCH 0303/1284] Fix --- .../SourceLocator/OptimizedDirectorySourceLocatorTest.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/PHPStan/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorTest.php b/tests/PHPStan/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorTest.php index 55bffa92dd..84e3d0646a 100644 --- a/tests/PHPStan/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorTest.php +++ b/tests/PHPStan/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorTest.php @@ -146,7 +146,10 @@ public function testBug5525(): void $classReflector = new ClassReflector($locator); $class = $classReflector->reflect('Faker\\Provider\\nl_BE\\Text'); - $this->assertSame('Faker\\Provider\\nl_BE\\Text', $class->getName()); + + /** @var string $className */ + $className = $class->getName(); + $this->assertSame('Faker\\Provider\\nl_BE\\Text', $className); } } From e1c24b396dae0cf68f0eab3d60919e23a7aca5ea Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 15 Sep 2021 21:00:38 +0200 Subject: [PATCH 0304/1284] Array functions stubs --- conf/bleedingEdge.neon | 2 -- conf/config.stubFiles.neon | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 6c4006b75a..61b9c39bc3 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -27,5 +27,3 @@ parameters: privateStaticCall: true overridingProperty: true throwsVoid: true - stubFiles: - - ../stubs/arrayFunctions.stub diff --git a/conf/config.stubFiles.neon b/conf/config.stubFiles.neon index e42f634273..1c90d8cdf1 100644 --- a/conf/config.stubFiles.neon +++ b/conf/config.stubFiles.neon @@ -17,3 +17,4 @@ parameters: - ../stubs/spl.stub - ../stubs/SplObjectStorage.stub - ../stubs/Exception.stub + - ../stubs/arrayFunctions.stub From 4b7d0f0233413be06ea07bfff5a9d1057d36b38a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 15 Sep 2021 21:02:37 +0200 Subject: [PATCH 0305/1284] Check methods and functions with explicit throw points and `@throws void` in PHPDoc --- conf/bleedingEdge.neon | 1 - conf/config.level3.neon | 8 ++++---- conf/config.neon | 4 +--- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 61b9c39bc3..98af79fcc5 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -26,4 +26,3 @@ parameters: classConstants: true privateStaticCall: true overridingProperty: true - throwsVoid: true diff --git a/conf/config.level3.neon b/conf/config.level3.neon index f30bea1c29..939c6adb06 100644 --- a/conf/config.level3.neon +++ b/conf/config.level3.neon @@ -20,10 +20,6 @@ rules: conditionalTags: PHPStan\Rules\Arrays\ArrayDestructuringRule: phpstan.rules.rule: %featureToggles.arrayDestructuring% - PHPStan\Rules\Exceptions\ThrowsVoidFunctionWithExplicitThrowPointRule: - phpstan.rules.rule: %featureToggles.throwsVoid% - PHPStan\Rules\Exceptions\ThrowsVoidMethodWithExplicitThrowPointRule: - phpstan.rules.rule: %featureToggles.throwsVoid% parameters: checkPhpDocMethodSignatures: true @@ -65,12 +61,16 @@ services: arguments: exceptionTypeResolver: @exceptionTypeResolver missingCheckedExceptionInThrows: %exceptions.check.missingCheckedExceptionInThrows% + tags: + - phpstan.rules.rule - class: PHPStan\Rules\Exceptions\ThrowsVoidMethodWithExplicitThrowPointRule arguments: exceptionTypeResolver: @exceptionTypeResolver missingCheckedExceptionInThrows: %exceptions.check.missingCheckedExceptionInThrows% + tags: + - phpstan.rules.rule - class: PHPStan\Rules\Functions\ReturnTypeRule diff --git a/conf/config.neon b/conf/config.neon index 9a43172fbb..4e4249d954 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -45,7 +45,6 @@ parameters: classConstants: false privateStaticCall: false overridingProperty: false - throwsVoid: false fileExtensions: - php checkAdvancedIsset: false @@ -225,8 +224,7 @@ parametersSchema: finalByPhpDocTag: bool(), classConstants: bool(), privateStaticCall: bool(), - overridingProperty: bool(), - throwsVoid: bool() + overridingProperty: bool() ]) fileExtensions: listOf(string()) checkAdvancedIsset: bool() From 3f6919ac4fa3e29d683ca3060838358744b1a05a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 15 Sep 2021 21:03:59 +0200 Subject: [PATCH 0306/1284] Closure that uses `$this` --- conf/bleedingEdge.neon | 1 - conf/config.level0.neon | 6 +----- conf/config.neon | 2 -- 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 98af79fcc5..6dd6038d84 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -1,7 +1,6 @@ parameters: featureToggles: bleedingEdge: true - closureUsesThis: true randomIntParameters: true nullCoalesce: true fileWhitespace: true diff --git a/conf/config.level0.neon b/conf/config.level0.neon index b76b57f0e0..8af45339c8 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -20,8 +20,6 @@ conditionalTags: phpstan.rules.rule: %featureToggles.apiRules% PHPStan\Rules\Constants\OverridingConstantRule: phpstan.rules.rule: %featureToggles.classConstants% - PHPStan\Rules\Functions\ClosureUsesThisRule: - phpstan.rules.rule: %featureToggles.closureUsesThis% PHPStan\Rules\Whitespace\FileWhitespaceRule: phpstan.rules.rule: %featureToggles.fileWhitespace% PHPStan\Rules\Properties\UninitializedPropertyRule: @@ -52,6 +50,7 @@ rules: - PHPStan\Rules\Functions\ArrowFunctionReturnNullsafeByRefRule - PHPStan\Rules\Functions\CallToFunctionParametersRule - PHPStan\Rules\Functions\ClosureAttributesRule + - PHPStan\Rules\Functions\ClosureUsesThisRule - PHPStan\Rules\Functions\ExistingClassesInArrowFunctionTypehintsRule - PHPStan\Rules\Functions\ExistingClassesInClosureTypehintsRule - PHPStan\Rules\Functions\ExistingClassesInTypehintsRule @@ -125,9 +124,6 @@ services: arguments: checkFunctionNameCase: %checkFunctionNameCase% - - - class: PHPStan\Rules\Functions\ClosureUsesThisRule - - class: PHPStan\Rules\Methods\CallMethodsRule tags: diff --git a/conf/config.neon b/conf/config.neon index 4e4249d954..d379fa26c8 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -20,7 +20,6 @@ parameters: featureToggles: bleedingEdge: false disableRuntimeReflectionProvider: false - closureUsesThis: false randomIntParameters: false nullCoalesce: false fileWhitespace: false @@ -200,7 +199,6 @@ parametersSchema: featureToggles: structure([ bleedingEdge: bool(), disableRuntimeReflectionProvider: bool(), - closureUsesThis: bool(), randomIntParameters: bool(), nullCoalesce: bool(), fileWhitespace: bool(), From 811f75268295458e0aa58654e42aa07f003c8871 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 15 Sep 2021 21:11:37 +0200 Subject: [PATCH 0307/1284] StubValidator - validate overriding methods in stubs --- conf/bleedingEdge.neon | 1 - conf/config.neon | 3 --- src/PhpDoc/StubValidator.php | 13 +------------ 3 files changed, 1 insertion(+), 16 deletions(-) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 6dd6038d84..6ef7ae93c3 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -19,7 +19,6 @@ parameters: apiRules: true deepInspectTypes: true neverInGenericReturnType: true - validateOverridingMethodsInStubs: true crossCheckInterfaces: true finalByPhpDocTag: true classConstants: true diff --git a/conf/config.neon b/conf/config.neon index d379fa26c8..9be8c66b9e 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -38,7 +38,6 @@ parameters: apiRules: false deepInspectTypes: false neverInGenericReturnType: false - validateOverridingMethodsInStubs: false crossCheckInterfaces: false finalByPhpDocTag: false classConstants: false @@ -217,7 +216,6 @@ parametersSchema: apiRules: bool(), deepInspectTypes: bool(), neverInGenericReturnType: bool(), - validateOverridingMethodsInStubs: bool(), crossCheckInterfaces: bool(), finalByPhpDocTag: bool(), classConstants: bool(), @@ -427,7 +425,6 @@ services: - class: PHPStan\PhpDoc\StubValidator arguments: - validateOverridingMethods: %featureToggles.validateOverridingMethodsInStubs% crossCheckInterfaces: %featureToggles.crossCheckInterfaces% - diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index 6e619f5b8a..7c4d425161 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -56,18 +56,14 @@ class StubValidator private \PHPStan\DependencyInjection\DerivativeContainerFactory $derivativeContainerFactory; - private bool $validateOverridingMethods; - private bool $crossCheckInterfaces; public function __construct( DerivativeContainerFactory $derivativeContainerFactory, - bool $validateOverridingMethods, bool $crossCheckInterfaces ) { $this->derivativeContainerFactory = $derivativeContainerFactory; - $this->validateOverridingMethods = $validateOverridingMethods; $this->crossCheckInterfaces = $crossCheckInterfaces; } @@ -149,6 +145,7 @@ private function getRuleRegistry(Container $container): Registry new ExistingClassesInTypehintsRule($functionDefinitionCheck), new \PHPStan\Rules\Functions\ExistingClassesInTypehintsRule($functionDefinitionCheck), new ExistingClassesInPropertiesRule($reflectionProvider, $classCaseSensitivityCheck, true, false), + new OverridingMethodRule($container->getByType(PhpVersion::class), new MethodSignatureRule(true, true), true), // level 2 new ClassAncestorsRule($fileTypeMapper, $genericAncestorsCheck, $crossCheckInterfacesHelper, $this->crossCheckInterfaces), @@ -180,14 +177,6 @@ private function getRuleRegistry(Container $container): Registry new MissingPropertyTypehintRule($missingTypehintCheck), ]; - if ($this->validateOverridingMethods) { - $rules[] = new OverridingMethodRule( - $container->getByType(PhpVersion::class), - new MethodSignatureRule(true, true), - true - ); - } - return new Registry($rules); } From e101156dc8ce5e3849956f259aec90bacc9e9382 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 15 Sep 2021 21:13:22 +0200 Subject: [PATCH 0308/1284] Cross-check generic interfaces implemented multiple times --- composer.json | 2 +- composer.lock | 14 +++++++------- conf/bleedingEdge.neon | 1 - conf/config.level2.neon | 4 ---- conf/config.neon | 4 ---- src/PhpDoc/StubValidator.php | 10 +++------- src/Rules/Generics/ClassAncestorsRule.php | 12 +++--------- src/Rules/Generics/InterfaceAncestorsRule.php | 12 +++--------- .../Rules/Generics/ClassAncestorsRuleTest.php | 3 +-- .../Rules/Generics/InterfaceAncestorsRuleTest.php | 3 +-- 10 files changed, 19 insertions(+), 46 deletions(-) diff --git a/composer.json b/composer.json index 5afd495efe..7619225be7 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "nette/utils": "^3.1.3", "nikic/php-parser": "dev-master as 4.12.0", "ondram/ci-detector": "^3.4.0", - "ondrejmirtes/better-reflection": "4.3.67", + "ondrejmirtes/better-reflection": "4.3.68", "phpstan/php-8-stubs": "^0.1.23", "phpstan/phpdoc-parser": "^1.0", "react/child-process": "^0.6.1", diff --git a/composer.lock b/composer.lock index 8052f368b4..d16953dc72 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "42d609d884ab7fa716f14e7e006e29a0", + "content-hash": "5f6ecea8ad1c410153bb884e8b26248e", "packages": [ { "name": "clue/block-react", @@ -2075,16 +2075,16 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "4.3.67", + "version": "4.3.68", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "f14002eab57de1ec3d0116371cb5239d4415c432" + "reference": "a90c6bca67001b61346f47f143bb9721a8ef88d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/f14002eab57de1ec3d0116371cb5239d4415c432", - "reference": "f14002eab57de1ec3d0116371cb5239d4415c432", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/a90c6bca67001b61346f47f143bb9721a8ef88d4", + "reference": "a90c6bca67001b61346f47f143bb9721a8ef88d4", "shasum": "" }, "require": { @@ -2139,9 +2139,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/4.3.67" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/4.3.68" }, - "time": "2021-08-23T20:27:27+00:00" + "time": "2021-09-15T20:00:14+00:00" }, { "name": "phpstan/php-8-stubs", diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 6ef7ae93c3..5e67b33850 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -19,7 +19,6 @@ parameters: apiRules: true deepInspectTypes: true neverInGenericReturnType: true - crossCheckInterfaces: true finalByPhpDocTag: true classConstants: true privateStaticCall: true diff --git a/conf/config.level2.neon b/conf/config.level2.neon index df6c9850f8..995020dfa5 100644 --- a/conf/config.level2.neon +++ b/conf/config.level2.neon @@ -58,8 +58,6 @@ services: - phpstan.rules.rule - class: PHPStan\Rules\Generics\ClassAncestorsRule - arguments: - crossCheckInterfaces: %featureToggles.crossCheckInterfaces% tags: - phpstan.rules.rule - @@ -70,8 +68,6 @@ services: class: PHPStan\Rules\Properties\AccessPrivatePropertyThroughStaticRule - class: PHPStan\Rules\Generics\InterfaceAncestorsRule - arguments: - crossCheckInterfaces: %featureToggles.crossCheckInterfaces% tags: - phpstan.rules.rule - diff --git a/conf/config.neon b/conf/config.neon index 9be8c66b9e..a8b42079c5 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -38,7 +38,6 @@ parameters: apiRules: false deepInspectTypes: false neverInGenericReturnType: false - crossCheckInterfaces: false finalByPhpDocTag: false classConstants: false privateStaticCall: false @@ -216,7 +215,6 @@ parametersSchema: apiRules: bool(), deepInspectTypes: bool(), neverInGenericReturnType: bool(), - crossCheckInterfaces: bool(), finalByPhpDocTag: bool(), classConstants: bool(), privateStaticCall: bool(), @@ -424,8 +422,6 @@ services: - class: PHPStan\PhpDoc\StubValidator - arguments: - crossCheckInterfaces: %featureToggles.crossCheckInterfaces% - class: PHPStan\Analyser\Analyser diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index 7c4d425161..93ad07363f 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -56,15 +56,11 @@ class StubValidator private \PHPStan\DependencyInjection\DerivativeContainerFactory $derivativeContainerFactory; - private bool $crossCheckInterfaces; - public function __construct( - DerivativeContainerFactory $derivativeContainerFactory, - bool $crossCheckInterfaces + DerivativeContainerFactory $derivativeContainerFactory ) { $this->derivativeContainerFactory = $derivativeContainerFactory; - $this->crossCheckInterfaces = $crossCheckInterfaces; } /** @@ -148,11 +144,11 @@ private function getRuleRegistry(Container $container): Registry new OverridingMethodRule($container->getByType(PhpVersion::class), new MethodSignatureRule(true, true), true), // level 2 - new ClassAncestorsRule($fileTypeMapper, $genericAncestorsCheck, $crossCheckInterfacesHelper, $this->crossCheckInterfaces), + new ClassAncestorsRule($fileTypeMapper, $genericAncestorsCheck, $crossCheckInterfacesHelper), new ClassTemplateTypeRule($templateTypeCheck), new FunctionTemplateTypeRule($fileTypeMapper, $templateTypeCheck), new FunctionSignatureVarianceRule($varianceCheck), - new InterfaceAncestorsRule($fileTypeMapper, $genericAncestorsCheck, $crossCheckInterfacesHelper, $this->crossCheckInterfaces), + new InterfaceAncestorsRule($fileTypeMapper, $genericAncestorsCheck, $crossCheckInterfacesHelper), new InterfaceTemplateTypeRule($fileTypeMapper, $templateTypeCheck), new MethodTemplateTypeRule($fileTypeMapper, $templateTypeCheck), new MethodSignatureVarianceRule($varianceCheck), diff --git a/src/Rules/Generics/ClassAncestorsRule.php b/src/Rules/Generics/ClassAncestorsRule.php index b07c3453d2..00a83bac5c 100644 --- a/src/Rules/Generics/ClassAncestorsRule.php +++ b/src/Rules/Generics/ClassAncestorsRule.php @@ -23,19 +23,15 @@ class ClassAncestorsRule implements Rule private CrossCheckInterfacesHelper $crossCheckInterfacesHelper; - private bool $crossCheckInterfaces; - public function __construct( FileTypeMapper $fileTypeMapper, GenericAncestorsCheck $genericAncestorsCheck, - CrossCheckInterfacesHelper $crossCheckInterfacesHelper, - bool $crossCheckInterfaces = false + CrossCheckInterfacesHelper $crossCheckInterfacesHelper ) { $this->fileTypeMapper = $fileTypeMapper; $this->genericAncestorsCheck = $genericAncestorsCheck; $this->crossCheckInterfacesHelper = $crossCheckInterfacesHelper; - $this->crossCheckInterfaces = $crossCheckInterfaces; } public function getNodeType(): string @@ -107,10 +103,8 @@ public function processNode(Node $node, Scope $scope): array sprintf('in implemented type %%s of class %s', $className) ); - if ($this->crossCheckInterfaces) { - foreach ($this->crossCheckInterfacesHelper->check($classReflection) as $error) { - $implementsErrors[] = $error; - } + foreach ($this->crossCheckInterfacesHelper->check($classReflection) as $error) { + $implementsErrors[] = $error; } return array_merge($extendsErrors, $implementsErrors); diff --git a/src/Rules/Generics/InterfaceAncestorsRule.php b/src/Rules/Generics/InterfaceAncestorsRule.php index 52fe2d03f6..ee0e33b4e9 100644 --- a/src/Rules/Generics/InterfaceAncestorsRule.php +++ b/src/Rules/Generics/InterfaceAncestorsRule.php @@ -23,19 +23,15 @@ class InterfaceAncestorsRule implements Rule private CrossCheckInterfacesHelper $crossCheckInterfacesHelper; - private bool $crossCheckInterfaces; - public function __construct( FileTypeMapper $fileTypeMapper, GenericAncestorsCheck $genericAncestorsCheck, - CrossCheckInterfacesHelper $crossCheckInterfacesHelper, - bool $crossCheckInterfaces = false + CrossCheckInterfacesHelper $crossCheckInterfacesHelper ) { $this->fileTypeMapper = $fileTypeMapper; $this->genericAncestorsCheck = $genericAncestorsCheck; $this->crossCheckInterfacesHelper = $crossCheckInterfacesHelper; - $this->crossCheckInterfaces = $crossCheckInterfaces; } public function getNodeType(): string @@ -104,10 +100,8 @@ public function processNode(Node $node, Scope $scope): array '' ); - if ($this->crossCheckInterfaces) { - foreach ($this->crossCheckInterfacesHelper->check($classReflection) as $error) { - $implementsErrors[] = $error; - } + foreach ($this->crossCheckInterfacesHelper->check($classReflection) as $error) { + $implementsErrors[] = $error; } return array_merge($extendsErrors, $implementsErrors); diff --git a/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php b/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php index 3a121c2fe8..b31aaa8278 100644 --- a/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php +++ b/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php @@ -22,8 +22,7 @@ protected function getRule(): Rule new VarianceCheck(), true ), - new CrossCheckInterfacesHelper(), - true + new CrossCheckInterfacesHelper() ); } diff --git a/tests/PHPStan/Rules/Generics/InterfaceAncestorsRuleTest.php b/tests/PHPStan/Rules/Generics/InterfaceAncestorsRuleTest.php index 7aeb93d07b..0207076df9 100644 --- a/tests/PHPStan/Rules/Generics/InterfaceAncestorsRuleTest.php +++ b/tests/PHPStan/Rules/Generics/InterfaceAncestorsRuleTest.php @@ -22,8 +22,7 @@ protected function getRule(): Rule new VarianceCheck(), true ), - new CrossCheckInterfacesHelper(), - true + new CrossCheckInterfacesHelper() ); } From b5c72f78bc847252fb05f43e070be03779a88977 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 15 Sep 2021 21:15:24 +0200 Subject: [PATCH 0309/1284] Detect duplicate stub files --- conf/bleedingEdge.neon | 1 - conf/config.neon | 2 -- src/Command/CommandHelper.php | 5 +---- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 5e67b33850..156c16d724 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -7,7 +7,6 @@ parameters: unusedClassElements: true readComposerPhpVersion: true dateTimeInstantiation: true - detectDuplicateStubFiles: true checkLogicalAndConstantCondition: true checkLogicalOrConstantCondition: true checkMissingTemplateTypeInParameter: true diff --git a/conf/config.neon b/conf/config.neon index a8b42079c5..9f658e8f68 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -26,7 +26,6 @@ parameters: unusedClassElements: false readComposerPhpVersion: false dateTimeInstantiation: false - detectDuplicateStubFiles: false checkLogicalAndConstantCondition: false checkLogicalOrConstantCondition: false checkMissingTemplateTypeInParameter: false @@ -203,7 +202,6 @@ parametersSchema: unusedClassElements: bool(), readComposerPhpVersion: bool(), dateTimeInstantiation: bool(), - detectDuplicateStubFiles: bool(), checkLogicalAndConstantCondition: bool(), checkLogicalOrConstantCondition: bool(), checkMissingTemplateTypeInParameter: bool(), diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php index 53156cde5b..85e4aeb422 100644 --- a/src/Command/CommandHelper.php +++ b/src/Command/CommandHelper.php @@ -334,10 +334,7 @@ public static function begin( $alreadyAddedStubFiles = []; foreach ($container->getParameter('stubFiles') as $stubFile) { - if ( - $container->getParameter('featureToggles')['detectDuplicateStubFiles'] - && array_key_exists($stubFile, $alreadyAddedStubFiles) - ) { + if (array_key_exists($stubFile, $alreadyAddedStubFiles)) { $errorOutput->writeLineFormatted(sprintf('Stub file %s is added multiple times.', $stubFile)); throw new \PHPStan\Command\InceptionNotSuccessfulException(); From 1a4a5d2439efd08a9ec2f70c8203a84a407e4ede Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 15 Sep 2021 21:16:58 +0200 Subject: [PATCH 0310/1284] Resolve type of new $class as ObjectWithoutClassType --- conf/bleedingEdge.neon | 1 - conf/config.neon | 2 -- src/Analyser/DirectScopeFactory.php | 5 ----- src/Analyser/LazyScopeFactory.php | 4 ---- src/Analyser/MutatingScope.php | 18 ++---------------- src/Testing/PHPStanTestCase.php | 1 - .../Analyser/AnalyserIntegrationTest.php | 3 ++- .../Analyser/LegacyNodeScopeResolverTest.php | 2 +- tests/PHPStan/Analyser/data/bug-4579.php | 2 +- .../Analyser/data/generic-class-string.php | 10 +++++----- 10 files changed, 11 insertions(+), 37 deletions(-) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 156c16d724..05343902ac 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -12,7 +12,6 @@ parameters: checkMissingTemplateTypeInParameter: true wrongVarUsage: true arrayDestructuring: true - objectFromNewClass: true skipCheckGenericClasses: [] rememberFunctionValues: true apiRules: true diff --git a/conf/config.neon b/conf/config.neon index 9f658e8f68..ce27918d3a 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -31,7 +31,6 @@ parameters: checkMissingTemplateTypeInParameter: false wrongVarUsage: false arrayDestructuring: false - objectFromNewClass: false skipCheckGenericClasses: [] rememberFunctionValues: false apiRules: false @@ -207,7 +206,6 @@ parametersSchema: checkMissingTemplateTypeInParameter: bool(), wrongVarUsage: bool(), arrayDestructuring: bool(), - objectFromNewClass: bool(), skipCheckGenericClasses: listOf(string()), rememberFunctionValues: bool(), apiRules: bool(), diff --git a/src/Analyser/DirectScopeFactory.php b/src/Analyser/DirectScopeFactory.php index f8e7e43553..eb7767275e 100644 --- a/src/Analyser/DirectScopeFactory.php +++ b/src/Analyser/DirectScopeFactory.php @@ -36,8 +36,6 @@ class DirectScopeFactory implements ScopeFactory private bool $treatPhpDocTypesAsCertain; - private bool $objectFromNewClass; - /** @var string[] */ private array $dynamicConstantNames; @@ -52,7 +50,6 @@ public function __construct( \PHPStan\Parser\Parser $parser, NodeScopeResolver $nodeScopeResolver, bool $treatPhpDocTypesAsCertain, - bool $objectFromNewClass, Container $container ) { @@ -66,7 +63,6 @@ public function __construct( $this->parser = $parser; $this->nodeScopeResolver = $nodeScopeResolver; $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; - $this->objectFromNewClass = $objectFromNewClass; $this->dynamicConstantNames = $container->getParameter('dynamicConstantNames'); } @@ -140,7 +136,6 @@ public function create( $inFunctionCallsStack, $this->dynamicConstantNames, $this->treatPhpDocTypesAsCertain, - $this->objectFromNewClass, $afterExtractCall, $parentScope ); diff --git a/src/Analyser/LazyScopeFactory.php b/src/Analyser/LazyScopeFactory.php index 23abbeeedd..a85269526d 100644 --- a/src/Analyser/LazyScopeFactory.php +++ b/src/Analyser/LazyScopeFactory.php @@ -23,8 +23,6 @@ class LazyScopeFactory implements ScopeFactory private bool $treatPhpDocTypesAsCertain; - private bool $objectFromNewClass; - public function __construct( string $scopeClass, Container $container @@ -34,7 +32,6 @@ public function __construct( $this->container = $container; $this->dynamicConstantNames = $container->getParameter('dynamicConstantNames'); $this->treatPhpDocTypesAsCertain = $container->getParameter('treatPhpDocTypesAsCertain'); - $this->objectFromNewClass = $container->getParameter('featureToggles')['objectFromNewClass']; } /** @@ -107,7 +104,6 @@ public function create( $inFunctionCallsStack, $this->dynamicConstantNames, $this->treatPhpDocTypesAsCertain, - $this->objectFromNewClass, $afterExtractCall, $parentScope ); diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 2f2bd0a110..d688a5b435 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -174,8 +174,6 @@ class MutatingScope implements Scope private bool $treatPhpDocTypesAsCertain; - private bool $objectFromNewClass; - private bool $afterExtractCall; private ?Scope $parentScope; @@ -206,7 +204,6 @@ class MutatingScope implements Scope * @param array $inFunctionCallsStack * @param string[] $dynamicConstantNames * @param bool $treatPhpDocTypesAsCertain - * @param bool $objectFromNewClass * @param bool $afterExtractCall * @param Scope|null $parentScope */ @@ -236,7 +233,6 @@ public function __construct( array $inFunctionCallsStack = [], array $dynamicConstantNames = [], bool $treatPhpDocTypesAsCertain = true, - bool $objectFromNewClass = false, bool $afterExtractCall = false, ?Scope $parentScope = null ) @@ -270,7 +266,6 @@ public function __construct( $this->inFunctionCallsStack = $inFunctionCallsStack; $this->dynamicConstantNames = $dynamicConstantNames; $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; - $this->objectFromNewClass = $objectFromNewClass; $this->afterExtractCall = $afterExtractCall; $this->parentScope = $parentScope; } @@ -2401,7 +2396,6 @@ public function doNotTreatPhpDocTypesAsCertain(): Scope $this->inFunctionCallsStack, $this->dynamicConstantNames, false, - $this->objectFromNewClass, $this->afterExtractCall, $this->parentScope ); @@ -4994,11 +4988,7 @@ private function getTypeToInstantiateForNew(Type $type): Type foreach ($type->getTypes() as $innerType) { $decidedType = $decideType($innerType); if ($decidedType === null) { - if ($this->objectFromNewClass) { - return new ObjectWithoutClassType(); - } - - return new MixedType(false, new StringType()); + return new ObjectWithoutClassType(); } $types[] = $decidedType; @@ -5009,11 +4999,7 @@ private function getTypeToInstantiateForNew(Type $type): Type $decidedType = $decideType($type); if ($decidedType === null) { - if ($this->objectFromNewClass) { - return new ObjectWithoutClassType(); - } - - return new MixedType(false, new StringType()); + return new ObjectWithoutClassType(); } return $decidedType; diff --git a/src/Testing/PHPStanTestCase.php b/src/Testing/PHPStanTestCase.php index 2cac14ec1a..042baa80bd 100644 --- a/src/Testing/PHPStanTestCase.php +++ b/src/Testing/PHPStanTestCase.php @@ -130,7 +130,6 @@ public function createScopeFactory(ReflectionProvider $reflectionProvider, TypeS $this->getParser(), self::getContainer()->getByType(NodeScopeResolver::class), $this->shouldTreatPhpDocTypesAsCertain(), - false, $container ); } diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 6ce746f5be..3a7c89d786 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -348,7 +348,8 @@ public function testBug1843(): void public function testBug4713(): void { $errors = $this->runAnalyse(__DIR__ . '/data/bug-4713.php'); - $this->assertCount(0, $errors); + $this->assertCount(1, $errors); + $this->assertSame('Method Bug4713\Service::createInstance() should return Bug4713\Service but returns object.', $errors[0]->getMessage()); $reflectionProvider = $this->createBroker(); $class = $reflectionProvider->getClass(Service::class); diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 94ae6a41df..7e1f8e5af3 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -1683,7 +1683,7 @@ public function dataDeductedTypes(): array '$loremObjectLiteral', ], [ - 'mixed~string', + 'object', '$mixedObjectLiteral', ], [ diff --git a/tests/PHPStan/Analyser/data/bug-4579.php b/tests/PHPStan/Analyser/data/bug-4579.php index bcaa6bb812..4b7c095153 100644 --- a/tests/PHPStan/Analyser/data/bug-4579.php +++ b/tests/PHPStan/Analyser/data/bug-4579.php @@ -6,7 +6,7 @@ function (string $class): void { $foo = new $class(); - assertType('mixed~string', $foo); + assertType('object', $foo); if (method_exists($foo, 'doFoo')) { assertType('object&hasMethod(doFoo)', $foo); } diff --git a/tests/PHPStan/Analyser/data/generic-class-string.php b/tests/PHPStan/Analyser/data/generic-class-string.php index f7cec7de62..45f1dfaddf 100644 --- a/tests/PHPStan/Analyser/data/generic-class-string.php +++ b/tests/PHPStan/Analyser/data/generic-class-string.php @@ -15,7 +15,7 @@ public static function f(): int { * @param mixed $a */ function testMixed($a) { - assertType('mixed~string', new $a()); + assertType('object', new $a()); if (is_subclass_of($a, 'DateTimeInterface')) { assertType('class-string|DateTimeInterface', $a); @@ -36,7 +36,7 @@ function testMixed($a) { * @param object $a */ function testObject($a) { - assertType('mixed~string', new $a()); + assertType('object', new $a()); if (is_subclass_of($a, 'DateTimeInterface')) { assertType('DateTimeInterface', $a); @@ -47,7 +47,7 @@ function testObject($a) { * @param string $a */ function testString($a) { - assertType('mixed~string', new $a()); + assertType('object', new $a()); if (is_subclass_of($a, 'DateTimeInterface')) { assertType('class-string', $a); @@ -63,7 +63,7 @@ function testString($a) { * @param string|object $a */ function testStringObject($a) { - assertType('mixed~string', new $a()); + assertType('object', new $a()); if (is_subclass_of($a, 'DateTimeInterface')) { assertType('class-string|DateTimeInterface', $a); @@ -92,7 +92,7 @@ function testClassExists(string $str) assertType('string', $str); if (class_exists($str)) { assertType('class-string', $str); - assertType('mixed~string', new $str()); + assertType('object', new $str()); } $existentClass = \stdClass::class; From a72d2a23b298bcef8dedd7dbd933d965cee6041a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 16 Sep 2021 10:25:42 +0200 Subject: [PATCH 0311/1284] Allow validating PHPStan's stub files --- src/Command/CommandHelper.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php index 85e4aeb422..b83b215dc9 100644 --- a/src/Command/CommandHelper.php +++ b/src/Command/CommandHelper.php @@ -221,7 +221,10 @@ public static function begin( } } - if ($projectConfigFile !== null) { + if ( + $projectConfigFile !== null + && $projectConfigFile !== $currentWorkingDirectoryFileHelper->normalizePath(__DIR__ . '/../../conf/config.stubFiles.neon') + ) { $additionalConfigFiles[] = $projectConfigFile; } From 54141c46692d76d299d9f5967a3e35d1b1a6cf5d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 16 Sep 2021 10:27:43 +0200 Subject: [PATCH 0312/1284] Fix Exception and Error PHPDoc stubs --- stubs/Exception.stub | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/stubs/Exception.stub b/stubs/Exception.stub index ec3b18f927..70f01e55f1 100644 --- a/stubs/Exception.stub +++ b/stubs/Exception.stub @@ -58,7 +58,7 @@ class Exception implements Throwable * @return string * @throws void */ - final public function getMessage() {} + final public function getMessage(): string {} /** * @return mixed @@ -70,31 +70,31 @@ class Exception implements Throwable * @return string * @throws void */ - final public function getFile() {} + final public function getFile(): string {} /** * @return int * @throws void */ - final public function getLine() {} + final public function getLine(): int {} /** * @return list> * @throws void */ - final public function getTrace() {} + final public function getTrace(): array {} /** * @return null|Throwable * @throws void */ - final public function getPrevious() {} + final public function getPrevious(): ?Throwable {} /** * @return string * @throws void */ - final public function getTraceAsString() {} + final public function getTraceAsString(): string {} } @@ -105,7 +105,7 @@ class Error implements Throwable * @return string * @throws void */ - final public function getMessage() {} + final public function getMessage(): string {} /** * @return mixed @@ -117,30 +117,30 @@ class Error implements Throwable * @return string * @throws void */ - final public function getFile() {} + final public function getFile(): string {} /** * @return int * @throws void */ - final public function getLine() {} + final public function getLine(): int {} /** * @return list> * @throws void */ - final public function getTrace() {} + final public function getTrace(): array {} /** * @return null|Throwable * @throws void */ - final public function getPrevious() {} + final public function getPrevious(): ?Throwable {} /** * @return string * @throws void */ - final public function getTraceAsString() {} + final public function getTraceAsString(): string {} } From b170a4f1d8c04ff88a4c5de3ff290bb6433684b1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 16 Sep 2021 10:31:56 +0200 Subject: [PATCH 0313/1284] ClosureUsesThisRule moved to phpstan-strict-rules --- conf/config.level0.neon | 1 - src/Rules/Functions/ClosureUsesThisRule.php | 45 ------------------- .../Functions/ClosureUsesThisRuleTest.php | 29 ------------ .../Functions/data/closure-uses-this.php | 26 ----------- 4 files changed, 101 deletions(-) delete mode 100644 src/Rules/Functions/ClosureUsesThisRule.php delete mode 100644 tests/PHPStan/Rules/Functions/ClosureUsesThisRuleTest.php delete mode 100644 tests/PHPStan/Rules/Functions/data/closure-uses-this.php diff --git a/conf/config.level0.neon b/conf/config.level0.neon index 8af45339c8..696ee23a62 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -50,7 +50,6 @@ rules: - PHPStan\Rules\Functions\ArrowFunctionReturnNullsafeByRefRule - PHPStan\Rules\Functions\CallToFunctionParametersRule - PHPStan\Rules\Functions\ClosureAttributesRule - - PHPStan\Rules\Functions\ClosureUsesThisRule - PHPStan\Rules\Functions\ExistingClassesInArrowFunctionTypehintsRule - PHPStan\Rules\Functions\ExistingClassesInClosureTypehintsRule - PHPStan\Rules\Functions\ExistingClassesInTypehintsRule diff --git a/src/Rules/Functions/ClosureUsesThisRule.php b/src/Rules/Functions/ClosureUsesThisRule.php deleted file mode 100644 index 60018f454b..0000000000 --- a/src/Rules/Functions/ClosureUsesThisRule.php +++ /dev/null @@ -1,45 +0,0 @@ - - */ -class ClosureUsesThisRule implements Rule -{ - - public function getNodeType(): string - { - return Node\Expr\Closure::class; - } - - public function processNode(Node $node, Scope $scope): array - { - if ($node->static) { - return []; - } - - $messages = []; - foreach ($node->uses as $closureUse) { - $varType = $scope->getType($closureUse->var); - if (!is_string($closureUse->var->name)) { - continue; - } - if (!$varType instanceof ThisType) { - continue; - } - - $messages[] = RuleErrorBuilder::message(sprintf('Anonymous function uses $this assigned to variable $%s. Use $this directly in the function body.', $closureUse->var->name)) - ->line($closureUse->getLine()) - ->build(); - } - return $messages; - } - -} diff --git a/tests/PHPStan/Rules/Functions/ClosureUsesThisRuleTest.php b/tests/PHPStan/Rules/Functions/ClosureUsesThisRuleTest.php deleted file mode 100644 index a84faa310a..0000000000 --- a/tests/PHPStan/Rules/Functions/ClosureUsesThisRuleTest.php +++ /dev/null @@ -1,29 +0,0 @@ - - */ -class ClosureUsesThisRuleTest extends RuleTestCase -{ - - protected function getRule(): Rule - { - return new ClosureUsesThisRule(); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/closure-uses-this.php'], [ - [ - 'Anonymous function uses $this assigned to variable $that. Use $this directly in the function body.', - 16, - ], - ]); - } - -} diff --git a/tests/PHPStan/Rules/Functions/data/closure-uses-this.php b/tests/PHPStan/Rules/Functions/data/closure-uses-this.php deleted file mode 100644 index e298303d90..0000000000 --- a/tests/PHPStan/Rules/Functions/data/closure-uses-this.php +++ /dev/null @@ -1,26 +0,0 @@ - Date: Thu, 16 Sep 2021 13:39:16 +0200 Subject: [PATCH 0314/1284] Basic support for interface-string and trait-string --- src/PhpDoc/TypeNodeResolver.php | 4 ++- .../Analyser/NodeScopeResolverTest.php | 1 + .../Analyser/data/more-type-strings.php | 29 +++++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Analyser/data/more-type-strings.php diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index 54fef4e139..0533c2acfd 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -151,6 +151,8 @@ private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameSco return new IntersectionType([new StringType(), new AccessoryLiteralStringType()]); case 'class-string': + case 'interface-string': + case 'trait-string': return new ClassStringType(); case 'callable-string': @@ -480,7 +482,7 @@ private function resolveGenericTypeNode(GenericTypeNode $typeNode, NameScope $na if (count($genericTypes) === 2) { // iterable return new IterableType($genericTypes[0], $genericTypes[1]); } - } elseif ($mainTypeName === 'class-string') { + } elseif (in_array($mainTypeName, ['class-string', 'interface-string'], true)) { if (count($genericTypes) === 1) { $genericType = $genericTypes[0]; if ((new ObjectWithoutClassType())->isSuperTypeOf($genericType)->yes() || $genericType instanceof MixedType) { diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 631e4ae8b8..bfdd577773 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -506,6 +506,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/range-numeric-string.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/missing-closure-native-return-typehint.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4741.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/more-type-strings.php'); } /** diff --git a/tests/PHPStan/Analyser/data/more-type-strings.php b/tests/PHPStan/Analyser/data/more-type-strings.php new file mode 100644 index 0000000000..0951303e20 --- /dev/null +++ b/tests/PHPStan/Analyser/data/more-type-strings.php @@ -0,0 +1,29 @@ + $genericInterfaceString + * @param trait-string $genericTraitString + */ + public function doFoo( + string $interfaceString, + string $traitString, + string $genericInterfaceString, + string $genericTraitString + ): void + { + assertType('class-string', $interfaceString); + assertType('class-string', $traitString); + assertType('class-string', $genericInterfaceString); + assertType('string', $genericTraitString); + } + +} From 18adc8c99f725ce95f097cba0ece1369626f26d1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 16 Sep 2021 14:15:06 +0200 Subject: [PATCH 0315/1284] Throwable__toString() can throw an exception --- stubs/Exception.stub | 1 - 1 file changed, 1 deletion(-) diff --git a/stubs/Exception.stub b/stubs/Exception.stub index 70f01e55f1..e95c24b250 100644 --- a/stubs/Exception.stub +++ b/stubs/Exception.stub @@ -46,7 +46,6 @@ interface Throwable /** * @return string - * @throws void */ public function __toString(); } From 956a7f483552dff1e3fb8195651bd983304ac17e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 16 Sep 2021 15:05:22 +0200 Subject: [PATCH 0316/1284] Fixed new $object with a template type --- src/Analyser/MutatingScope.php | 6 +++--- tests/PHPStan/Analyser/data/generics.php | 25 ++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index d688a5b435..f42133ca9c 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -4974,12 +4974,12 @@ private function getTypeToInstantiateForNew(Type $type): Type if ($type instanceof ConstantStringType) { return new ObjectType($type->getValue()); } - if ($type instanceof TypeWithClassName) { - return $type; - } if ($type instanceof GenericClassStringType) { return $type->getGenericType(); } + if ((new ObjectWithoutClassType())->isSuperTypeOf($type)->yes()) { + return $type; + } return null; }; diff --git a/tests/PHPStan/Analyser/data/generics.php b/tests/PHPStan/Analyser/data/generics.php index aaecc87e75..8488158b12 100644 --- a/tests/PHPStan/Analyser/data/generics.php +++ b/tests/PHPStan/Analyser/data/generics.php @@ -1462,3 +1462,28 @@ function foooo(UnionT $foo): void { assertType('string|null', $foo->doFoo('a')); } + +/** + * @template T1 of object + * @param T1 $type + * @return T1 + */ +function newObject($type): void +{ + assertType('T1 of object (function PHPStan\Generics\FunctionsAssertType\newObject(), argument)', new $type); +} + +function newStdClass(\stdClass $std): void +{ + assertType('stdClass', new $std); +} + +/** + * @template T1 of object + * @param class-string $type + * @return T1 + */ +function newClassString($type): void +{ + assertType('T1 of object (function PHPStan\Generics\FunctionsAssertType\newClassString(), argument)', new $type); +} From c0ad14bb8d171bdcd757dbcc3b5a9a4df2cab5d2 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 16 Sep 2021 15:09:56 +0200 Subject: [PATCH 0317/1284] Regression test Closes https://github.com/phpstan/phpstan/issues/2782 --- .../CallToFunctionParametersRuleTest.php | 10 ++++++++++ .../PHPStan/Rules/Functions/data/bug-2782.php | 19 +++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 tests/PHPStan/Rules/Functions/data/bug-2782.php diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 7cdd046914..0714cd96ce 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -901,4 +901,14 @@ public function testBug1954(): void ]); } + public function testBug2782(): void + { + $this->analyse([__DIR__ . '/data/bug-2782.php'], [ + [ + 'Parameter #2 $callback of function usort expects callable(stdClass, stdClass): int, Closure(int, int): -1|1 given.', + 13, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/bug-2782.php b/tests/PHPStan/Rules/Functions/data/bug-2782.php new file mode 100644 index 0000000000..5f4f587a16 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-2782.php @@ -0,0 +1,19 @@ + $j ? 1 : -1; + } + ); + } + +} From 3a773ac925fb99248ed3287eb196e22ee682a775 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 16 Sep 2021 15:18:50 +0200 Subject: [PATCH 0318/1284] Update E2E test --- tests/e2e/ResultCacheEndToEndTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/e2e/ResultCacheEndToEndTest.php b/tests/e2e/ResultCacheEndToEndTest.php index c1fdff0fab..d9a12e7a4a 100644 --- a/tests/e2e/ResultCacheEndToEndTest.php +++ b/tests/e2e/ResultCacheEndToEndTest.php @@ -104,7 +104,7 @@ public function testResultCacheDeleteFile(): void $fileHelper = new FileHelper(__DIR__); $result = $this->runPhpstan(1); - $this->assertSame(4, $result['totals']['file_errors'], Json::encode($result)); + $this->assertSame(5, $result['totals']['file_errors'], Json::encode($result)); $this->assertSame(0, $result['totals']['errors'], Json::encode($result)); $message = $result['files'][$fileHelper->normalizePath(__DIR__ . '/PHP-Parser/lib/PhpParser/Serializer/XML.php')]['messages'][0]['message']; @@ -113,6 +113,7 @@ public function testResultCacheDeleteFile(): void $this->assertSame('Reflection error: PhpParser\Serializer not found.', $result['files'][$fileHelper->normalizePath(__DIR__ . '/PHP-Parser/lib/PhpParser/Serializer/XML.php')]['messages'][1]['message']); $this->assertSame('Reflection error: PhpParser\Serializer not found.', $result['files'][$fileHelper->normalizePath(__DIR__ . '/PHP-Parser/lib/PhpParser/Serializer/XML.php')]['messages'][2]['message']); $this->assertSame('Reflection error: PhpParser\Serializer not found.', $result['files'][$fileHelper->normalizePath(__DIR__ . '/PHP-Parser/lib/PhpParser/Serializer/XML.php')]['messages'][3]['message']); + $this->assertSame('Reflection error: PhpParser\Serializer not found.', $result['files'][$fileHelper->normalizePath(__DIR__ . '/PHP-Parser/lib/PhpParser/Serializer/XML.php')]['messages'][4]['message']); file_put_contents($serializerPath, $originalSerializerCode); $this->runPhpstan(0); From 27a5d0f10b1b9b65c3165ddae500e40b769d1301 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 16 Sep 2021 16:54:07 +0200 Subject: [PATCH 0319/1284] Rename cmp_function parameters to callback --- resources/functionMap.php | 18 +++--- .../Php8SignatureMapProviderTest.php | 2 +- .../CallToFunctionParametersRuleTest.php | 57 +++---------------- 3 files changed, 18 insertions(+), 59 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index a52dbdbeeb..b4a7943555 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -346,8 +346,8 @@ 'ArrayIterator::seek' => ['void', 'position'=>'int'], 'ArrayIterator::serialize' => ['string'], 'ArrayIterator::setFlags' => ['void', 'flags'=>'string'], -'ArrayIterator::uasort' => ['void', 'cmp_function'=>'callable(mixed,mixed):int'], -'ArrayIterator::uksort' => ['void', 'cmp_function'=>'callable(array-key,array-key):int'], +'ArrayIterator::uasort' => ['void', 'callback'=>'callable(mixed,mixed):int'], +'ArrayIterator::uksort' => ['void', 'callback'=>'callable(array-key,array-key):int'], 'ArrayIterator::unserialize' => ['void', 'serialized'=>'string'], 'ArrayIterator::valid' => ['bool'], 'ArrayObject::__construct' => ['void', 'input='=>'array|object', 'flags='=>'int', 'iterator_class='=>'string'], @@ -369,8 +369,8 @@ 'ArrayObject::serialize' => ['string'], 'ArrayObject::setFlags' => ['void', 'flags'=>'int'], 'ArrayObject::setIteratorClass' => ['void', 'iterator_class'=>'string'], -'ArrayObject::uasort' => ['void', 'cmp_function'=>'callable'], -'ArrayObject::uksort' => ['void', 'cmp_function'=>'callable(array-key,array-key):int'], +'ArrayObject::uasort' => ['void', 'callback'=>'callable'], +'ArrayObject::uksort' => ['void', 'callback'=>'callable(array-key,array-key):int'], 'ArrayObject::unserialize' => ['void', 'serialized'=>'string'], 'arsort' => ['bool', '&rw_array_arg'=>'array', 'sort_flags='=>'int'], 'asin' => ['float', 'number'=>'float'], @@ -9174,8 +9174,8 @@ 'RecursiveArrayIterator::seek' => ['void', 'position'=>'int'], 'RecursiveArrayIterator::serialize' => ['string'], 'RecursiveArrayIterator::setFlags' => ['void', 'flags'=>'string'], -'RecursiveArrayIterator::uasort' => ['void', 'cmp_function'=>'callable(mixed,mixed):int'], -'RecursiveArrayIterator::uksort' => ['void', 'cmp_function'=>'callable(array-key,array-key):int'], +'RecursiveArrayIterator::uasort' => ['void', 'callback'=>'callable(mixed,mixed):int'], +'RecursiveArrayIterator::uksort' => ['void', 'callback'=>'callable(array-key,array-key):int'], 'RecursiveArrayIterator::unserialize' => ['string', 'serialized'=>'string'], 'RecursiveArrayIterator::valid' => ['bool'], 'RecursiveCachingIterator::__construct' => ['void', 'iterator'=>'Iterator', 'flags'=>''], @@ -12565,7 +12565,7 @@ 'TypeError::getPrevious' => ['Throwable|TypeError|null'], 'TypeError::getTrace' => ['array'], 'TypeError::getTraceAsString' => ['string'], -'uasort' => ['bool', '&rw_array_arg'=>'array', 'cmp_function'=>'callable(mixed,mixed):int'], +'uasort' => ['bool', '&rw_array_arg'=>'array', 'callback'=>'callable(mixed,mixed):int'], 'ucfirst' => ['string', 'str'=>'string'], 'UConverter::__construct' => ['void', 'destination_encoding'=>'string', 'source_encoding='=>'string'], 'UConverter::convert' => ['string', 'str'=>'string', 'reverse='=>'bool'], @@ -12614,7 +12614,7 @@ 'ui\draw\text\font\fontfamilies' => ['array'], 'ui\quit' => ['void'], 'ui\run' => ['void', 'flags='=>'int'], -'uksort' => ['bool', '&rw_array_arg'=>'array', 'cmp_function'=>'callable(array-key,array-key):int'], +'uksort' => ['bool', '&rw_array_arg'=>'array', 'callback'=>'callable(array-key,array-key):int'], 'umask' => ['int', 'mask='=>'int'], 'UnderflowException::__clone' => ['void'], 'UnderflowException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'previous='=>'(?Throwable)|(?UnderflowException)'], @@ -12694,7 +12694,7 @@ 'urlencode' => ['string', 'str'=>'string'], 'use_soap_error_handler' => ['bool', 'handler='=>'bool'], 'usleep' => ['void', 'micro_seconds'=>'int'], -'usort' => ['bool', '&rw_array_arg'=>'array', 'cmp_function'=>'callable(mixed,mixed):int'], +'usort' => ['bool', '&rw_array_arg'=>'array', 'callback'=>'callable(mixed,mixed):int'], 'utf8_decode' => ['string', 'data'=>'string'], 'utf8_encode' => ['string', 'data'=>'string'], 'V8Js::__construct' => ['void', 'object_name='=>'string', 'variables='=>'array', 'extensions='=>'array', 'report_uncaught_exceptions='=>'bool', 'snapshot_blob='=>'string'], diff --git a/tests/PHPStan/Reflection/SignatureMap/Php8SignatureMapProviderTest.php b/tests/PHPStan/Reflection/SignatureMap/Php8SignatureMapProviderTest.php index 390ad5a955..eab83934f2 100644 --- a/tests/PHPStan/Reflection/SignatureMap/Php8SignatureMapProviderTest.php +++ b/tests/PHPStan/Reflection/SignatureMap/Php8SignatureMapProviderTest.php @@ -220,7 +220,7 @@ public function dataMethods(): array 'uasort', [ [ - 'name' => 'cmp_function', + 'name' => 'callback', 'optional' => false, 'type' => new CallableType([ new NativeParameterReflection('', false, new MixedType(true), PassedByReference::createNo(), false, null), diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 0714cd96ce..cfdaaad4ba 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -630,16 +630,9 @@ public function testArrayWalkArrowFunctionCallback(): void public function testUasortCallback(): void { - $paramTwoName = PHP_VERSION_ID >= 80000 - ? 'callback' - : 'cmp_function'; - $this->analyse([__DIR__ . '/data/uasort.php'], [ [ - sprintf( - 'Parameter #2 $%s of function uasort expects callable(int, int): int, Closure(string, string): 1 given.', - $paramTwoName - ), + 'Parameter #2 $callback of function uasort expects callable(int, int): int, Closure(string, string): 1 given.', 7, ], ]); @@ -650,16 +643,10 @@ public function testUasortArrowFunctionCallback(): void if (PHP_VERSION_ID < 70400 && !self::$useStaticReflectionProvider) { $this->markTestSkipped('Test requires PHP 7.4.'); } - $paramTwoName = PHP_VERSION_ID >= 80000 - ? 'callback' - : 'cmp_function'; $this->analyse([__DIR__ . '/data/uasort_arrow.php'], [ [ - sprintf( - 'Parameter #2 $%s of function uasort expects callable(int, int): int, Closure(string, string): 1 given.', - $paramTwoName - ), + 'Parameter #2 $callback of function uasort expects callable(int, int): int, Closure(string, string): 1 given.', 7, ], ]); @@ -667,16 +654,9 @@ public function testUasortArrowFunctionCallback(): void public function testUsortCallback(): void { - $paramTwoName = PHP_VERSION_ID >= 80000 - ? 'callback' - : 'cmp_function'; - $this->analyse([__DIR__ . '/data/usort.php'], [ [ - sprintf( - 'Parameter #2 $%s of function usort expects callable(int, int): int, Closure(string, string): 1 given.', - $paramTwoName - ), + 'Parameter #2 $callback of function usort expects callable(int, int): int, Closure(string, string): 1 given.', 14, ], ]); @@ -688,16 +668,9 @@ public function testUsortArrowFunctionCallback(): void $this->markTestSkipped('Test requires PHP 7.4.'); } - $paramTwoName = PHP_VERSION_ID >= 80000 - ? 'callback' - : 'cmp_function'; - $this->analyse([__DIR__ . '/data/usort_arrow.php'], [ [ - sprintf( - 'Parameter #2 $%s of function usort expects callable(int, int): int, Closure(string, string): 1 given.', - $paramTwoName - ), + 'Parameter #2 $callback of function usort expects callable(int, int): int, Closure(string, string): 1 given.', 14, ], ]); @@ -705,20 +678,13 @@ public function testUsortArrowFunctionCallback(): void public function testUksortCallback(): void { - $paramTwoName = PHP_VERSION_ID >= 80000 - ? 'callback' - : 'cmp_function'; - $this->analyse([__DIR__ . '/data/uksort.php'], [ [ - sprintf( - 'Parameter #2 $%s of function uksort expects callable(string, string): int, Closure(stdClass, stdClass): 1 given.', - $paramTwoName - ), + 'Parameter #2 $callback of function uksort expects callable(string, string): int, Closure(stdClass, stdClass): 1 given.', 14, ], [ - sprintf('Parameter #2 $%s of function uksort expects callable(int, int): int, Closure(string, string): 1 given.', $paramTwoName), + 'Parameter #2 $callback of function uksort expects callable(int, int): int, Closure(string, string): 1 given.', 50, ], ]); @@ -730,20 +696,13 @@ public function testUksortArrowFunctionCallback(): void $this->markTestSkipped('Test requires PHP 7.4.'); } - $paramTwoName = PHP_VERSION_ID >= 80000 - ? 'callback' - : 'cmp_function'; - $this->analyse([__DIR__ . '/data/uksort_arrow.php'], [ [ - sprintf( - 'Parameter #2 $%s of function uksort expects callable(string, string): int, Closure(stdClass, stdClass): 1 given.', - $paramTwoName - ), + 'Parameter #2 $callback of function uksort expects callable(string, string): int, Closure(stdClass, stdClass): 1 given.', 14, ], [ - sprintf('Parameter #2 $%s of function uksort expects callable(int, int): int, Closure(string, string): 1 given.', $paramTwoName), + 'Parameter #2 $callback of function uksort expects callable(int, int): int, Closure(string, string): 1 given.', 44, ], ]); From e1ccc856a3333d77f9c5527f32d78e38ac27215e Mon Sep 17 00:00:00 2001 From: Danack Date: Mon, 6 Jul 2020 22:14:42 +0100 Subject: [PATCH 0320/1284] Change message 'typehint' to 'type' and 'typehint type' to 'type'. --- ...ingClassesInArrowFunctionTypehintsRule.php | 4 +- .../ExistingClassesInClosureTypehintsRule.php | 4 +- .../ExistingClassesInTypehintsRule.php | 4 +- .../MissingFunctionParameterTypehintRule.php | 2 +- .../MissingFunctionReturnTypehintRule.php | 2 +- .../ExistingClassesInTypehintsRule.php | 4 +- .../MissingMethodParameterTypehintRule.php | 2 +- .../MissingMethodReturnTypehintRule.php | 2 +- .../MissingPropertyTypehintRule.php | 2 +- ...TooWideArrowFunctionReturnTypehintRule.php | 2 +- .../TooWideClosureReturnTypehintRule.php | 2 +- .../TooWideFunctionReturnTypehintRule.php | 2 +- .../TooWideMethodReturnTypehintRule.php | 2 +- .../Analyser/AnalyserIntegrationTest.php | 8 ++- .../ErrorFormatter/data/unixBaseline.neon | 4 +- .../ErrorFormatter/data/windowsBaseline.neon | 4 +- tests/PHPStan/Generics/data/functions-6.json | 2 +- tests/PHPStan/Generics/data/pick-6.json | 2 +- .../Generics/data/varyingAcceptor-6.json | 2 +- tests/PHPStan/Levels/data/acceptTypes-6.json | 60 +++++++++---------- tests/PHPStan/Levels/data/arrayAccess-6.json | 10 ++-- .../Levels/data/arrayDimFetches-6.json | 4 +- tests/PHPStan/Levels/data/binaryOps-6.json | 2 +- .../PHPStan/Levels/data/callableCalls-6.json | 4 +- .../Levels/data/callableVariance-6.json | 4 +- tests/PHPStan/Levels/data/casts-6.json | 2 +- tests/PHPStan/Levels/data/clone-6.json | 2 +- tests/PHPStan/Levels/data/comparison-6.json | 2 +- .../Levels/data/constantAccesses-6.json | 4 +- .../Levels/data/inferPropertyType-6.json | 4 +- tests/PHPStan/Levels/data/iterable-6.json | 2 +- tests/PHPStan/Levels/data/methodCalls-6.json | 28 ++++----- tests/PHPStan/Levels/data/object-6.json | 6 +- .../Levels/data/propertyAccesses-6.json | 16 ++--- .../PHPStan/Levels/data/stubValidator-0.json | 8 +-- .../Levels/data/stubs-functions-6.json | 4 +- .../PHPStan/Levels/data/stubs-methods-6.json | 10 ++-- tests/PHPStan/Levels/data/throwValues-6.json | 2 +- tests/PHPStan/Levels/data/typehints-0.json | 4 +- tests/PHPStan/Levels/data/typehints-2.json | 4 +- .../Levels/data/unreachable-6-alwaysTrue.json | 24 ++++---- tests/PHPStan/Levels/data/unreachable-6.json | 24 ++++---- .../ParallelAnalyserIntegrationTest.php | 4 +- ...lassesInArrowFunctionTypehintsRuleTest.php | 4 +- ...stingClassesInClosureTypehintsRuleTest.php | 14 ++--- .../ExistingClassesInTypehintsRuleTest.php | 34 +++++------ ...ssingFunctionParameterTypehintRuleTest.php | 6 +- .../MissingFunctionReturnTypehintRuleTest.php | 4 +- .../ExistingClassesInTypehintsRuleTest.php | 36 +++++------ ...MissingMethodParameterTypehintRuleTest.php | 10 ++-- .../MissingMethodReturnTypehintRuleTest.php | 8 +-- .../MissingPropertyTypehintRuleTest.php | 8 +-- ...ideArrowFunctionReturnTypehintRuleTest.php | 2 +- .../TooWideClosureReturnTypehintRuleTest.php | 2 +- .../TooWideFunctionReturnTypehintRuleTest.php | 12 ++-- .../TooWideMethodReturnTypehintRuleTest.php | 24 ++++---- 56 files changed, 228 insertions(+), 226 deletions(-) diff --git a/src/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRule.php b/src/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRule.php index 8b5e4a304f..85de1d6b5c 100644 --- a/src/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRule.php +++ b/src/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRule.php @@ -30,8 +30,8 @@ public function processNode(Node $node, Scope $scope): array $scope, $node->getParams(), $node->getReturnType(), - 'Parameter $%s of anonymous function has invalid typehint type %s.', - 'Return typehint of anonymous function has invalid type %s.', + 'Parameter $%s of anonymous function has invalid type %s.', + 'Anonymous function has invalid return type %s.', 'Anonymous function uses native union types but they\'re supported only on PHP 8.0 and later.' ); } diff --git a/src/Rules/Functions/ExistingClassesInClosureTypehintsRule.php b/src/Rules/Functions/ExistingClassesInClosureTypehintsRule.php index 3b2d45f799..9033078f9b 100644 --- a/src/Rules/Functions/ExistingClassesInClosureTypehintsRule.php +++ b/src/Rules/Functions/ExistingClassesInClosureTypehintsRule.php @@ -31,8 +31,8 @@ public function processNode(Node $node, Scope $scope): array $scope, $node->getParams(), $node->getReturnType(), - 'Parameter $%s of anonymous function has invalid typehint type %s.', - 'Return typehint of anonymous function has invalid type %s.', + 'Parameter $%s of anonymous function has invalid type %s.', + 'Anonymous function has invalid return type %s.', 'Anonymous function uses native union types but they\'re supported only on PHP 8.0 and later.' ); } diff --git a/src/Rules/Functions/ExistingClassesInTypehintsRule.php b/src/Rules/Functions/ExistingClassesInTypehintsRule.php index c03593ec9c..0b3cfdc77b 100644 --- a/src/Rules/Functions/ExistingClassesInTypehintsRule.php +++ b/src/Rules/Functions/ExistingClassesInTypehintsRule.php @@ -38,11 +38,11 @@ public function processNode(Node $node, Scope $scope): array $node->getOriginalNode(), $scope->getFunction(), sprintf( - 'Parameter $%%s of function %s() has invalid typehint type %%s.', + 'Parameter $%%s of function %s() has invalid type %%s.', $functionName ), sprintf( - 'Return typehint of function %s() has invalid type %%s.', + 'Function %s() has invalid return type %%s.', $functionName ), sprintf('Function %s() uses native union types but they\'re supported only on PHP 8.0 and later.', $functionName), diff --git a/src/Rules/Functions/MissingFunctionParameterTypehintRule.php b/src/Rules/Functions/MissingFunctionParameterTypehintRule.php index 63c6a43e79..5a86ac7662 100644 --- a/src/Rules/Functions/MissingFunctionParameterTypehintRule.php +++ b/src/Rules/Functions/MissingFunctionParameterTypehintRule.php @@ -64,7 +64,7 @@ private function checkFunctionParameter(FunctionReflection $functionReflection, if ($parameterType instanceof MixedType && !$parameterType->isExplicitMixed()) { return [ RuleErrorBuilder::message(sprintf( - 'Function %s() has parameter $%s with no typehint specified.', + 'Function %s() has parameter $%s with no type specified.', $functionReflection->getName(), $parameterReflection->getName() ))->build(), diff --git a/src/Rules/Functions/MissingFunctionReturnTypehintRule.php b/src/Rules/Functions/MissingFunctionReturnTypehintRule.php index 8d75bf1578..57d6533169 100644 --- a/src/Rules/Functions/MissingFunctionReturnTypehintRule.php +++ b/src/Rules/Functions/MissingFunctionReturnTypehintRule.php @@ -44,7 +44,7 @@ public function processNode(Node $node, Scope $scope): array if ($returnType instanceof MixedType && !$returnType->isExplicitMixed()) { return [ RuleErrorBuilder::message(sprintf( - 'Function %s() has no return typehint specified.', + 'Function %s() has no return type specified.', $functionReflection->getName() ))->build(), ]; diff --git a/src/Rules/Methods/ExistingClassesInTypehintsRule.php b/src/Rules/Methods/ExistingClassesInTypehintsRule.php index 18d088b5a6..3ca81d6c3b 100644 --- a/src/Rules/Methods/ExistingClassesInTypehintsRule.php +++ b/src/Rules/Methods/ExistingClassesInTypehintsRule.php @@ -40,12 +40,12 @@ public function processNode(Node $node, Scope $scope): array $methodReflection, $node->getOriginalNode(), sprintf( - 'Parameter $%%s of method %s::%s() has invalid typehint type %%s.', + 'Parameter $%%s of method %s::%s() has invalid type %%s.', $scope->getClassReflection()->getDisplayName(), $methodReflection->getName() ), sprintf( - 'Return typehint of method %s::%s() has invalid type %%s.', + 'Method %s::%s() has invalid return type %%s.', $scope->getClassReflection()->getDisplayName(), $methodReflection->getName() ), diff --git a/src/Rules/Methods/MissingMethodParameterTypehintRule.php b/src/Rules/Methods/MissingMethodParameterTypehintRule.php index 13a7c2983a..effd1d6a05 100644 --- a/src/Rules/Methods/MissingMethodParameterTypehintRule.php +++ b/src/Rules/Methods/MissingMethodParameterTypehintRule.php @@ -61,7 +61,7 @@ private function checkMethodParameter(MethodReflection $methodReflection, Parame if ($parameterType instanceof MixedType && !$parameterType->isExplicitMixed()) { return [ RuleErrorBuilder::message(sprintf( - 'Method %s::%s() has parameter $%s with no typehint specified.', + 'Method %s::%s() has parameter $%s with no type specified.', $methodReflection->getDeclaringClass()->getDisplayName(), $methodReflection->getName(), $parameterReflection->getName() diff --git a/src/Rules/Methods/MissingMethodReturnTypehintRule.php b/src/Rules/Methods/MissingMethodReturnTypehintRule.php index c8779f0e2a..bcacd1bc9c 100644 --- a/src/Rules/Methods/MissingMethodReturnTypehintRule.php +++ b/src/Rules/Methods/MissingMethodReturnTypehintRule.php @@ -42,7 +42,7 @@ public function processNode(Node $node, Scope $scope): array if ($returnType instanceof MixedType && !$returnType->isExplicitMixed()) { return [ RuleErrorBuilder::message(sprintf( - 'Method %s::%s() has no return typehint specified.', + 'Method %s::%s() has no return type specified.', $methodReflection->getDeclaringClass()->getDisplayName(), $methodReflection->getName() ))->build(), diff --git a/src/Rules/Properties/MissingPropertyTypehintRule.php b/src/Rules/Properties/MissingPropertyTypehintRule.php index f45f29ce7a..04a2d25fed 100644 --- a/src/Rules/Properties/MissingPropertyTypehintRule.php +++ b/src/Rules/Properties/MissingPropertyTypehintRule.php @@ -39,7 +39,7 @@ public function processNode(Node $node, Scope $scope): array if ($propertyType instanceof MixedType && !$propertyType->isExplicitMixed()) { return [ RuleErrorBuilder::message(sprintf( - 'Property %s::$%s has no typehint specified.', + 'Property %s::$%s has no type specified.', $propertyReflection->getDeclaringClass()->getDisplayName(), $node->getName() ))->build(), diff --git a/src/Rules/TooWideTypehints/TooWideArrowFunctionReturnTypehintRule.php b/src/Rules/TooWideTypehints/TooWideArrowFunctionReturnTypehintRule.php index cf064a1c87..b97c8f81e4 100644 --- a/src/Rules/TooWideTypehints/TooWideArrowFunctionReturnTypehintRule.php +++ b/src/Rules/TooWideTypehints/TooWideArrowFunctionReturnTypehintRule.php @@ -49,7 +49,7 @@ public function processNode(Node $node, Scope $scope): array } $messages[] = RuleErrorBuilder::message(sprintf( - 'Anonymous function never returns %s so it can be removed from the return typehint.', + 'Anonymous function never returns %s so it can be removed from the return type.', $type->describe(VerbosityLevel::getRecommendedLevelByType($type)) ))->build(); } diff --git a/src/Rules/TooWideTypehints/TooWideClosureReturnTypehintRule.php b/src/Rules/TooWideTypehints/TooWideClosureReturnTypehintRule.php index 0bcb7fa810..d70e4c92e5 100644 --- a/src/Rules/TooWideTypehints/TooWideClosureReturnTypehintRule.php +++ b/src/Rules/TooWideTypehints/TooWideClosureReturnTypehintRule.php @@ -71,7 +71,7 @@ public function processNode(Node $node, Scope $scope): array } $messages[] = RuleErrorBuilder::message(sprintf( - 'Anonymous function never returns %s so it can be removed from the return typehint.', + 'Anonymous function never returns %s so it can be removed from the return type.', $type->describe(VerbosityLevel::getRecommendedLevelByType($type)) ))->build(); } diff --git a/src/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRule.php b/src/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRule.php index ea35670a56..3506a48bed 100644 --- a/src/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRule.php +++ b/src/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRule.php @@ -79,7 +79,7 @@ public function processNode(Node $node, Scope $scope): array } $messages[] = RuleErrorBuilder::message(sprintf( - 'Function %s() never returns %s so it can be removed from the return typehint.', + 'Function %s() never returns %s so it can be removed from the return type.', $function->getName(), $type->describe(VerbosityLevel::getRecommendedLevelByType($type)) ))->build(); diff --git a/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php b/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php index dfe18c0b08..0cc7c1d17d 100644 --- a/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php +++ b/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php @@ -103,7 +103,7 @@ public function processNode(Node $node, Scope $scope): array } $messages[] = RuleErrorBuilder::message(sprintf( - 'Method %s::%s() never returns %s so it can be removed from the return typehint.', + 'Method %s::%s() never returns %s so it can be removed from the return type.', $method->getDeclaringClass()->getDisplayName(), $method->getName(), $type->describe(VerbosityLevel::getRecommendedLevelByType($type)) diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 3a7c89d786..68235a4c5a 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -134,9 +134,11 @@ public function testAnonymousClassWithWrongFilename(): void { $errors = $this->runAnalyse(__DIR__ . '/data/anonymous-class-wrong-filename-regression.php'); $this->assertCount(5, $errors); - $this->assertStringContainsString('Return typehint of method', $errors[0]->getMessage()); + $this->assertStringContainsString('Method', $errors[0]->getMessage()); + $this->assertStringContainsString('has invalid return type', $errors[0]->getMessage()); $this->assertSame(16, $errors[0]->getLine()); - $this->assertStringContainsString('Return typehint of method', $errors[1]->getMessage()); + $this->assertStringContainsString('Method', $errors[1]->getMessage()); + $this->assertStringContainsString('has invalid return type', $errors[1]->getMessage()); $this->assertSame(16, $errors[1]->getLine()); $this->assertSame('Instantiated class AnonymousClassWrongFilename\Bar not found.', $errors[2]->getMessage()); $this->assertSame(18, $errors[2]->getLine()); @@ -168,7 +170,7 @@ public function testNestedNamespaces(): void $this->assertCount(2, $errors); $this->assertSame('Property y\x::$baz has unknown class x\baz as its type.', $errors[0]->getMessage()); $this->assertSame(15, $errors[0]->getLine()); - $this->assertSame('Parameter $baz of method y\x::__construct() has invalid typehint type x\baz.', $errors[1]->getMessage()); + $this->assertSame('Parameter $baz of method y\x::__construct() has invalid type x\baz.', $errors[1]->getMessage()); $this->assertSame(16, $errors[1]->getLine()); } diff --git a/tests/PHPStan/Command/ErrorFormatter/data/unixBaseline.neon b/tests/PHPStan/Command/ErrorFormatter/data/unixBaseline.neon index df99f564ae..ca7c8f9c2c 100644 --- a/tests/PHPStan/Command/ErrorFormatter/data/unixBaseline.neon +++ b/tests/PHPStan/Command/ErrorFormatter/data/unixBaseline.neon @@ -1,12 +1,12 @@ parameters: ignoreErrors: - - message: "#^Method BaselineIntegration\\\\WindowsNewlines\\:\\:phpdocWithNewlines\\(\\) has no return typehint specified\\.$#" + message: "#^Method BaselineIntegration\\\\WindowsNewlines\\:\\:phpdocWithNewlines\\(\\) has no return type specified\\.$#" count: 1 path: WindowsNewlines.php - - message: "#^Method BaselineIntegration\\\\WindowsNewlines\\:\\:phpdocWithNewlines\\(\\) has parameter \\$object with no typehint specified\\.$#" + message: "#^Method BaselineIntegration\\\\WindowsNewlines\\:\\:phpdocWithNewlines\\(\\) has parameter \\$object with no type specified\\.$#" count: 1 path: WindowsNewlines.php diff --git a/tests/PHPStan/Command/ErrorFormatter/data/windowsBaseline.neon b/tests/PHPStan/Command/ErrorFormatter/data/windowsBaseline.neon index b914b6553f..3bfe998b6e 100644 --- a/tests/PHPStan/Command/ErrorFormatter/data/windowsBaseline.neon +++ b/tests/PHPStan/Command/ErrorFormatter/data/windowsBaseline.neon @@ -1,12 +1,12 @@ parameters: ignoreErrors: - - message: "#^Method BaselineIntegration\\\\UnixNewlines\\:\\:phpdocWithNewlines\\(\\) has no return typehint specified\\.$#" + message: "#^Method BaselineIntegration\\\\UnixNewlines\\:\\:phpdocWithNewlines\\(\\) has no return type specified\\.$#" count: 1 path: UnixNewlines.php - - message: "#^Method BaselineIntegration\\\\UnixNewlines\\:\\:phpdocWithNewlines\\(\\) has parameter \\$object with no typehint specified\\.$#" + message: "#^Method BaselineIntegration\\\\UnixNewlines\\:\\:phpdocWithNewlines\\(\\) has parameter \\$object with no type specified\\.$#" count: 1 path: UnixNewlines.php diff --git a/tests/PHPStan/Generics/data/functions-6.json b/tests/PHPStan/Generics/data/functions-6.json index 00b36cab69..72e5278a64 100644 --- a/tests/PHPStan/Generics/data/functions-6.json +++ b/tests/PHPStan/Generics/data/functions-6.json @@ -1,6 +1,6 @@ [ { - "message": "Function PHPStan\\Generics\\Functions\\testF() has no return typehint specified.", + "message": "Function PHPStan\\Generics\\Functions\\testF() has no return type specified.", "line": 27, "ignorable": true }, diff --git a/tests/PHPStan/Generics/data/pick-6.json b/tests/PHPStan/Generics/data/pick-6.json index 4b5aef0bd0..1598be5320 100644 --- a/tests/PHPStan/Generics/data/pick-6.json +++ b/tests/PHPStan/Generics/data/pick-6.json @@ -1,6 +1,6 @@ [ { - "message": "Function PHPStan\\Generics\\Pick\\test() has no return typehint specified.", + "message": "Function PHPStan\\Generics\\Pick\\test() has no return type specified.", "line": 22, "ignorable": true } diff --git a/tests/PHPStan/Generics/data/varyingAcceptor-6.json b/tests/PHPStan/Generics/data/varyingAcceptor-6.json index cb26b5d815..72f7b7dc7a 100644 --- a/tests/PHPStan/Generics/data/varyingAcceptor-6.json +++ b/tests/PHPStan/Generics/data/varyingAcceptor-6.json @@ -1,6 +1,6 @@ [ { - "message": "Function PHPStan\\Generics\\VaryingAcceptor\\testApply() has no return typehint specified.", + "message": "Function PHPStan\\Generics\\VaryingAcceptor\\testApply() has no return type specified.", "line": 30, "ignorable": true } diff --git a/tests/PHPStan/Levels/data/acceptTypes-6.json b/tests/PHPStan/Levels/data/acceptTypes-6.json index 543bf6620a..e4fa1ffa2c 100644 --- a/tests/PHPStan/Levels/data/acceptTypes-6.json +++ b/tests/PHPStan/Levels/data/acceptTypes-6.json @@ -1,151 +1,151 @@ [ { - "message": "Method Levels\\AcceptTypes\\Foo::doFoo() has no return typehint specified.", + "message": "Method Levels\\AcceptTypes\\Foo::doFoo() has no return type specified.", "line": 15, "ignorable": true }, { - "message": "Method Levels\\AcceptTypes\\Foo::doBar() has no return typehint specified.", + "message": "Method Levels\\AcceptTypes\\Foo::doBar() has no return type specified.", "line": 30, "ignorable": true }, { - "message": "Method Levels\\AcceptTypes\\Foo::doBaz() has no return typehint specified.", + "message": "Method Levels\\AcceptTypes\\Foo::doBaz() has no return type specified.", "line": 41, "ignorable": true }, { - "message": "Method Levels\\AcceptTypes\\Foo::doLorem() has no return typehint specified.", + "message": "Method Levels\\AcceptTypes\\Foo::doLorem() has no return type specified.", "line": 60, "ignorable": true }, { - "message": "Method Levels\\AcceptTypes\\Foo::doIpsum() has no return typehint specified.", + "message": "Method Levels\\AcceptTypes\\Foo::doIpsum() has no return type specified.", "line": 68, "ignorable": true }, { - "message": "Method Levels\\AcceptTypes\\Foo::doFooArray() has no return typehint specified.", + "message": "Method Levels\\AcceptTypes\\Foo::doFooArray() has no return type specified.", "line": 80, "ignorable": true }, { - "message": "Method Levels\\AcceptTypes\\Foo::doBarArray() has no return typehint specified.", + "message": "Method Levels\\AcceptTypes\\Foo::doBarArray() has no return type specified.", "line": 98, "ignorable": true }, { - "message": "Method Levels\\AcceptTypes\\Foo::doBazArray() has no return typehint specified.", + "message": "Method Levels\\AcceptTypes\\Foo::doBazArray() has no return type specified.", "line": 103, "ignorable": true }, { - "message": "Method Levels\\AcceptTypes\\Foo::doBazArrayUnionItemTypes() has no return typehint specified.", + "message": "Method Levels\\AcceptTypes\\Foo::doBazArrayUnionItemTypes() has no return type specified.", "line": 127, "ignorable": true }, { - "message": "Method Levels\\AcceptTypes\\Foo::callableArray() has no return typehint specified.", + "message": "Method Levels\\AcceptTypes\\Foo::callableArray() has no return type specified.", "line": 138, "ignorable": true }, { - "message": "Method Levels\\AcceptTypes\\Foo::expectCallable() has no return typehint specified.", + "message": "Method Levels\\AcceptTypes\\Foo::expectCallable() has no return type specified.", "line": 148, "ignorable": true }, { - "message": "Method Levels\\AcceptTypes\\Foo::iterableCountable() has no return typehint specified.", + "message": "Method Levels\\AcceptTypes\\Foo::iterableCountable() has no return type specified.", "line": 160, "ignorable": true }, { - "message": "Method Levels\\AcceptTypes\\Foo::benevolentUnionNotReported() has no return typehint specified.", + "message": "Method Levels\\AcceptTypes\\Foo::benevolentUnionNotReported() has no return type specified.", "line": 176, "ignorable": true }, { - "message": "Method Levels\\AcceptTypes\\ClosureAccepts::doFoo() has no return typehint specified.", + "message": "Method Levels\\AcceptTypes\\ClosureAccepts::doFoo() has no return type specified.", "line": 208, "ignorable": true }, { - "message": "Method Levels\\AcceptTypes\\ClosureAccepts::doFooUnionClosures() has no return typehint specified.", + "message": "Method Levels\\AcceptTypes\\ClosureAccepts::doFooUnionClosures() has no return type specified.", "line": 254, "ignorable": true }, { - "message": "Method Levels\\AcceptTypes\\ClosureAccepts::doBar() has no return typehint specified.", + "message": "Method Levels\\AcceptTypes\\ClosureAccepts::doBar() has no return type specified.", "line": 332, "ignorable": true }, { - "message": "Method Levels\\AcceptTypes\\ClosureAccepts::doBaz() has no return typehint specified.", + "message": "Method Levels\\AcceptTypes\\ClosureAccepts::doBaz() has no return type specified.", "line": 342, "ignorable": true }, { - "message": "Method Levels\\AcceptTypes\\Baz::doFoo() has no return typehint specified.", + "message": "Method Levels\\AcceptTypes\\Baz::doFoo() has no return type specified.", "line": 409, "ignorable": true }, { - "message": "Method Levels\\AcceptTypes\\Baz::doBar() has no return typehint specified.", + "message": "Method Levels\\AcceptTypes\\Baz::doBar() has no return type specified.", "line": 418, "ignorable": true }, { - "message": "Method Levels\\AcceptTypes\\Baz::doBaz() has no return typehint specified.", + "message": "Method Levels\\AcceptTypes\\Baz::doBaz() has no return type specified.", "line": 423, "ignorable": true }, { - "message": "Method Levels\\AcceptTypes\\Baz::doLorem() has no return typehint specified.", + "message": "Method Levels\\AcceptTypes\\Baz::doLorem() has no return type specified.", "line": 437, "ignorable": true }, { - "message": "Method Levels\\AcceptTypes\\Baz::doIpsum() has no return typehint specified.", + "message": "Method Levels\\AcceptTypes\\Baz::doIpsum() has no return type specified.", "line": 445, "ignorable": true }, { - "message": "Method Levels\\AcceptTypes\\Baz::doFooArray() has no return typehint specified.", + "message": "Method Levels\\AcceptTypes\\Baz::doFooArray() has no return type specified.", "line": 490, "ignorable": true }, { - "message": "Method Levels\\AcceptTypes\\Baz::doBarArray() has no return typehint specified.", + "message": "Method Levels\\AcceptTypes\\Baz::doBarArray() has no return type specified.", "line": 502, "ignorable": true }, { - "message": "Method Levels\\AcceptTypes\\Baz::testUnions() has no return typehint specified.", + "message": "Method Levels\\AcceptTypes\\Baz::testUnions() has no return type specified.", "line": 524, "ignorable": true }, { - "message": "Method Levels\\AcceptTypes\\Baz::testUnions2() has no return typehint specified.", + "message": "Method Levels\\AcceptTypes\\Baz::testUnions2() has no return type specified.", "line": 535, "ignorable": true }, { - "message": "Method Levels\\AcceptTypes\\Baz::requireArray() has no return typehint specified.", + "message": "Method Levels\\AcceptTypes\\Baz::requireArray() has no return type specified.", "line": 549, "ignorable": true }, { - "message": "Method Levels\\AcceptTypes\\Baz::requireFoo() has no return typehint specified.", + "message": "Method Levels\\AcceptTypes\\Baz::requireFoo() has no return type specified.", "line": 554, "ignorable": true }, { - "message": "Method Levels\\AcceptTypes\\ArrayShapes::doFoo() has no return typehint specified.", + "message": "Method Levels\\AcceptTypes\\ArrayShapes::doFoo() has no return type specified.", "line": 570, "ignorable": true }, { - "message": "Method Levels\\AcceptTypes\\ArrayShapes::doBar() has no return typehint specified.", + "message": "Method Levels\\AcceptTypes\\ArrayShapes::doBar() has no return type specified.", "line": 603, "ignorable": true } diff --git a/tests/PHPStan/Levels/data/arrayAccess-6.json b/tests/PHPStan/Levels/data/arrayAccess-6.json index bf65c24f00..3a8d649bcb 100644 --- a/tests/PHPStan/Levels/data/arrayAccess-6.json +++ b/tests/PHPStan/Levels/data/arrayAccess-6.json @@ -1,26 +1,26 @@ [ { - "message": "Method Levels\\ArrayAccess\\Foo::doFoo() has no return typehint specified.", + "message": "Method Levels\\ArrayAccess\\Foo::doFoo() has no return type specified.", "line": 11, "ignorable": true }, { - "message": "Method Levels\\ArrayAccess\\Foo::doBar() has no return typehint specified.", + "message": "Method Levels\\ArrayAccess\\Foo::doBar() has no return type specified.", "line": 22, "ignorable": true }, { - "message": "Method Levels\\ArrayAccess\\Foo::doBaz() has no return typehint specified.", + "message": "Method Levels\\ArrayAccess\\Foo::doBaz() has no return type specified.", "line": 30, "ignorable": true }, { - "message": "Method Levels\\ArrayAccess\\Foo::doLorem() has no return typehint specified.", + "message": "Method Levels\\ArrayAccess\\Foo::doLorem() has no return type specified.", "line": 38, "ignorable": true }, { - "message": "Method Levels\\ArrayAccess\\Foo::doLorem() has parameter $mixed with no typehint specified.", + "message": "Method Levels\\ArrayAccess\\Foo::doLorem() has parameter $mixed with no type specified.", "line": 38, "ignorable": true } diff --git a/tests/PHPStan/Levels/data/arrayDimFetches-6.json b/tests/PHPStan/Levels/data/arrayDimFetches-6.json index dfc3b07751..1a39a16f63 100644 --- a/tests/PHPStan/Levels/data/arrayDimFetches-6.json +++ b/tests/PHPStan/Levels/data/arrayDimFetches-6.json @@ -1,11 +1,11 @@ [ { - "message": "Method Levels\\ArrayDimFetches\\Foo::doFoo() has no return typehint specified.", + "message": "Method Levels\\ArrayDimFetches\\Foo::doFoo() has no return type specified.", "line": 12, "ignorable": true }, { - "message": "Method Levels\\ArrayDimFetches\\Foo::doBar() has no return typehint specified.", + "message": "Method Levels\\ArrayDimFetches\\Foo::doBar() has no return type specified.", "line": 31, "ignorable": true } diff --git a/tests/PHPStan/Levels/data/binaryOps-6.json b/tests/PHPStan/Levels/data/binaryOps-6.json index 41ae5dc405..f71cae89e6 100644 --- a/tests/PHPStan/Levels/data/binaryOps-6.json +++ b/tests/PHPStan/Levels/data/binaryOps-6.json @@ -1,6 +1,6 @@ [ { - "message": "Method Levels\\BinaryOps\\Foo::doFoo() has no return typehint specified.", + "message": "Method Levels\\BinaryOps\\Foo::doFoo() has no return type specified.", "line": 14, "ignorable": true } diff --git a/tests/PHPStan/Levels/data/callableCalls-6.json b/tests/PHPStan/Levels/data/callableCalls-6.json index 1ac782fa92..ad0d233ac4 100644 --- a/tests/PHPStan/Levels/data/callableCalls-6.json +++ b/tests/PHPStan/Levels/data/callableCalls-6.json @@ -1,11 +1,11 @@ [ { - "message": "Method Levels\\CallableCalls\\Foo::doFoo() has no return typehint specified.", + "message": "Method Levels\\CallableCalls\\Foo::doFoo() has no return type specified.", "line": 14, "ignorable": true }, { - "message": "Method Levels\\CallableCalls\\Foo::doBar() has no return typehint specified.", + "message": "Method Levels\\CallableCalls\\Foo::doBar() has no return type specified.", "line": 41, "ignorable": true } diff --git a/tests/PHPStan/Levels/data/callableVariance-6.json b/tests/PHPStan/Levels/data/callableVariance-6.json index d5b814feae..28eec65b64 100644 --- a/tests/PHPStan/Levels/data/callableVariance-6.json +++ b/tests/PHPStan/Levels/data/callableVariance-6.json @@ -1,11 +1,11 @@ [ { - "message": "Function Levels\\CallableVariance\\d() has no return typehint specified.", + "message": "Function Levels\\CallableVariance\\d() has no return type specified.", "line": 68, "ignorable": true }, { - "message": "Function Levels\\CallableVariance\\testD() has no return typehint specified.", + "message": "Function Levels\\CallableVariance\\testD() has no return type specified.", "line": 79, "ignorable": true } diff --git a/tests/PHPStan/Levels/data/casts-6.json b/tests/PHPStan/Levels/data/casts-6.json index 842590fde6..1fc1590698 100644 --- a/tests/PHPStan/Levels/data/casts-6.json +++ b/tests/PHPStan/Levels/data/casts-6.json @@ -1,6 +1,6 @@ [ { - "message": "Method Levels\\Casts\\Foo::doFoo() has no return typehint specified.", + "message": "Method Levels\\Casts\\Foo::doFoo() has no return type specified.", "line": 13, "ignorable": true } diff --git a/tests/PHPStan/Levels/data/clone-6.json b/tests/PHPStan/Levels/data/clone-6.json index 20f2c4389a..5a448fefc5 100644 --- a/tests/PHPStan/Levels/data/clone-6.json +++ b/tests/PHPStan/Levels/data/clone-6.json @@ -1,6 +1,6 @@ [ { - "message": "Method Levels\\Cloning\\Foo::doFoo() has no return typehint specified.", + "message": "Method Levels\\Cloning\\Foo::doFoo() has no return type specified.", "line": 18, "ignorable": true } diff --git a/tests/PHPStan/Levels/data/comparison-6.json b/tests/PHPStan/Levels/data/comparison-6.json index 39a509dbc4..c470766a2e 100644 --- a/tests/PHPStan/Levels/data/comparison-6.json +++ b/tests/PHPStan/Levels/data/comparison-6.json @@ -1,6 +1,6 @@ [ { - "message": "Method Levels\\Comparison\\Foo::doFoo() has no return typehint specified.", + "message": "Method Levels\\Comparison\\Foo::doFoo() has no return type specified.", "line": 18, "ignorable": true } diff --git a/tests/PHPStan/Levels/data/constantAccesses-6.json b/tests/PHPStan/Levels/data/constantAccesses-6.json index 8c3f1d74ee..2a9e2775c7 100644 --- a/tests/PHPStan/Levels/data/constantAccesses-6.json +++ b/tests/PHPStan/Levels/data/constantAccesses-6.json @@ -1,11 +1,11 @@ [ { - "message": "Method Levels\\ConstantAccesses\\Foo::doFoo() has no return typehint specified.", + "message": "Method Levels\\ConstantAccesses\\Foo::doFoo() has no return type specified.", "line": 14, "ignorable": true }, { - "message": "Method Levels\\ConstantAccesses\\Baz::doBaz() has no return typehint specified.", + "message": "Method Levels\\ConstantAccesses\\Baz::doBaz() has no return type specified.", "line": 42, "ignorable": true } diff --git a/tests/PHPStan/Levels/data/inferPropertyType-6.json b/tests/PHPStan/Levels/data/inferPropertyType-6.json index 20a2ad3c6e..42ce35fafd 100644 --- a/tests/PHPStan/Levels/data/inferPropertyType-6.json +++ b/tests/PHPStan/Levels/data/inferPropertyType-6.json @@ -1,11 +1,11 @@ [ { - "message": "Property InferPropertyType\\Foo::$bar has no typehint specified.", + "message": "Property InferPropertyType\\Foo::$bar has no type specified.", "line": 10, "ignorable": true }, { - "message": "Method InferPropertyType\\Foo::doFoo() has no return typehint specified.", + "message": "Method InferPropertyType\\Foo::doFoo() has no return type specified.", "line": 18, "ignorable": true } diff --git a/tests/PHPStan/Levels/data/iterable-6.json b/tests/PHPStan/Levels/data/iterable-6.json index d2cccc8bcb..d77f788ce1 100644 --- a/tests/PHPStan/Levels/data/iterable-6.json +++ b/tests/PHPStan/Levels/data/iterable-6.json @@ -1,6 +1,6 @@ [ { - "message": "Method Levels\\Iterables\\Foo::doFoo() has no return typehint specified.", + "message": "Method Levels\\Iterables\\Foo::doFoo() has no return type specified.", "line": 15, "ignorable": true } diff --git a/tests/PHPStan/Levels/data/methodCalls-6.json b/tests/PHPStan/Levels/data/methodCalls-6.json index d1f740f5cb..60f6147b69 100644 --- a/tests/PHPStan/Levels/data/methodCalls-6.json +++ b/tests/PHPStan/Levels/data/methodCalls-6.json @@ -1,71 +1,71 @@ [ { - "message": "Method Levels\\MethodCalls\\Foo::doFoo() has no return typehint specified.", + "message": "Method Levels\\MethodCalls\\Foo::doFoo() has no return type specified.", "line": 8, "ignorable": true }, { - "message": "Method Levels\\MethodCalls\\Bar::doBar() has no return typehint specified.", + "message": "Method Levels\\MethodCalls\\Bar::doBar() has no return type specified.", "line": 23, "ignorable": true }, { - "message": "Method Levels\\MethodCalls\\Baz::doBaz() has no return typehint specified.", + "message": "Method Levels\\MethodCalls\\Baz::doBaz() has no return type specified.", "line": 45, "ignorable": true }, { - "message": "Method Levels\\MethodCalls\\ClassWithMagicMethod::doFoo() has no return typehint specified.", + "message": "Method Levels\\MethodCalls\\ClassWithMagicMethod::doFoo() has no return type specified.", "line": 70, "ignorable": true }, { - "message": "Method Levels\\MethodCalls\\ClassWithMagicMethod::__call() has no return typehint specified.", + "message": "Method Levels\\MethodCalls\\ClassWithMagicMethod::__call() has no return type specified.", "line": 79, "ignorable": true }, { - "message": "Method Levels\\MethodCalls\\AnotherClassWithMagicMethod::doFoo() has no return typehint specified.", + "message": "Method Levels\\MethodCalls\\AnotherClassWithMagicMethod::doFoo() has no return type specified.", "line": 89, "ignorable": true }, { - "message": "Method Levels\\MethodCalls\\AnotherClassWithMagicMethod::__callStatic() has no return typehint specified.", + "message": "Method Levels\\MethodCalls\\AnotherClassWithMagicMethod::__callStatic() has no return type specified.", "line": 98, "ignorable": true }, { - "message": "Method Levels\\MethodCalls\\Ipsum::doLorem() has no return typehint specified.", + "message": "Method Levels\\MethodCalls\\Ipsum::doLorem() has no return type specified.", "line": 158, "ignorable": true }, { - "message": "Method Levels\\MethodCalls\\FooException::commonMethod() has no return typehint specified.", + "message": "Method Levels\\MethodCalls\\FooException::commonMethod() has no return type specified.", "line": 182, "ignorable": true }, { - "message": "Method Levels\\MethodCalls\\FooException::doFoo() has no return typehint specified.", + "message": "Method Levels\\MethodCalls\\FooException::doFoo() has no return type specified.", "line": 187, "ignorable": true }, { - "message": "Method Levels\\MethodCalls\\BarException::commonMethod() has no return typehint specified.", + "message": "Method Levels\\MethodCalls\\BarException::commonMethod() has no return type specified.", "line": 197, "ignorable": true }, { - "message": "Method Levels\\MethodCalls\\BarException::doBar() has no return typehint specified.", + "message": "Method Levels\\MethodCalls\\BarException::doBar() has no return type specified.", "line": 202, "ignorable": true }, { - "message": "Method Levels\\MethodCalls\\TestExceptions::doFoo() has no return typehint specified.", + "message": "Method Levels\\MethodCalls\\TestExceptions::doFoo() has no return type specified.", "line": 212, "ignorable": true }, { - "message": "Method Levels\\MethodCalls\\ExtraArguments::doFoo() has no return typehint specified.", + "message": "Method Levels\\MethodCalls\\ExtraArguments::doFoo() has no return type specified.", "line": 234, "ignorable": true } diff --git a/tests/PHPStan/Levels/data/object-6.json b/tests/PHPStan/Levels/data/object-6.json index f98cc39107..3110d6eed2 100644 --- a/tests/PHPStan/Levels/data/object-6.json +++ b/tests/PHPStan/Levels/data/object-6.json @@ -1,16 +1,16 @@ [ { - "message": "Method Levels\\ObjectTests\\Foo::doFoo() has no return typehint specified.", + "message": "Method Levels\\ObjectTests\\Foo::doFoo() has no return type specified.", "line": 11, "ignorable": true }, { - "message": "Method Levels\\ObjectTests\\Foo::doBar() has no return typehint specified.", + "message": "Method Levels\\ObjectTests\\Foo::doBar() has no return type specified.", "line": 23, "ignorable": true }, { - "message": "Method Levels\\ObjectTests\\Foo::doBaz() has no return typehint specified.", + "message": "Method Levels\\ObjectTests\\Foo::doBaz() has no return type specified.", "line": 35, "ignorable": true } diff --git a/tests/PHPStan/Levels/data/propertyAccesses-6.json b/tests/PHPStan/Levels/data/propertyAccesses-6.json index f0d47cd16e..edeb6d1b42 100644 --- a/tests/PHPStan/Levels/data/propertyAccesses-6.json +++ b/tests/PHPStan/Levels/data/propertyAccesses-6.json @@ -1,41 +1,41 @@ [ { - "message": "Method Levels\\PropertyAccesses\\Foo::doFoo() has no return typehint specified.", + "message": "Method Levels\\PropertyAccesses\\Foo::doFoo() has no return type specified.", "line": 11, "ignorable": true }, { - "message": "Method Levels\\PropertyAccesses\\Bar::doBar() has no return typehint specified.", + "message": "Method Levels\\PropertyAccesses\\Bar::doBar() has no return type specified.", "line": 29, "ignorable": true }, { - "message": "Method Levels\\PropertyAccesses\\Baz::doBaz() has no return typehint specified.", + "message": "Method Levels\\PropertyAccesses\\Baz::doBaz() has no return type specified.", "line": 50, "ignorable": true }, { - "message": "Method Levels\\PropertyAccesses\\ClassWithMagicMethod::doFoo() has no return typehint specified.", + "message": "Method Levels\\PropertyAccesses\\ClassWithMagicMethod::doFoo() has no return type specified.", "line": 74, "ignorable": true }, { - "message": "Method Levels\\PropertyAccesses\\ClassWithMagicMethod::__set() has no return typehint specified.", + "message": "Method Levels\\PropertyAccesses\\ClassWithMagicMethod::__set() has no return type specified.", "line": 83, "ignorable": true }, { - "message": "Method Levels\\PropertyAccesses\\AnotherClassWithMagicMethod::doFoo() has no return typehint specified.", + "message": "Method Levels\\PropertyAccesses\\AnotherClassWithMagicMethod::doFoo() has no return type specified.", "line": 93, "ignorable": true }, { - "message": "Method Levels\\PropertyAccesses\\AnotherClassWithMagicMethod::__get() has no return typehint specified.", + "message": "Method Levels\\PropertyAccesses\\AnotherClassWithMagicMethod::__get() has no return type specified.", "line": 98, "ignorable": true }, { - "message": "Method Levels\\PropertyAccesses\\Ipsum::doBaz() has no return typehint specified.", + "message": "Method Levels\\PropertyAccesses\\Ipsum::doBaz() has no return type specified.", "line": 158, "ignorable": true } diff --git a/tests/PHPStan/Levels/data/stubValidator-0.json b/tests/PHPStan/Levels/data/stubValidator-0.json index 0625085298..f56b9475af 100644 --- a/tests/PHPStan/Levels/data/stubValidator-0.json +++ b/tests/PHPStan/Levels/data/stubValidator-0.json @@ -1,6 +1,6 @@ [ { - "message": "Method StubValidator\\Foo::doFoo() has no return typehint specified.", + "message": "Method StubValidator\\Foo::doFoo() has no return type specified.", "line": 15, "ignorable": true }, @@ -10,7 +10,7 @@ "ignorable": true }, { - "message": "Function StubValidator\\someFunction() has no return typehint specified.", + "message": "Function StubValidator\\someFunction() has no return type specified.", "line": 22, "ignorable": true }, @@ -20,12 +20,12 @@ "ignorable": true }, { - "message": "Method class@anonymous/stubValidator/stubs.php:27::doFoo() has no return typehint specified.", + "message": "Method class@anonymous/stubValidator/stubs.php:27::doFoo() has no return type specified.", "line": 30, "ignorable": true }, { - "message": "Parameter $foo of method class@anonymous/stubValidator/stubs.php:27::doFoo() has invalid typehint type StubValidator\\Foooooooo.", + "message": "Parameter $foo of method class@anonymous/stubValidator/stubs.php:27::doFoo() has invalid type StubValidator\\Foooooooo.", "line": 30, "ignorable": true } diff --git a/tests/PHPStan/Levels/data/stubs-functions-6.json b/tests/PHPStan/Levels/data/stubs-functions-6.json index 2b701606cf..68b139c505 100644 --- a/tests/PHPStan/Levels/data/stubs-functions-6.json +++ b/tests/PHPStan/Levels/data/stubs-functions-6.json @@ -1,11 +1,11 @@ [ { - "message": "Function StubsIntegrationTest\\foo() has no return typehint specified.", + "message": "Function StubsIntegrationTest\\foo() has no return type specified.", "line": 5, "ignorable": true }, { - "message": "Function StubsIntegrationTest\\foo() has parameter $i with no typehint specified.", + "message": "Function StubsIntegrationTest\\foo() has parameter $i with no type specified.", "line": 5, "ignorable": true } diff --git a/tests/PHPStan/Levels/data/stubs-methods-6.json b/tests/PHPStan/Levels/data/stubs-methods-6.json index 9b15e1a786..d626c962a0 100644 --- a/tests/PHPStan/Levels/data/stubs-methods-6.json +++ b/tests/PHPStan/Levels/data/stubs-methods-6.json @@ -1,26 +1,26 @@ [ { - "message": "Method StubsIntegrationTest\\Foo::doFoo() has no return typehint specified.", + "message": "Method StubsIntegrationTest\\Foo::doFoo() has no return type specified.", "line": 8, "ignorable": true }, { - "message": "Method StubsIntegrationTest\\Foo::doFoo() has parameter $i with no typehint specified.", + "message": "Method StubsIntegrationTest\\Foo::doFoo() has parameter $i with no type specified.", "line": 8, "ignorable": true }, { - "message": "Method StubsIntegrationTest\\InterfaceWithStubPhpDoc2::doFoo() has no return typehint specified.", + "message": "Method StubsIntegrationTest\\InterfaceWithStubPhpDoc2::doFoo() has no return type specified.", "line": 151, "ignorable": true }, { - "message": "Method StubsIntegrationTest\\YetAnotherFoo::doFoo() has no return typehint specified.", + "message": "Method StubsIntegrationTest\\YetAnotherFoo::doFoo() has no return type specified.", "line": 197, "ignorable": true }, { - "message": "Method StubsIntegrationTest\\YetAnotherFoo::doFoo() has parameter $j with no typehint specified.", + "message": "Method StubsIntegrationTest\\YetAnotherFoo::doFoo() has parameter $j with no type specified.", "line": 197, "ignorable": true } diff --git a/tests/PHPStan/Levels/data/throwValues-6.json b/tests/PHPStan/Levels/data/throwValues-6.json index ae6f775eef..ccbd412f3b 100644 --- a/tests/PHPStan/Levels/data/throwValues-6.json +++ b/tests/PHPStan/Levels/data/throwValues-6.json @@ -1,6 +1,6 @@ [ { - "message": "Method Levels\\ThrowValues\\Foo::doFoo() has no return typehint specified.", + "message": "Method Levels\\ThrowValues\\Foo::doFoo() has no return type specified.", "line": 30, "ignorable": true } diff --git a/tests/PHPStan/Levels/data/typehints-0.json b/tests/PHPStan/Levels/data/typehints-0.json index 07084c7e86..46421c021f 100644 --- a/tests/PHPStan/Levels/data/typehints-0.json +++ b/tests/PHPStan/Levels/data/typehints-0.json @@ -1,11 +1,11 @@ [ { - "message": "Parameter $lorem of method Levels\\Typehints\\Foo::doFoo() has invalid typehint type Levels\\Typehints\\Lorem.", + "message": "Method Levels\\Typehints\\Foo::doFoo() has invalid return type Levels\\Typehints\\Ipsum.", "line": 8, "ignorable": true }, { - "message": "Return typehint of method Levels\\Typehints\\Foo::doFoo() has invalid type Levels\\Typehints\\Ipsum.", + "message": "Parameter $lorem of method Levels\\Typehints\\Foo::doFoo() has invalid type Levels\\Typehints\\Lorem.", "line": 8, "ignorable": true }, diff --git a/tests/PHPStan/Levels/data/typehints-2.json b/tests/PHPStan/Levels/data/typehints-2.json index adbf371a07..1f1a947306 100644 --- a/tests/PHPStan/Levels/data/typehints-2.json +++ b/tests/PHPStan/Levels/data/typehints-2.json @@ -1,11 +1,11 @@ [ { - "message": "Parameter $lorem of method Levels\\Typehints\\Foo::doBar() has invalid typehint type Levels\\Typehints\\Lorem.", + "message": "Method Levels\\Typehints\\Foo::doBar() has invalid return type Levels\\Typehints\\Ipsum.", "line": 17, "ignorable": true }, { - "message": "Return typehint of method Levels\\Typehints\\Foo::doBar() has invalid type Levels\\Typehints\\Ipsum.", + "message": "Parameter $lorem of method Levels\\Typehints\\Foo::doBar() has invalid type Levels\\Typehints\\Lorem.", "line": 17, "ignorable": true } diff --git a/tests/PHPStan/Levels/data/unreachable-6-alwaysTrue.json b/tests/PHPStan/Levels/data/unreachable-6-alwaysTrue.json index 05edefa8b4..b285fc696d 100644 --- a/tests/PHPStan/Levels/data/unreachable-6-alwaysTrue.json +++ b/tests/PHPStan/Levels/data/unreachable-6-alwaysTrue.json @@ -1,61 +1,61 @@ [ { - "message": "Method Levels\\Unreachable\\Foo::doStrictComparison() has no return typehint specified.", + "message": "Method Levels\\Unreachable\\Foo::doStrictComparison() has no return type specified.", "line": 8, "ignorable": true }, { - "message": "Method Levels\\Unreachable\\Foo::doInstanceOf() has no return typehint specified.", + "message": "Method Levels\\Unreachable\\Foo::doInstanceOf() has no return type specified.", "line": 18, "ignorable": true }, { - "message": "Method Levels\\Unreachable\\Foo::doTypeSpecifyingFunction() has no return typehint specified.", + "message": "Method Levels\\Unreachable\\Foo::doTypeSpecifyingFunction() has no return type specified.", "line": 27, "ignorable": true }, { - "message": "Method Levels\\Unreachable\\Foo::doOtherFunction() has no return typehint specified.", + "message": "Method Levels\\Unreachable\\Foo::doOtherFunction() has no return type specified.", "line": 36, "ignorable": true }, { - "message": "Method Levels\\Unreachable\\Foo::doOtherValue() has no return typehint specified.", + "message": "Method Levels\\Unreachable\\Foo::doOtherValue() has no return type specified.", "line": 45, "ignorable": true }, { - "message": "Method Levels\\Unreachable\\Foo::doBooleanAnd() has no return typehint specified.", + "message": "Method Levels\\Unreachable\\Foo::doBooleanAnd() has no return type specified.", "line": 54, "ignorable": true }, { - "message": "Method Levels\\Unreachable\\Bar::doStrictComparison() has no return typehint specified.", + "message": "Method Levels\\Unreachable\\Bar::doStrictComparison() has no return type specified.", "line": 71, "ignorable": true }, { - "message": "Method Levels\\Unreachable\\Bar::doInstanceOf() has no return typehint specified.", + "message": "Method Levels\\Unreachable\\Bar::doInstanceOf() has no return type specified.", "line": 77, "ignorable": true }, { - "message": "Method Levels\\Unreachable\\Bar::doTypeSpecifyingFunction() has no return typehint specified.", + "message": "Method Levels\\Unreachable\\Bar::doTypeSpecifyingFunction() has no return type specified.", "line": 82, "ignorable": true }, { - "message": "Method Levels\\Unreachable\\Bar::doOtherFunction() has no return typehint specified.", + "message": "Method Levels\\Unreachable\\Bar::doOtherFunction() has no return type specified.", "line": 87, "ignorable": true }, { - "message": "Method Levels\\Unreachable\\Bar::doOtherValue() has no return typehint specified.", + "message": "Method Levels\\Unreachable\\Bar::doOtherValue() has no return type specified.", "line": 92, "ignorable": true }, { - "message": "Method Levels\\Unreachable\\Bar::doBooleanAnd() has no return typehint specified.", + "message": "Method Levels\\Unreachable\\Bar::doBooleanAnd() has no return type specified.", "line": 97, "ignorable": true } diff --git a/tests/PHPStan/Levels/data/unreachable-6.json b/tests/PHPStan/Levels/data/unreachable-6.json index 05edefa8b4..b285fc696d 100644 --- a/tests/PHPStan/Levels/data/unreachable-6.json +++ b/tests/PHPStan/Levels/data/unreachable-6.json @@ -1,61 +1,61 @@ [ { - "message": "Method Levels\\Unreachable\\Foo::doStrictComparison() has no return typehint specified.", + "message": "Method Levels\\Unreachable\\Foo::doStrictComparison() has no return type specified.", "line": 8, "ignorable": true }, { - "message": "Method Levels\\Unreachable\\Foo::doInstanceOf() has no return typehint specified.", + "message": "Method Levels\\Unreachable\\Foo::doInstanceOf() has no return type specified.", "line": 18, "ignorable": true }, { - "message": "Method Levels\\Unreachable\\Foo::doTypeSpecifyingFunction() has no return typehint specified.", + "message": "Method Levels\\Unreachable\\Foo::doTypeSpecifyingFunction() has no return type specified.", "line": 27, "ignorable": true }, { - "message": "Method Levels\\Unreachable\\Foo::doOtherFunction() has no return typehint specified.", + "message": "Method Levels\\Unreachable\\Foo::doOtherFunction() has no return type specified.", "line": 36, "ignorable": true }, { - "message": "Method Levels\\Unreachable\\Foo::doOtherValue() has no return typehint specified.", + "message": "Method Levels\\Unreachable\\Foo::doOtherValue() has no return type specified.", "line": 45, "ignorable": true }, { - "message": "Method Levels\\Unreachable\\Foo::doBooleanAnd() has no return typehint specified.", + "message": "Method Levels\\Unreachable\\Foo::doBooleanAnd() has no return type specified.", "line": 54, "ignorable": true }, { - "message": "Method Levels\\Unreachable\\Bar::doStrictComparison() has no return typehint specified.", + "message": "Method Levels\\Unreachable\\Bar::doStrictComparison() has no return type specified.", "line": 71, "ignorable": true }, { - "message": "Method Levels\\Unreachable\\Bar::doInstanceOf() has no return typehint specified.", + "message": "Method Levels\\Unreachable\\Bar::doInstanceOf() has no return type specified.", "line": 77, "ignorable": true }, { - "message": "Method Levels\\Unreachable\\Bar::doTypeSpecifyingFunction() has no return typehint specified.", + "message": "Method Levels\\Unreachable\\Bar::doTypeSpecifyingFunction() has no return type specified.", "line": 82, "ignorable": true }, { - "message": "Method Levels\\Unreachable\\Bar::doOtherFunction() has no return typehint specified.", + "message": "Method Levels\\Unreachable\\Bar::doOtherFunction() has no return type specified.", "line": 87, "ignorable": true }, { - "message": "Method Levels\\Unreachable\\Bar::doOtherValue() has no return typehint specified.", + "message": "Method Levels\\Unreachable\\Bar::doOtherValue() has no return type specified.", "line": 92, "ignorable": true }, { - "message": "Method Levels\\Unreachable\\Bar::doBooleanAnd() has no return typehint specified.", + "message": "Method Levels\\Unreachable\\Bar::doBooleanAnd() has no return type specified.", "line": 97, "ignorable": true } diff --git a/tests/PHPStan/Parallel/ParallelAnalyserIntegrationTest.php b/tests/PHPStan/Parallel/ParallelAnalyserIntegrationTest.php index d438f44f48..6ee2b4746e 100644 --- a/tests/PHPStan/Parallel/ParallelAnalyserIntegrationTest.php +++ b/tests/PHPStan/Parallel/ParallelAnalyserIntegrationTest.php @@ -58,7 +58,7 @@ public function testRun(string $command): void 'errors' => 1, 'messages' => [ [ - 'message' => 'Method ParallelAnalyserIntegrationTest\\Bar::doFoo() has no return typehint specified.', + 'message' => 'Method ParallelAnalyserIntegrationTest\\Bar::doFoo() has no return type specified.', 'line' => 8, 'ignorable' => true, ], @@ -68,7 +68,7 @@ public function testRun(string $command): void 'errors' => 2, 'messages' => [ [ - 'message' => 'Method ParallelAnalyserIntegrationTest\\Foo::doFoo() has no return typehint specified.', + 'message' => 'Method ParallelAnalyserIntegrationTest\\Foo::doFoo() has no return type specified.', 'line' => 8, 'ignorable' => true, ], diff --git a/tests/PHPStan/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRuleTest.php b/tests/PHPStan/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRuleTest.php index f93f2c077b..f3ad46c3ff 100644 --- a/tests/PHPStan/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRuleTest.php @@ -28,11 +28,11 @@ public function testRule(): void } $this->analyse([__DIR__ . '/data/arrow-function-typehints.php'], [ [ - 'Parameter $bar of anonymous function has invalid typehint type ArrowFunctionExistingClassesInTypehints\Bar.', + 'Parameter $bar of anonymous function has invalid type ArrowFunctionExistingClassesInTypehints\Bar.', 10, ], [ - 'Return typehint of anonymous function has invalid type ArrowFunctionExistingClassesInTypehints\Baz.', + 'Anonymous function has invalid return type ArrowFunctionExistingClassesInTypehints\Baz.', 10, ], ]); diff --git a/tests/PHPStan/Rules/Functions/ExistingClassesInClosureTypehintsRuleTest.php b/tests/PHPStan/Rules/Functions/ExistingClassesInClosureTypehintsRuleTest.php index d9364af66b..4d5d5a511c 100644 --- a/tests/PHPStan/Rules/Functions/ExistingClassesInClosureTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ExistingClassesInClosureTypehintsRuleTest.php @@ -25,11 +25,11 @@ public function testExistingClassInTypehint(): void { $this->analyse([__DIR__ . '/data/closure-typehints.php'], [ [ - 'Return typehint of anonymous function has invalid type TestClosureFunctionTypehints\NonexistentClass.', + 'Anonymous function has invalid return type TestClosureFunctionTypehints\NonexistentClass.', 10, ], [ - 'Parameter $bar of anonymous function has invalid typehint type TestClosureFunctionTypehints\BarFunctionTypehints.', + 'Parameter $bar of anonymous function has invalid type TestClosureFunctionTypehints\BarFunctionTypehints.', 15, ], [ @@ -41,11 +41,11 @@ public function testExistingClassInTypehint(): void 30, ], [ - 'Parameter $trait of anonymous function has invalid typehint type TestClosureFunctionTypehints\SomeTrait.', + 'Parameter $trait of anonymous function has invalid type TestClosureFunctionTypehints\SomeTrait.', 45, ], [ - 'Return typehint of anonymous function has invalid type TestClosureFunctionTypehints\SomeTrait.', + 'Anonymous function has invalid return type TestClosureFunctionTypehints\SomeTrait.', 50, ], ]); @@ -55,11 +55,11 @@ public function testValidTypehintPhp71(): void { $this->analyse([__DIR__ . '/data/closure-7.1-typehints.php'], [ [ - 'Parameter $bar of anonymous function has invalid typehint type TestClosureFunctionTypehintsPhp71\NonexistentClass.', + 'Parameter $bar of anonymous function has invalid type TestClosureFunctionTypehintsPhp71\NonexistentClass.', 35, ], [ - 'Return typehint of anonymous function has invalid type TestClosureFunctionTypehintsPhp71\NonexistentClass.', + 'Anonymous function has invalid return type TestClosureFunctionTypehintsPhp71\NonexistentClass.', 35, ], ]); @@ -80,7 +80,7 @@ public function testVoidParameterTypehint(): void } $this->analyse([__DIR__ . '/data/void-parameter-typehint.php'], [ [ - 'Parameter $param of anonymous function has invalid typehint type void.', + 'Parameter $param of anonymous function has invalid type void.', 5, ], ]); diff --git a/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php b/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php index 9200981e8c..b28263881f 100644 --- a/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php @@ -27,15 +27,15 @@ public function testExistingClassInTypehint(): void require_once __DIR__ . '/data/typehints.php'; $this->analyse([__DIR__ . '/data/typehints.php'], [ [ - 'Return typehint of function TestFunctionTypehints\foo() has invalid type TestFunctionTypehints\NonexistentClass.', + 'Function TestFunctionTypehints\foo() has invalid return type TestFunctionTypehints\NonexistentClass.', 15, ], [ - 'Parameter $bar of function TestFunctionTypehints\bar() has invalid typehint type TestFunctionTypehints\BarFunctionTypehints.', + 'Parameter $bar of function TestFunctionTypehints\bar() has invalid type TestFunctionTypehints\BarFunctionTypehints.', 20, ], [ - 'Return typehint of function TestFunctionTypehints\returnParent() has invalid type TestFunctionTypehints\parent.', + 'Function TestFunctionTypehints\returnParent() has invalid return type TestFunctionTypehints\parent.', 33, ], [ @@ -63,27 +63,27 @@ public function testExistingClassInTypehint(): void 56, ], [ - 'Parameter $trait of function TestFunctionTypehints\referencesTraitsInNative() has invalid typehint type TestFunctionTypehints\SomeTrait.', + 'Parameter $trait of function TestFunctionTypehints\referencesTraitsInNative() has invalid type TestFunctionTypehints\SomeTrait.', 61, ], [ - 'Return typehint of function TestFunctionTypehints\referencesTraitsInNative() has invalid type TestFunctionTypehints\SomeTrait.', + 'Function TestFunctionTypehints\referencesTraitsInNative() has invalid return type TestFunctionTypehints\SomeTrait.', 61, ], [ - 'Parameter $trait of function TestFunctionTypehints\referencesTraitsInPhpDoc() has invalid typehint type TestFunctionTypehints\SomeTrait.', + 'Parameter $trait of function TestFunctionTypehints\referencesTraitsInPhpDoc() has invalid type TestFunctionTypehints\SomeTrait.', 70, ], [ - 'Return typehint of function TestFunctionTypehints\referencesTraitsInPhpDoc() has invalid type TestFunctionTypehints\SomeTrait.', + 'Function TestFunctionTypehints\referencesTraitsInPhpDoc() has invalid return type TestFunctionTypehints\SomeTrait.', 70, ], [ - 'Parameter $string of function TestFunctionTypehints\genericClassString() has invalid typehint type TestFunctionTypehints\SomeNonexistentClass.', + 'Parameter $string of function TestFunctionTypehints\genericClassString() has invalid type TestFunctionTypehints\SomeNonexistentClass.', 78, ], [ - 'Parameter $string of function TestFunctionTypehints\genericTemplateClassString() has invalid typehint type TestFunctionTypehints\SomeNonexistentClass.', + 'Parameter $string of function TestFunctionTypehints\genericTemplateClassString() has invalid type TestFunctionTypehints\SomeNonexistentClass.', 87, ], [ @@ -98,15 +98,15 @@ public function testWithoutNamespace(): void require_once __DIR__ . '/data/typehintsWithoutNamespace.php'; $this->analyse([__DIR__ . '/data/typehintsWithoutNamespace.php'], [ [ - 'Return typehint of function fooWithoutNamespace() has invalid type NonexistentClass.', + 'Function fooWithoutNamespace() has invalid return type NonexistentClass.', 13, ], [ - 'Parameter $bar of function barWithoutNamespace() has invalid typehint type BarFunctionTypehints.', + 'Parameter $bar of function barWithoutNamespace() has invalid type BarFunctionTypehints.', 18, ], [ - 'Return typehint of function returnParentWithoutNamespace() has invalid type parent.', + 'Function returnParentWithoutNamespace() has invalid return type parent.', 31, ], [ @@ -134,19 +134,19 @@ public function testWithoutNamespace(): void 54, ], [ - 'Parameter $trait of function referencesTraitsInNativeWithoutNamespace() has invalid typehint type SomeTraitWithoutNamespace.', + 'Parameter $trait of function referencesTraitsInNativeWithoutNamespace() has invalid type SomeTraitWithoutNamespace.', 59, ], [ - 'Return typehint of function referencesTraitsInNativeWithoutNamespace() has invalid type SomeTraitWithoutNamespace.', + 'Function referencesTraitsInNativeWithoutNamespace() has invalid return type SomeTraitWithoutNamespace.', 59, ], [ - 'Parameter $trait of function referencesTraitsInPhpDocWithoutNamespace() has invalid typehint type SomeTraitWithoutNamespace.', + 'Parameter $trait of function referencesTraitsInPhpDocWithoutNamespace() has invalid type SomeTraitWithoutNamespace.', 68, ], [ - 'Return typehint of function referencesTraitsInPhpDocWithoutNamespace() has invalid type SomeTraitWithoutNamespace.', + 'Function referencesTraitsInPhpDocWithoutNamespace() has invalid return type SomeTraitWithoutNamespace.', 68, ], ]); @@ -159,7 +159,7 @@ public function testVoidParameterTypehint(): void } $this->analyse([__DIR__ . '/data/void-parameter-typehint.php'], [ [ - 'Parameter $param of function VoidParameterTypehint\doFoo() has invalid typehint type void.', + 'Parameter $param of function VoidParameterTypehint\doFoo() has invalid type void.', 9, ], ]); diff --git a/tests/PHPStan/Rules/Functions/MissingFunctionParameterTypehintRuleTest.php b/tests/PHPStan/Rules/Functions/MissingFunctionParameterTypehintRuleTest.php index 93bc8df9bd..5e17b2b675 100644 --- a/tests/PHPStan/Rules/Functions/MissingFunctionParameterTypehintRuleTest.php +++ b/tests/PHPStan/Rules/Functions/MissingFunctionParameterTypehintRuleTest.php @@ -21,15 +21,15 @@ public function testRule(): void require_once __DIR__ . '/data/missing-function-parameter-typehint.php'; $this->analyse([__DIR__ . '/data/missing-function-parameter-typehint.php'], [ [ - 'Function globalFunction() has parameter $b with no typehint specified.', + 'Function globalFunction() has parameter $b with no type specified.', 9, ], [ - 'Function globalFunction() has parameter $c with no typehint specified.', + 'Function globalFunction() has parameter $c with no type specified.', 9, ], [ - 'Function MissingFunctionParameterTypehint\namespacedFunction() has parameter $d with no typehint specified.', + 'Function MissingFunctionParameterTypehint\namespacedFunction() has parameter $d with no type specified.', 24, ], [ diff --git a/tests/PHPStan/Rules/Functions/MissingFunctionReturnTypehintRuleTest.php b/tests/PHPStan/Rules/Functions/MissingFunctionReturnTypehintRuleTest.php index 006902082e..eb26d2d90e 100644 --- a/tests/PHPStan/Rules/Functions/MissingFunctionReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/Functions/MissingFunctionReturnTypehintRuleTest.php @@ -21,11 +21,11 @@ public function testRule(): void require_once __DIR__ . '/data/missing-function-return-typehint.php'; $this->analyse([__DIR__ . '/data/missing-function-return-typehint.php'], [ [ - 'Function globalFunction1() has no return typehint specified.', + 'Function globalFunction1() has no return type specified.', 5, ], [ - 'Function MissingFunctionReturnTypehint\namespacedFunction1() has no return typehint specified.', + 'Function MissingFunctionReturnTypehint\namespacedFunction1() has no return type specified.', 30, ], [ diff --git a/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php b/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php index db1fd829c3..ba82c2b2aa 100644 --- a/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php @@ -29,51 +29,51 @@ public function testExistingClassInTypehint(): void } $this->analyse([__DIR__ . '/data/typehints.php'], [ [ - 'Return typehint of method TestMethodTypehints\FooMethodTypehints::foo() has invalid type TestMethodTypehints\NonexistentClass.', + 'Method TestMethodTypehints\FooMethodTypehints::foo() has invalid return type TestMethodTypehints\NonexistentClass.', 8, ], [ - 'Parameter $bar of method TestMethodTypehints\FooMethodTypehints::bar() has invalid typehint type TestMethodTypehints\BarMethodTypehints.', + 'Parameter $bar of method TestMethodTypehints\FooMethodTypehints::bar() has invalid type TestMethodTypehints\BarMethodTypehints.', 13, ], [ - 'Parameter $bars of method TestMethodTypehints\FooMethodTypehints::lorem() has invalid typehint type TestMethodTypehints\BarMethodTypehints.', + 'Parameter $bars of method TestMethodTypehints\FooMethodTypehints::lorem() has invalid type TestMethodTypehints\BarMethodTypehints.', 28, ], [ - 'Return typehint of method TestMethodTypehints\FooMethodTypehints::lorem() has invalid type TestMethodTypehints\BazMethodTypehints.', + 'Method TestMethodTypehints\FooMethodTypehints::lorem() has invalid return type TestMethodTypehints\BazMethodTypehints.', 28, ], [ - 'Parameter $bars of method TestMethodTypehints\FooMethodTypehints::ipsum() has invalid typehint type TestMethodTypehints\BarMethodTypehints.', + 'Parameter $bars of method TestMethodTypehints\FooMethodTypehints::ipsum() has invalid type TestMethodTypehints\BarMethodTypehints.', 38, ], [ - 'Return typehint of method TestMethodTypehints\FooMethodTypehints::ipsum() has invalid type TestMethodTypehints\BazMethodTypehints.', + 'Method TestMethodTypehints\FooMethodTypehints::ipsum() has invalid return type TestMethodTypehints\BazMethodTypehints.', 38, ], [ - 'Parameter $bars of method TestMethodTypehints\FooMethodTypehints::dolor() has invalid typehint type TestMethodTypehints\BarMethodTypehints.', + 'Parameter $bars of method TestMethodTypehints\FooMethodTypehints::dolor() has invalid type TestMethodTypehints\BarMethodTypehints.', 48, ], [ - 'Return typehint of method TestMethodTypehints\FooMethodTypehints::dolor() has invalid type TestMethodTypehints\BazMethodTypehints.', + 'Method TestMethodTypehints\FooMethodTypehints::dolor() has invalid return type TestMethodTypehints\BazMethodTypehints.', 48, ], [ - 'Parameter $parent of method TestMethodTypehints\FooMethodTypehints::parentWithoutParent() has invalid typehint type parent.', + 'Parameter $parent of method TestMethodTypehints\FooMethodTypehints::parentWithoutParent() has invalid type parent.', 53, ], [ - 'Return typehint of method TestMethodTypehints\FooMethodTypehints::parentWithoutParent() has invalid type parent.', + 'Method TestMethodTypehints\FooMethodTypehints::parentWithoutParent() has invalid return type parent.', 53, ], [ - 'Parameter $parent of method TestMethodTypehints\FooMethodTypehints::phpDocParentWithoutParent() has invalid typehint type parent.', + 'Parameter $parent of method TestMethodTypehints\FooMethodTypehints::phpDocParentWithoutParent() has invalid type parent.', 62, ], [ - 'Return typehint of method TestMethodTypehints\FooMethodTypehints::phpDocParentWithoutParent() has invalid type parent.', + 'Method TestMethodTypehints\FooMethodTypehints::phpDocParentWithoutParent() has invalid return type parent.', 62, ], [ @@ -109,15 +109,15 @@ public function testExistingClassInTypehint(): void 94, ], [ - 'Parameter $array of method TestMethodTypehints\FooMethodTypehints::unknownTypesInArrays() has invalid typehint type TestMethodTypehints\AnotherNonexistentClass.', + 'Parameter $array of method TestMethodTypehints\FooMethodTypehints::unknownTypesInArrays() has invalid type TestMethodTypehints\AnotherNonexistentClass.', 102, ], [ - 'Parameter $cb of method TestMethodTypehints\CallableTypehints::doFoo() has invalid typehint type TestMethodTypehints\Bla.', + 'Parameter $cb of method TestMethodTypehints\CallableTypehints::doFoo() has invalid type TestMethodTypehints\Bla.', 113, ], [ - 'Parameter $cb of method TestMethodTypehints\CallableTypehints::doFoo() has invalid typehint type TestMethodTypehints\Ble.', + 'Parameter $cb of method TestMethodTypehints\CallableTypehints::doFoo() has invalid type TestMethodTypehints\Ble.', 113, ], [ @@ -131,11 +131,11 @@ public function testExistingClassInIterableTypehint(): void { $this->analyse([__DIR__ . '/data/typehints-iterable.php'], [ [ - 'Parameter $iterable of method TestMethodTypehints\IterableTypehints::doFoo() has invalid typehint type TestMethodTypehints\NonexistentClass.', + 'Parameter $iterable of method TestMethodTypehints\IterableTypehints::doFoo() has invalid type TestMethodTypehints\NonexistentClass.', 11, ], [ - 'Parameter $iterable of method TestMethodTypehints\IterableTypehints::doFoo() has invalid typehint type TestMethodTypehints\AnotherNonexistentClass.', + 'Parameter $iterable of method TestMethodTypehints\IterableTypehints::doFoo() has invalid type TestMethodTypehints\AnotherNonexistentClass.', 11, ], ]); @@ -148,7 +148,7 @@ public function testVoidParameterTypehint(): void } $this->analyse([__DIR__ . '/data/void-parameter-typehint.php'], [ [ - 'Parameter $param of method VoidParameterTypehintMethod\Foo::doFoo() has invalid typehint type void.', + 'Parameter $param of method VoidParameterTypehintMethod\Foo::doFoo() has invalid type void.', 8, ], ]); diff --git a/tests/PHPStan/Rules/Methods/MissingMethodParameterTypehintRuleTest.php b/tests/PHPStan/Rules/Methods/MissingMethodParameterTypehintRuleTest.php index b400fc75b0..e940975ddc 100644 --- a/tests/PHPStan/Rules/Methods/MissingMethodParameterTypehintRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MissingMethodParameterTypehintRuleTest.php @@ -23,23 +23,23 @@ public function testRule(): void { $errors = [ [ - 'Method MissingMethodParameterTypehint\FooInterface::getFoo() has parameter $p1 with no typehint specified.', + 'Method MissingMethodParameterTypehint\FooInterface::getFoo() has parameter $p1 with no type specified.', 8, ], [ - 'Method MissingMethodParameterTypehint\FooParent::getBar() has parameter $p2 with no typehint specified.', + 'Method MissingMethodParameterTypehint\FooParent::getBar() has parameter $p2 with no type specified.', 15, ], [ - 'Method MissingMethodParameterTypehint\Foo::getFoo() has parameter $p1 with no typehint specified.', + 'Method MissingMethodParameterTypehint\Foo::getFoo() has parameter $p1 with no type specified.', 25, ], [ - 'Method MissingMethodParameterTypehint\Foo::getBar() has parameter $p2 with no typehint specified.', + 'Method MissingMethodParameterTypehint\Foo::getBar() has parameter $p2 with no type specified.', 33, ], [ - 'Method MissingMethodParameterTypehint\Foo::getBaz() has parameter $p3 with no typehint specified.', + 'Method MissingMethodParameterTypehint\Foo::getBaz() has parameter $p3 with no type specified.', 42, ], [ diff --git a/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php b/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php index ac0e206740..ced521e97f 100644 --- a/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php @@ -20,19 +20,19 @@ public function testRule(): void { $this->analyse([__DIR__ . '/data/missing-method-return-typehint.php'], [ [ - 'Method MissingMethodReturnTypehint\FooInterface::getFoo() has no return typehint specified.', + 'Method MissingMethodReturnTypehint\FooInterface::getFoo() has no return type specified.', 8, ], [ - 'Method MissingMethodReturnTypehint\FooParent::getBar() has no return typehint specified.', + 'Method MissingMethodReturnTypehint\FooParent::getBar() has no return type specified.', 15, ], [ - 'Method MissingMethodReturnTypehint\Foo::getFoo() has no return typehint specified.', + 'Method MissingMethodReturnTypehint\Foo::getFoo() has no return type specified.', 25, ], [ - 'Method MissingMethodReturnTypehint\Foo::getBar() has no return typehint specified.', + 'Method MissingMethodReturnTypehint\Foo::getBar() has no return type specified.', 33, ], [ diff --git a/tests/PHPStan/Rules/Properties/MissingPropertyTypehintRuleTest.php b/tests/PHPStan/Rules/Properties/MissingPropertyTypehintRuleTest.php index 622295adab..46fbc40179 100644 --- a/tests/PHPStan/Rules/Properties/MissingPropertyTypehintRuleTest.php +++ b/tests/PHPStan/Rules/Properties/MissingPropertyTypehintRuleTest.php @@ -20,15 +20,15 @@ public function testRule(): void { $this->analyse([__DIR__ . '/data/missing-property-typehint.php'], [ [ - 'Property MissingPropertyTypehint\MyClass::$prop1 has no typehint specified.', + 'Property MissingPropertyTypehint\MyClass::$prop1 has no type specified.', 7, ], [ - 'Property MissingPropertyTypehint\MyClass::$prop2 has no typehint specified.', + 'Property MissingPropertyTypehint\MyClass::$prop2 has no type specified.', 9, ], [ - 'Property MissingPropertyTypehint\MyClass::$prop3 has no typehint specified.', + 'Property MissingPropertyTypehint\MyClass::$prop3 has no type specified.', 14, ], [ @@ -65,7 +65,7 @@ public function testPromotedProperties(): void } $this->analyse([__DIR__ . '/data/promoted-properties-missing-typehint.php'], [ [ - 'Property PromotedPropertiesMissingTypehint\Foo::$lorem has no typehint specified.', + 'Property PromotedPropertiesMissingTypehint\Foo::$lorem has no type specified.', 15, ], [ diff --git a/tests/PHPStan/Rules/TooWideTypehints/TooWideArrowFunctionReturnTypehintRuleTest.php b/tests/PHPStan/Rules/TooWideTypehints/TooWideArrowFunctionReturnTypehintRuleTest.php index 00b517040d..3e7935d1fe 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/TooWideArrowFunctionReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/TooWideTypehints/TooWideArrowFunctionReturnTypehintRuleTest.php @@ -23,7 +23,7 @@ public function testRule(): void } $this->analyse([__DIR__ . '/data/tooWideArrowFunctionReturnType.php'], [ [ - 'Anonymous function never returns null so it can be removed from the return typehint.', + 'Anonymous function never returns null so it can be removed from the return type.', 14, ], ]); diff --git a/tests/PHPStan/Rules/TooWideTypehints/TooWideClosureReturnTypehintRuleTest.php b/tests/PHPStan/Rules/TooWideTypehints/TooWideClosureReturnTypehintRuleTest.php index 13e17f6aa6..2febeeddd6 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/TooWideClosureReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/TooWideTypehints/TooWideClosureReturnTypehintRuleTest.php @@ -20,7 +20,7 @@ public function testRule(): void { $this->analyse([__DIR__ . '/data/tooWideClosureReturnType.php'], [ [ - 'Anonymous function never returns null so it can be removed from the return typehint.', + 'Anonymous function never returns null so it can be removed from the return type.', 20, ], ]); diff --git a/tests/PHPStan/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRuleTest.php b/tests/PHPStan/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRuleTest.php index dc6921b40e..0323dbe958 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRuleTest.php @@ -21,27 +21,27 @@ public function testRule(): void require_once __DIR__ . '/data/tooWideFunctionReturnType.php'; $this->analyse([__DIR__ . '/data/tooWideFunctionReturnType.php'], [ [ - 'Function TooWideFunctionReturnType\bar() never returns string so it can be removed from the return typehint.', + 'Function TooWideFunctionReturnType\bar() never returns string so it can be removed from the return type.', 11, ], [ - 'Function TooWideFunctionReturnType\baz() never returns null so it can be removed from the return typehint.', + 'Function TooWideFunctionReturnType\baz() never returns null so it can be removed from the return type.', 15, ], [ - 'Function TooWideFunctionReturnType\ipsum() never returns null so it can be removed from the return typehint.', + 'Function TooWideFunctionReturnType\ipsum() never returns null so it can be removed from the return type.', 27, ], [ - 'Function TooWideFunctionReturnType\dolor2() never returns null so it can be removed from the return typehint.', + 'Function TooWideFunctionReturnType\dolor2() never returns null so it can be removed from the return type.', 41, ], [ - 'Function TooWideFunctionReturnType\dolor4() never returns int so it can be removed from the return typehint.', + 'Function TooWideFunctionReturnType\dolor4() never returns int so it can be removed from the return type.', 59, ], [ - 'Function TooWideFunctionReturnType\dolor6() never returns null so it can be removed from the return typehint.', + 'Function TooWideFunctionReturnType\dolor6() never returns null so it can be removed from the return type.', 79, ], ]); diff --git a/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php b/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php index bc5e952219..7409bc6d9f 100644 --- a/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php @@ -20,27 +20,27 @@ public function testPrivate(): void { $this->analyse([__DIR__ . '/data/tooWideMethodReturnType-private.php'], [ [ - 'Method TooWideMethodReturnType\Foo::bar() never returns string so it can be removed from the return typehint.', + 'Method TooWideMethodReturnType\Foo::bar() never returns string so it can be removed from the return type.', 14, ], [ - 'Method TooWideMethodReturnType\Foo::baz() never returns null so it can be removed from the return typehint.', + 'Method TooWideMethodReturnType\Foo::baz() never returns null so it can be removed from the return type.', 18, ], [ - 'Method TooWideMethodReturnType\Foo::dolor() never returns null so it can be removed from the return typehint.', + 'Method TooWideMethodReturnType\Foo::dolor() never returns null so it can be removed from the return type.', 34, ], [ - 'Method TooWideMethodReturnType\Foo::dolor2() never returns null so it can be removed from the return typehint.', + 'Method TooWideMethodReturnType\Foo::dolor2() never returns null so it can be removed from the return type.', 48, ], [ - 'Method TooWideMethodReturnType\Foo::dolor4() never returns int so it can be removed from the return typehint.', + 'Method TooWideMethodReturnType\Foo::dolor4() never returns int so it can be removed from the return type.', 66, ], [ - 'Method TooWideMethodReturnType\Foo::dolor6() never returns null so it can be removed from the return typehint.', + 'Method TooWideMethodReturnType\Foo::dolor6() never returns null so it can be removed from the return type.', 86, ], ]); @@ -50,15 +50,15 @@ public function testPublicProtected(): void { $this->analyse([__DIR__ . '/data/tooWideMethodReturnType-public-protected.php'], [ [ - 'Method TooWideMethodReturnType\Bar::bar() never returns string so it can be removed from the return typehint.', + 'Method TooWideMethodReturnType\Bar::bar() never returns string so it can be removed from the return type.', 14, ], [ - 'Method TooWideMethodReturnType\Bar::baz() never returns null so it can be removed from the return typehint.', + 'Method TooWideMethodReturnType\Bar::baz() never returns null so it can be removed from the return type.', 18, ], [ - 'Method TooWideMethodReturnType\Bazz::lorem() never returns string so it can be removed from the return typehint.', + 'Method TooWideMethodReturnType\Bazz::lorem() never returns string so it can be removed from the return type.', 35, ], ]); @@ -68,11 +68,11 @@ public function testPublicProtectedWithInheritance(): void { $this->analyse([__DIR__ . '/data/tooWideMethodReturnType-public-protected-inheritance.php'], [ [ - 'Method TooWideMethodReturnType\Baz::baz() never returns null so it can be removed from the return typehint.', + 'Method TooWideMethodReturnType\Baz::baz() never returns null so it can be removed from the return type.', 27, ], [ - 'Method TooWideMethodReturnType\BarClass::doFoo() never returns null so it can be removed from the return typehint.', + 'Method TooWideMethodReturnType\BarClass::doFoo() never returns null so it can be removed from the return type.', 51, ], ]); @@ -82,7 +82,7 @@ public function testBug5095(): void { $this->analyse([__DIR__ . '/data/bug-5095.php'], [ [ - 'Method Bug5095\Parser::unaryOperatorFor() never returns \'not\' so it can be removed from the return typehint.', + 'Method Bug5095\Parser::unaryOperatorFor() never returns \'not\' so it can be removed from the return type.', 21, ], ]); From c5cb0e89df8bdad55c50b116f9cc299a963229bb Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 16 Sep 2021 17:37:10 +0200 Subject: [PATCH 0321/1284] Respect treatPhpDocAsCertain in IssetCheck --- conf/config.neon | 1 + src/Rules/IssetCheck.php | 15 ++- .../PHPStan/Rules/Variables/EmptyRuleTest.php | 17 ++- .../PHPStan/Rules/Variables/IssetRuleTest.php | 109 +++++++++++++++++- .../Rules/Variables/NullCoalesceRuleTest.php | 21 +++- tests/PHPStan/Rules/Variables/data/isset.php | 12 ++ 6 files changed, 168 insertions(+), 7 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index ce27918d3a..ee036abbd5 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -828,6 +828,7 @@ services: class: PHPStan\Rules\IssetCheck arguments: checkAdvancedIsset: %checkAdvancedIsset% + treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% - # checked as part of OverridingMethodRule diff --git a/src/Rules/IssetCheck.php b/src/Rules/IssetCheck.php index cb2dd02022..a1a1db0607 100644 --- a/src/Rules/IssetCheck.php +++ b/src/Rules/IssetCheck.php @@ -20,15 +20,19 @@ class IssetCheck private bool $checkAdvancedIsset; + private bool $treatPhpDocTypesAsCertain; + public function __construct( PropertyDescriptor $propertyDescriptor, PropertyReflectionFinder $propertyReflectionFinder, - bool $checkAdvancedIsset + bool $checkAdvancedIsset, + bool $treatPhpDocTypesAsCertain ) { $this->propertyDescriptor = $propertyDescriptor; $this->propertyReflectionFinder = $propertyReflectionFinder; $this->checkAdvancedIsset = $checkAdvancedIsset; + $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; } /** @@ -60,9 +64,12 @@ public function check(Expr $expr, Scope $scope, string $operatorDescription, cal return $error; } elseif ($expr instanceof Node\Expr\ArrayDimFetch && $expr->dim !== null) { - - $type = $scope->getType($expr->var); - $dimType = $scope->getType($expr->dim); + $type = $this->treatPhpDocTypesAsCertain + ? $scope->getType($expr->var) + : $scope->getNativeType($expr->var); + $dimType = $this->treatPhpDocTypesAsCertain + ? $scope->getType($expr->dim) + : $scope->getNativeType($expr->dim); $hasOffsetValue = $type->hasOffsetValueType($dimType); if (!$type->isOffsetAccessible()->yes()) { return $error ?? $this->checkUndefined($expr->var, $scope, $operatorDescription); diff --git a/tests/PHPStan/Rules/Variables/EmptyRuleTest.php b/tests/PHPStan/Rules/Variables/EmptyRuleTest.php index 95ca45d34b..5a300c8cda 100644 --- a/tests/PHPStan/Rules/Variables/EmptyRuleTest.php +++ b/tests/PHPStan/Rules/Variables/EmptyRuleTest.php @@ -13,13 +13,27 @@ class EmptyRuleTest extends RuleTestCase { + /** @var bool */ + private $treatPhpDocTypesAsCertain; + protected function getRule(): \PHPStan\Rules\Rule { - return new EmptyRule(new IssetCheck(new PropertyDescriptor(), new PropertyReflectionFinder(), true)); + return new EmptyRule(new IssetCheck( + new PropertyDescriptor(), + new PropertyReflectionFinder(), + true, + $this->treatPhpDocTypesAsCertain + )); + } + + protected function shouldTreatPhpDocTypesAsCertain(): bool + { + return $this->treatPhpDocTypesAsCertain; } public function testRule(): void { + $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/empty-rule.php'], [ [ 'Offset \'nonexistent\' on array(?0 => bool, ?1 => false, 2 => bool, 3 => false, 4 => true) in empty() does not exist.', @@ -58,6 +72,7 @@ public function testRule(): void public function testBug970(): void { + $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-970.php'], [ [ 'Variable $ar in empty() is never defined.', diff --git a/tests/PHPStan/Rules/Variables/IssetRuleTest.php b/tests/PHPStan/Rules/Variables/IssetRuleTest.php index 2f2c7b1c11..8989023c5e 100644 --- a/tests/PHPStan/Rules/Variables/IssetRuleTest.php +++ b/tests/PHPStan/Rules/Variables/IssetRuleTest.php @@ -14,13 +14,114 @@ class IssetRuleTest extends RuleTestCase { + /** @var bool */ + private $treatPhpDocTypesAsCertain; + protected function getRule(): Rule { - return new IssetRule(new IssetCheck(new PropertyDescriptor(), new PropertyReflectionFinder(), true)); + return new IssetRule(new IssetCheck( + new PropertyDescriptor(), + new PropertyReflectionFinder(), + true, + $this->treatPhpDocTypesAsCertain + )); + } + + protected function shouldTreatPhpDocTypesAsCertain(): bool + { + return $this->treatPhpDocTypesAsCertain; } public function testRule(): void { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/isset.php'], [ + [ + 'Property IssetRule\FooCoalesce::$string (string) in isset() is not nullable.', + 32, + ], + [ + 'Variable $scalar in isset() always exists and is not nullable.', + 41, + ], + [ + 'Offset \'string\' on array(1, 2, 3) in isset() does not exist.', + 45, + ], + [ + 'Offset \'string\' on array(array(1), array(2), array(3)) in isset() does not exist.', + 49, + ], + [ + 'Variable $doesNotExist in isset() is never defined.', + 51, + ], + [ + 'Offset \'dim\' on array(\'dim\' => 1, \'dim-null\' => 1|null, \'dim-null-offset\' => array(\'a\' => true|null), \'dim-empty\' => array()) in isset() always exists and is not nullable.', + 67, + ], + [ + 'Offset \'dim-null-not-set\' on array(\'dim\' => 1, \'dim-null\' => 1|null, \'dim-null-offset\' => array(\'a\' => true|null), \'dim-empty\' => array()) in isset() does not exist.', + 73, + ], + [ + 'Offset \'b\' on array() in isset() does not exist.', + 79, + ], + [ + 'Property IssetRule\FooCoalesce::$string (string) in isset() is not nullable.', + 85, + ], + [ + 'Property IssetRule\FooCoalesce::$alwaysNull (null) in isset() is always null.', + 87, + ], + [ + 'Property IssetRule\FooCoalesce::$string (string) in isset() is not nullable.', + 89, + ], + [ + 'Static property IssetRule\FooCoalesce::$staticString (string) in isset() is not nullable.', + 95, + ], + [ + 'Static property IssetRule\FooCoalesce::$staticAlwaysNull (null) in isset() is always null.', + 97, + ], + [ + 'Variable $a in isset() always exists and is always null.', + 111, + ], + [ + 'Property IssetRule\FooCoalesce::$string (string) in isset() is not nullable.', + 116, + ], + [ + 'Property IssetRule\FooCoalesce::$alwaysNull (null) in isset() is always null.', + 118, + ], + [ + 'Static property IssetRule\FooCoalesce::$staticAlwaysNull (null) in isset() is always null.', + 123, + ], + [ + 'Static property IssetRule\FooCoalesce::$staticString (string) in isset() is not nullable.', + 124, + ], + [ + "Offset 'foo' on array('foo' => string) in isset() always exists and is not nullable.", + 170, + ], + [ + "Offset 'bar' on array('bar' => 1) in isset() always exists and is not nullable.", + 173, + ], + ]); + } + + public function testRuleWithoutTreatPhpDocTypesAsCertain(): void + { + $this->treatPhpDocTypesAsCertain = false; $this->analyse([__DIR__ . '/data/isset.php'], [ [ 'Property IssetRule\FooCoalesce::$string (string) in isset() is not nullable.', @@ -102,6 +203,7 @@ public function testNativePropertyTypes(): void if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 70400) { $this->markTestSkipped('Test requires PHP 7.4.'); } + $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/isset-native-property-types.php'], [ /*[ // no way to achieve this with current PHP Reflection API @@ -119,11 +221,13 @@ public function testNativePropertyTypes(): void public function testBug4290(): void { + $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-4290.php'], []); } public function testBug4671(): void { + $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-4671.php'], [[ 'Offset string&numeric on array in isset() does not exist.', 13, @@ -132,6 +236,7 @@ public function testBug4671(): void public function testVariableCertaintyInIsset(): void { + $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/variable-certainty-isset.php'], [ [ 'Variable $alwaysDefinedNotNullable in isset() always exists and is not nullable.', @@ -206,6 +311,7 @@ public function testVariableCertaintyInIsset(): void public function testIssetInGlobalScope(): void { + $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/isset-global-scope.php'], [ [ 'Variable $alwaysDefinedNotNullable in isset() always exists and is not nullable.', @@ -220,6 +326,7 @@ public function testNullsafe(): void $this->markTestSkipped('Test requires PHP 8.0.'); } + $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/isset-nullsafe.php'], []); } diff --git a/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php b/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php index 243c36b3dd..60778f3b38 100644 --- a/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php +++ b/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php @@ -12,13 +12,27 @@ class NullCoalesceRuleTest extends \PHPStan\Testing\RuleTestCase { + /** @var bool */ + private $treatPhpDocTypesAsCertain; + protected function getRule(): \PHPStan\Rules\Rule { - return new NullCoalesceRule(new IssetCheck(new PropertyDescriptor(), new PropertyReflectionFinder(), true)); + return new NullCoalesceRule(new IssetCheck( + new PropertyDescriptor(), + new PropertyReflectionFinder(), + true, + $this->treatPhpDocTypesAsCertain + )); + } + + protected function shouldTreatPhpDocTypesAsCertain(): bool + { + return $this->treatPhpDocTypesAsCertain; } public function testCoalesceRule(): void { + $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/null-coalesce.php'], [ [ 'Property CoalesceRule\FooCoalesce::$string (string) on left side of ?? is not nullable.', @@ -125,6 +139,7 @@ public function testCoalesceAssignRule(): void $this->markTestSkipped('Test requires PHP 7.4.'); } + $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/null-coalesce-assign.php'], [ [ 'Property CoalesceAssignRule\FooCoalesce::$string (string) on left side of ??= is not nullable.', @@ -191,11 +206,13 @@ public function testNullsafe(): void $this->markTestSkipped('Test requires PHP 8.0.'); } + $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/null-coalesce-nullsafe.php'], []); } public function testVariableCertaintyInNullCoalesce(): void { + $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/variable-certainty-null.php'], [ [ 'Variable $scalar on left side of ?? always exists and is not nullable.', @@ -218,6 +235,7 @@ public function testVariableCertaintyInNullCoalesceAssign(): void $this->markTestSkipped('Test requires PHP 7.4.'); } + $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/variable-certainty-null-assign.php'], [ [ 'Variable $scalar on left side of ??= always exists and is not nullable.', @@ -236,6 +254,7 @@ public function testVariableCertaintyInNullCoalesceAssign(): void public function testNullCoalesceInGlobalScope(): void { + $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/null-coalesce-global-scope.php'], [ [ 'Variable $bar on left side of ?? always exists and is not nullable.', diff --git a/tests/PHPStan/Rules/Variables/data/isset.php b/tests/PHPStan/Rules/Variables/data/isset.php index 508bbac2ce..b937d47f97 100644 --- a/tests/PHPStan/Rules/Variables/data/isset.php +++ b/tests/PHPStan/Rules/Variables/data/isset.php @@ -160,3 +160,15 @@ function numericStringOffset(string $code): string throw new \RuntimeException(); } + +/** + * @param array{foo: string} $array + * @param 'bar' $bar + */ +function offsetFromPhpdoc(array $array, string $bar) +{ + echo isset($array['foo']) ? $array['foo'] : 0; + + $array = ['bar' => 1]; + echo isset($array[$bar]) ? $array[$bar] : 0; +} From 24c1eb422b515378a623a03629b0f4f6da10c599 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 16 Sep 2021 17:22:16 +0200 Subject: [PATCH 0322/1284] OverwrittenExitPointByFinallyRule - detect more situations --- src/Analyser/NodeScopeResolver.php | 5 +- .../OverwrittenExitPointByFinallyRuleTest.php | 54 ++++++++++++++++++ .../Rules/Exceptions/data/bug-5627.php | 55 +++++++++++++++++++ .../Rules/Methods/ReturnTypeRuleTest.php | 4 ++ .../Rules/Methods/data/returnTypes.php | 14 +++++ 5 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Rules/Exceptions/data/bug-5627.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 7dec2afc94..321f285b58 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -1150,6 +1150,7 @@ private function processStmtNode( $finalScope = $branchScopeResult->isAlwaysTerminating() ? null : $branchScope; $exitPoints = []; + $finallyExitPoints = []; $alwaysTerminating = $branchScopeResult->isAlwaysTerminating(); $hasYield = $branchScopeResult->hasYield(); @@ -1159,6 +1160,7 @@ private function processStmtNode( $finallyScope = null; } foreach ($branchScopeResult->getExitPoints() as $exitPoint) { + $finallyExitPoints[] = $exitPoint; if ($exitPoint->getStatement() instanceof Throw_) { continue; } @@ -1257,6 +1259,7 @@ private function processStmtNode( $finallyScope = $finallyScope->mergeWith($catchScopeForFinally); } foreach ($catchScopeResult->getExitPoints() as $exitPoint) { + $finallyExitPoints[] = $exitPoint; if ($exitPoint->getStatement() instanceof Throw_) { continue; } @@ -1296,7 +1299,7 @@ private function processStmtNode( if (count($finallyResult->getExitPoints()) > 0) { $nodeCallback(new FinallyExitPointsNode( $finallyResult->getExitPoints(), - $exitPoints + $finallyExitPoints ), $scope); } $exitPoints = array_merge($exitPoints, $finallyResult->getExitPoints()); diff --git a/tests/PHPStan/Rules/Exceptions/OverwrittenExitPointByFinallyRuleTest.php b/tests/PHPStan/Rules/Exceptions/OverwrittenExitPointByFinallyRuleTest.php index 268b220f34..cc89c21f85 100644 --- a/tests/PHPStan/Rules/Exceptions/OverwrittenExitPointByFinallyRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/OverwrittenExitPointByFinallyRuleTest.php @@ -19,6 +19,10 @@ protected function getRule(): Rule public function testRule(): void { $this->analyse([__DIR__ . '/data/overwritten-exit-point.php'], [ + [ + 'This throw is overwritten by a different one in the finally block below.', + 8, + ], [ 'This return is overwritten by a different one in the finally block below.', 11, @@ -34,4 +38,54 @@ public function testRule(): void ]); } + public function testBug5627(): void + { + $this->analyse([__DIR__ . '/data/bug-5627.php'], [ + [ + 'This throw is overwritten by a different one in the finally block below.', + 10, + ], + [ + 'This throw is overwritten by a different one in the finally block below.', + 12, + ], + [ + 'The overwriting return is on this line.', + 14, + ], + [ + 'This exit point is overwritten by a different one in the finally block below.', + 29, + ], + [ + 'This exit point is overwritten by a different one in the finally block below.', + 31, + ], + [ + 'The overwriting return is on this line.', + 33, + ], + [ + 'This exit point is overwritten by a different one in the finally block below.', + 39, + ], + [ + 'This exit point is overwritten by a different one in the finally block below.', + 41, + ], + [ + 'The overwriting return is on this line.', + 43, + ], + [ + 'This exit point is overwritten by a different one in the finally block below.', + 49, + ], + [ + 'The overwriting return is on this line.', + 51, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Exceptions/data/bug-5627.php b/tests/PHPStan/Rules/Exceptions/data/bug-5627.php new file mode 100644 index 0000000000..f5db414e4a --- /dev/null +++ b/tests/PHPStan/Rules/Exceptions/data/bug-5627.php @@ -0,0 +1,55 @@ +abort(); + } catch (\Exception $e) { + $this->abort(); + } finally { + return 'finally'; + } + } + + public function c(): string { + try { + $this->abort(); + } catch (\Throwable $e) { + $this->abort(); + } finally { + return 'finally'; + } + } + + public function d(): string { + try { + $this->abort(); + } finally { + return 'finally'; + } + } + +} diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index 5092659b10..13fe986ae1 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -270,6 +270,10 @@ public function testReturnTypeRule(): void 'Method ReturnTypes\NeverReturn::doFoo() should never return but return statement found.', 1238, ], + [ + 'Method ReturnTypes\NeverReturn::doBaz3() should never return but return statement found.', + 1251, + ], ]); } diff --git a/tests/PHPStan/Rules/Methods/data/returnTypes.php b/tests/PHPStan/Rules/Methods/data/returnTypes.php index 99babb0253..bc5bfc2742 100644 --- a/tests/PHPStan/Rules/Methods/data/returnTypes.php +++ b/tests/PHPStan/Rules/Methods/data/returnTypes.php @@ -1238,4 +1238,18 @@ public function doFoo(): void return; } + /** + * @return never + */ + public function doBaz3(): string + { + try { + throw new \Exception('try'); + } catch (\Exception $e) { + throw new \Exception('catch'); + } finally { + return 'finally'; + } + } + } From c362fc59b25e10d3079b5004cd738ae5fd4fed4e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 16 Sep 2021 18:11:57 +0200 Subject: [PATCH 0323/1284] Function that returns never is always an explicit throw point --- src/Analyser/NodeScopeResolver.php | 26 +++- .../OverwrittenExitPointByFinallyRuleTest.php | 88 ++++++++++++++ .../Rules/Exceptions/data/bug-5627.php | 112 ++++++++++++++++++ 3 files changed, 220 insertions(+), 6 deletions(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 321f285b58..aec3926f8b 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -1975,7 +1975,7 @@ function (MutatingScope $scope) use ($expr, $nodeCallback, $context): Expression $expr->args, $methodReflection->getVariants() ); - $methodThrowPoint = $this->getMethodThrowPoint($methodReflection, $expr, $scope); + $methodThrowPoint = $this->getMethodThrowPoint($methodReflection, $parametersAcceptor, $expr, $scope); if ($methodThrowPoint !== null) { $throwPoints[] = $methodThrowPoint; } @@ -2642,8 +2642,15 @@ private function getFunctionThrowPoint( return ThrowPoint::createExplicit($scope, $throwType, $funcCall, false); } - if ($functionReflection->getThrowType() !== null) { - $throwType = $functionReflection->getThrowType(); + $throwType = $functionReflection->getThrowType(); + if ($throwType === null && $parametersAcceptor !== null) { + $returnType = $parametersAcceptor->getReturnType(); + if ($returnType instanceof NeverType && $returnType->isExplicit()) { + $throwType = new ObjectType(\Throwable::class); + } + } + + if ($throwType !== null) { if (!$throwType instanceof VoidType) { return ThrowPoint::createExplicit($scope, $throwType, $funcCall, true); } @@ -2675,7 +2682,7 @@ private function getFunctionThrowPoint( return null; } - private function getMethodThrowPoint(MethodReflection $methodReflection, MethodCall $methodCall, MutatingScope $scope): ?ThrowPoint + private function getMethodThrowPoint(MethodReflection $methodReflection, ParametersAcceptor $parametersAcceptor, MethodCall $methodCall, MutatingScope $scope): ?ThrowPoint { foreach ($this->dynamicThrowTypeExtensionProvider->getDynamicMethodThrowTypeExtensions() as $extension) { if (!$extension->isMethodSupported($methodReflection)) { @@ -2690,8 +2697,15 @@ private function getMethodThrowPoint(MethodReflection $methodReflection, MethodC return ThrowPoint::createExplicit($scope, $throwType, $methodCall, false); } - if ($methodReflection->getThrowType() !== null) { - $throwType = $methodReflection->getThrowType(); + $throwType = $methodReflection->getThrowType(); + if ($throwType === null) { + $returnType = $parametersAcceptor->getReturnType(); + if ($returnType instanceof NeverType && $returnType->isExplicit()) { + $throwType = new ObjectType(\Throwable::class); + } + } + + if ($throwType !== null) { if (!$throwType instanceof VoidType) { return ThrowPoint::createExplicit($scope, $throwType, $methodCall, true); } diff --git a/tests/PHPStan/Rules/Exceptions/OverwrittenExitPointByFinallyRuleTest.php b/tests/PHPStan/Rules/Exceptions/OverwrittenExitPointByFinallyRuleTest.php index cc89c21f85..1698ada07c 100644 --- a/tests/PHPStan/Rules/Exceptions/OverwrittenExitPointByFinallyRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/OverwrittenExitPointByFinallyRuleTest.php @@ -85,6 +85,94 @@ public function testBug5627(): void 'The overwriting return is on this line.', 51, ], + [ + 'This throw is overwritten by a different one in the finally block below.', + 62, + ], + [ + 'This throw is overwritten by a different one in the finally block below.', + 64, + ], + [ + 'The overwriting return is on this line.', + 66, + ], + [ + 'This exit point is overwritten by a different one in the finally block below.', + 81, + ], + [ + 'This exit point is overwritten by a different one in the finally block below.', + 83, + ], + [ + 'The overwriting return is on this line.', + 85, + ], + [ + 'This exit point is overwritten by a different one in the finally block below.', + 91, + ], + [ + 'This exit point is overwritten by a different one in the finally block below.', + 93, + ], + [ + 'The overwriting return is on this line.', + 95, + ], + [ + 'This exit point is overwritten by a different one in the finally block below.', + 101, + ], + [ + 'The overwriting return is on this line.', + 103, + ], + [ + 'This throw is overwritten by a different one in the finally block below.', + 122, + ], + [ + 'This throw is overwritten by a different one in the finally block below.', + 124, + ], + [ + 'The overwriting return is on this line.', + 126, + ], + [ + 'This exit point is overwritten by a different one in the finally block below.', + 141, + ], + [ + 'This exit point is overwritten by a different one in the finally block below.', + 143, + ], + [ + 'The overwriting return is on this line.', + 145, + ], + [ + 'This exit point is overwritten by a different one in the finally block below.', + 151, + ], + [ + 'This exit point is overwritten by a different one in the finally block below.', + 153, + ], + [ + 'The overwriting return is on this line.', + 155, + ], + [ + 'This exit point is overwritten by a different one in the finally block below.', + 161, + ], + [ + 'The overwriting return is on this line.', + 163, + ], ]); } diff --git a/tests/PHPStan/Rules/Exceptions/data/bug-5627.php b/tests/PHPStan/Rules/Exceptions/data/bug-5627.php index f5db414e4a..570d8aabdc 100644 --- a/tests/PHPStan/Rules/Exceptions/data/bug-5627.php +++ b/tests/PHPStan/Rules/Exceptions/data/bug-5627.php @@ -53,3 +53,115 @@ public function d(): string { } } + +class Bar +{ + + public function a(): string { + try { + throw new \Exception('try'); + } catch (\Exception $e) { + throw new \Exception('catch'); + } finally { + return 'finally'; + } + } + + /** + * + * @return never + */ + public function abort() + { + throw new \Exception(); + } + + public function b(): string { + try { + $this->abort(); + } catch (\Exception $e) { + $this->abort(); + } finally { + return 'finally'; + } + } + + public function c(): string { + try { + $this->abort(); + } catch (\Throwable $e) { + $this->abort(); + } finally { + return 'finally'; + } + } + + public function d(): string { + try { + $this->abort(); + } finally { + return 'finally'; + } + } + +} + +/** + * @return never + */ +function abort() +{ + +} + +class Baz +{ + + public function a(): string { + try { + throw new \Exception('try'); + } catch (\Exception $e) { + throw new \Exception('catch'); + } finally { + return 'finally'; + } + } + + + + + + + + + + + public function b(): string { + try { + abort(); + } catch (\Exception $e) { + abort(); + } finally { + return 'finally'; + } + } + + public function c(): string { + try { + abort(); + } catch (\Throwable $e) { + abort(); + } finally { + return 'finally'; + } + } + + public function d(): string { + try { + abort(); + } finally { + return 'finally'; + } + } + +} From 96b7c48025dcbd5a709cea71ee4e9e73770e0d54 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 16 Sep 2021 18:31:27 +0200 Subject: [PATCH 0324/1284] [BCB] Moved implicitThrows to exceptions.implicitThrows --- conf/config.neon | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index ee036abbd5..e9de357097 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -10,6 +10,7 @@ parameters: level: null paths: [] exceptions: + implicitThrows: true uncheckedExceptionRegexes: [] uncheckedExceptionClasses: [] checkedExceptionRegexes: [] @@ -68,7 +69,6 @@ parameters: checkTooWideReturnTypesInProtectedAndPublicMethods: false checkUninitializedProperties: false inferPrivatePropertyTypeFromConstructor: false - implicitThrows: true reportMaybes: false reportMaybesInMethodSignatures: false reportMaybesInPropertyPhpDocTypes: false @@ -183,6 +183,7 @@ parametersSchema: level: schema(anyOf(int(), string()), nullable()) paths: listOf(string()) exceptions: structure([ + implicitThrows: bool(), uncheckedExceptionRegexes: listOf(string()), uncheckedExceptionClasses: listOf(string()), checkedExceptionRegexes: listOf(string()), @@ -243,7 +244,7 @@ parametersSchema: checkTooWideReturnTypesInProtectedAndPublicMethods: bool() checkUninitializedProperties: bool() inferPrivatePropertyTypeFromConstructor: bool() - implicitThrows: bool() + tipsOfTheDay: bool() reportMaybes: bool() reportMaybesInMethodSignatures: bool() @@ -450,7 +451,7 @@ services: polluteScopeWithAlwaysIterableForeach: %polluteScopeWithAlwaysIterableForeach% earlyTerminatingMethodCalls: %earlyTerminatingMethodCalls% earlyTerminatingFunctionCalls: %earlyTerminatingFunctionCalls% - implicitThrows: %implicitThrows% + implicitThrows: %exceptions.implicitThrows% - implement: PHPStan\Analyser\ResultCache\ResultCacheManagerFactory From 01321a644ffd8d6c6d91dcde7e370abc8a5ae787 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1chym=20Tou=C5=A1ek?= Date: Thu, 16 Sep 2021 18:20:51 +0200 Subject: [PATCH 0325/1284] Fix ext-ds.stub --- stubs/ext-ds.stub | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/stubs/ext-ds.stub b/stubs/ext-ds.stub index 99066ac971..78ed9f3ac5 100644 --- a/stubs/ext-ds.stub +++ b/stubs/ext-ds.stub @@ -19,7 +19,7 @@ interface Collection extends Traversable, Countable, JsonSerializable /** * @return Collection */ - public function copy(): Collection; + public function copy(); /** * @return array @@ -398,7 +398,7 @@ interface Sequence extends Collection * @param (callable(TValue): bool)|null $callback * @return Sequence */ - public function filter(callable $callback = null): Sequence; + public function filter(callable $callback = null); /** * @param TValue $value @@ -442,14 +442,14 @@ interface Sequence extends Collection * @param callable(TValue): TNewValue $callback * @return Sequence */ - public function map(callable $callback): Sequence; + public function map(callable $callback); /** * @template TValue2 * @param iterable $values * @return Sequence */ - public function merge(iterable $values): Sequence; + public function merge(iterable $values); /** * @return TValue @@ -480,7 +480,7 @@ interface Sequence extends Collection /** * @return Sequence */ - public function reversed(): Sequence; + public function reversed(); /** * @param TValue $value @@ -498,7 +498,7 @@ interface Sequence extends Collection /** * @return Sequence */ - public function slice(int $index, ?int $length = null): Sequence; + public function slice(int $index, ?int $length = null); /** * @param (callable(TValue, TValue): int)|null $comparator @@ -510,7 +510,7 @@ interface Sequence extends Collection * @param (callable(TValue, TValue): int)|null $comparator * @return Sequence */ - public function sorted(callable $comparator = null): Sequence; + public function sorted(callable $comparator = null); /** * @param TValue ...$values From 915fa2e2bb567d22e9d5968008a46e762605c780 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 16 Sep 2021 18:37:48 +0200 Subject: [PATCH 0326/1284] IgnoredErrorHelper does not produce warnings, only errors --- src/Analyser/IgnoredErrorHelper.php | 15 +++++++-------- src/Analyser/IgnoredErrorHelperResult.php | 14 -------------- src/Command/AnalyseApplication.php | 4 +--- tests/PHPStan/Analyser/AnalyserTest.php | 1 - 4 files changed, 8 insertions(+), 26 deletions(-) diff --git a/src/Analyser/IgnoredErrorHelper.php b/src/Analyser/IgnoredErrorHelper.php index cf57521f3b..f9159dd61e 100644 --- a/src/Analyser/IgnoredErrorHelper.php +++ b/src/Analyser/IgnoredErrorHelper.php @@ -41,7 +41,6 @@ public function initialize(): IgnoredErrorHelperResult { $otherIgnoreErrors = []; $ignoreErrorsByFile = []; - $warnings = []; $errors = []; foreach ($this->ignoreErrors as $i => $ignoreError) { try { @@ -89,11 +88,11 @@ public function initialize(): IgnoredErrorHelperResult $validationResult = $this->ignoredRegexValidator->validate($ignoreMessage); $ignoredTypes = $validationResult->getIgnoredTypes(); if (count($ignoredTypes) > 0) { - $warnings[] = $this->createIgnoredTypesWarning($ignoreMessage, $ignoredTypes); + $errors[] = $this->createIgnoredTypesError($ignoreMessage, $ignoredTypes); } if ($validationResult->hasAnchorsInTheMiddle()) { - $warnings[] = $this->createAnchorInTheMiddleWarning($ignoreMessage); + $errors[] = $this->createAnchorInTheMiddleError($ignoreMessage); } if ($validationResult->areAllErrorsIgnored()) { @@ -109,11 +108,11 @@ public function initialize(): IgnoredErrorHelperResult $validationResult = $this->ignoredRegexValidator->validate($ignoreMessage); $ignoredTypes = $validationResult->getIgnoredTypes(); if (count($ignoredTypes) > 0) { - $warnings[] = $this->createIgnoredTypesWarning($ignoreMessage, $ignoredTypes); + $errors[] = $this->createIgnoredTypesError($ignoreMessage, $ignoredTypes); } if ($validationResult->hasAnchorsInTheMiddle()) { - $warnings[] = $this->createAnchorInTheMiddleWarning($ignoreMessage); + $errors[] = $this->createAnchorInTheMiddleError($ignoreMessage); } if ($validationResult->areAllErrorsIgnored()) { @@ -127,7 +126,7 @@ public function initialize(): IgnoredErrorHelperResult } } - return new IgnoredErrorHelperResult($this->fileHelper, $errors, $warnings, $otherIgnoreErrors, $ignoreErrorsByFile, $this->ignoreErrors, $this->reportUnmatchedIgnoredErrors); + return new IgnoredErrorHelperResult($this->fileHelper, $errors, $otherIgnoreErrors, $ignoreErrorsByFile, $this->ignoreErrors, $this->reportUnmatchedIgnoredErrors); } /** @@ -135,7 +134,7 @@ public function initialize(): IgnoredErrorHelperResult * @param array $ignoredTypes * @return string */ - private function createIgnoredTypesWarning(string $regex, array $ignoredTypes): string + private function createIgnoredTypesError(string $regex, array $ignoredTypes): string { return sprintf( "Ignored error %s has an unescaped '|' which leads to ignoring more errors than intended. Use '\\|' instead.\n%s", @@ -149,7 +148,7 @@ private function createIgnoredTypesWarning(string $regex, array $ignoredTypes): ); } - private function createAnchorInTheMiddleWarning(string $regex): string + private function createAnchorInTheMiddleError(string $regex): string { return sprintf("Ignored error %s has an unescaped anchor '$' in the middle. This leads to unintended behavior. Use '\\$' instead.", $regex); } diff --git a/src/Analyser/IgnoredErrorHelperResult.php b/src/Analyser/IgnoredErrorHelperResult.php index 4aa7b42bf1..df9eaa2ff2 100644 --- a/src/Analyser/IgnoredErrorHelperResult.php +++ b/src/Analyser/IgnoredErrorHelperResult.php @@ -12,9 +12,6 @@ class IgnoredErrorHelperResult /** @var string[] */ private array $errors; - /** @var string[] */ - private array $warnings; - /** @var array> */ private array $otherIgnoreErrors; @@ -29,7 +26,6 @@ class IgnoredErrorHelperResult /** * @param FileHelper $fileHelper * @param string[] $errors - * @param string[] $warnings * @param array> $otherIgnoreErrors * @param array>> $ignoreErrorsByFile * @param (string|mixed[])[] $ignoreErrors @@ -38,7 +34,6 @@ class IgnoredErrorHelperResult public function __construct( FileHelper $fileHelper, array $errors, - array $warnings, array $otherIgnoreErrors, array $ignoreErrorsByFile, array $ignoreErrors, @@ -47,7 +42,6 @@ public function __construct( { $this->fileHelper = $fileHelper; $this->errors = $errors; - $this->warnings = $warnings; $this->otherIgnoreErrors = $otherIgnoreErrors; $this->ignoreErrorsByFile = $ignoreErrorsByFile; $this->ignoreErrors = $ignoreErrors; @@ -62,14 +56,6 @@ public function getErrors(): array return $this->errors; } - /** - * @return string[] - */ - public function getWarnings(): array - { - return $this->warnings; - } - /** * @param Error[] $errors * @param string[] $analysedFiles diff --git a/src/Command/AnalyseApplication.php b/src/Command/AnalyseApplication.php index 4d1656b0c6..1618d2eb28 100644 --- a/src/Command/AnalyseApplication.php +++ b/src/Command/AnalyseApplication.php @@ -92,7 +92,6 @@ public function analyse( $ignoredErrorHelperResult = $this->ignoredErrorHelper->initialize(); if (count($ignoredErrorHelperResult->getErrors()) > 0) { $errors = $ignoredErrorHelperResult->getErrors(); - $warnings = []; $internalErrors = []; $savedResultCache = false; if ($errorOutput->isDebug()) { @@ -113,7 +112,6 @@ public function analyse( $analyserResult = $resultCacheResult->getAnalyserResult(); $internalErrors = $analyserResult->getInternalErrors(); $errors = $ignoredErrorHelperResult->process($analyserResult->getErrors(), $onlyFiles, $files, count($internalErrors) > 0 || $analyserResult->hasReachedInternalErrorsCountLimit()); - $warnings = $ignoredErrorHelperResult->getWarnings(); $savedResultCache = $resultCacheResult->isSaved(); if ($analyserResult->hasReachedInternalErrorsCountLimit()) { $errors[] = sprintf('Reached internal errors count limit of %d, exiting...', $this->internalErrorsCountLimit); @@ -138,7 +136,7 @@ public function analyse( $fileSpecificErrors, $notFileSpecificErrors, $internalErrors, - $warnings, + [], $defaultLevelUsed, $projectConfigFile, $savedResultCache diff --git a/tests/PHPStan/Analyser/AnalyserTest.php b/tests/PHPStan/Analyser/AnalyserTest.php index 81fbeccb53..22e953852b 100644 --- a/tests/PHPStan/Analyser/AnalyserTest.php +++ b/tests/PHPStan/Analyser/AnalyserTest.php @@ -472,7 +472,6 @@ private function runAnalyser( return array_merge( $errors, - $ignoredErrorHelperResult->getWarnings(), $analyserResult->getInternalErrors() ); } From 5956ec4f6cd09c8d7db9466ed4e7f25706f37a43 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 16 Sep 2021 21:26:43 +0200 Subject: [PATCH 0327/1284] Fix Windows issue --- src/Command/CommandHelper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php index b83b215dc9..c21d8d1843 100644 --- a/src/Command/CommandHelper.php +++ b/src/Command/CommandHelper.php @@ -223,7 +223,7 @@ public static function begin( if ( $projectConfigFile !== null - && $projectConfigFile !== $currentWorkingDirectoryFileHelper->normalizePath(__DIR__ . '/../../conf/config.stubFiles.neon') + && $currentWorkingDirectoryFileHelper->normalizePath($projectConfigFile, '/') !== $currentWorkingDirectoryFileHelper->normalizePath(__DIR__ . '/../../conf/config.stubFiles.neon', '/') ) { $additionalConfigFiles[] = $projectConfigFile; } From 2b51392ad6e86b7ac858bc3e0094ef36488977f7 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 16 Sep 2021 21:30:15 +0200 Subject: [PATCH 0328/1284] ext-ds: Sequence::copy() --- stubs/ext-ds.stub | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/stubs/ext-ds.stub b/stubs/ext-ds.stub index 78ed9f3ac5..e5514af2e3 100644 --- a/stubs/ext-ds.stub +++ b/stubs/ext-ds.stub @@ -389,6 +389,11 @@ interface Sequence extends Collection */ public function apply(callable $callback); + /** + * @return Sequence + */ + public function copy(): Sequence; + /** * @param TValue ...$values */ From 36649d37c597bd3e9bdc0c9f220380ccfc4162c3 Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Thu, 16 Sep 2021 21:51:18 +0200 Subject: [PATCH 0329/1284] Support general array and array shape as template type bound --- src/Rules/Generics/TemplateTypeCheck.php | 4 + src/Type/Generic/TemplateArrayType.php | 54 ++++++++++++++ .../Generic/TemplateConstantArrayType.php | 54 ++++++++++++++ src/Type/Generic/TemplateTypeFactory.php | 10 +++ tests/PHPStan/Analyser/data/generics.php | 73 +++++++++++++++++++ .../Rules/Generics/data/function-template.php | 12 +++ .../Rules/Methods/CallMethodsRuleTest.php | 18 +++++ .../Rules/Methods/data/call-methods.php | 20 +++++ 8 files changed, 245 insertions(+) create mode 100644 src/Type/Generic/TemplateArrayType.php create mode 100644 src/Type/Generic/TemplateConstantArrayType.php diff --git a/src/Rules/Generics/TemplateTypeCheck.php b/src/Rules/Generics/TemplateTypeCheck.php index 37a6b55e38..1d6ca66b19 100644 --- a/src/Rules/Generics/TemplateTypeCheck.php +++ b/src/Rules/Generics/TemplateTypeCheck.php @@ -7,7 +7,9 @@ use PHPStan\Rules\ClassCaseSensitivityCheck; use PHPStan\Rules\ClassNameNodePair; use PHPStan\Rules\RuleErrorBuilder; +use PHPStan\Type\ArrayType; use PHPStan\Type\BooleanType; +use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\FloatType; use PHPStan\Type\Generic\GenericObjectType; use PHPStan\Type\Generic\TemplateType; @@ -110,6 +112,8 @@ public function check( $boundClass = get_class($type); if ( $boundClass === MixedType::class + || $boundClass === ConstantArrayType::class + || $boundClass === ArrayType::class || $boundClass === StringType::class || $boundClass === IntegerType::class || $boundClass === FloatType::class diff --git a/src/Type/Generic/TemplateArrayType.php b/src/Type/Generic/TemplateArrayType.php new file mode 100644 index 0000000000..ce577adb31 --- /dev/null +++ b/src/Type/Generic/TemplateArrayType.php @@ -0,0 +1,54 @@ + */ + use TemplateTypeTrait; + use UndecidedComparisonCompoundTypeTrait; + + public function __construct( + TemplateTypeScope $scope, + TemplateTypeStrategy $templateTypeStrategy, + TemplateTypeVariance $templateTypeVariance, + string $name, + ArrayType $bound + ) + { + parent::__construct($bound->getKeyType(), $bound->getItemType()); + $this->scope = $scope; + $this->strategy = $templateTypeStrategy; + $this->variance = $templateTypeVariance; + $this->name = $name; + $this->bound = $bound; + } + + public function traverse(callable $cb): Type + { + $newBound = $cb($this->getBound()); + if ($this->getBound() !== $newBound && $newBound instanceof ArrayType) { + return new self( + $this->scope, + $this->strategy, + $this->variance, + $this->name, + $newBound + ); + } + + return $this; + } + + protected function shouldGeneralizeInferredType(): bool + { + return false; + } + +} diff --git a/src/Type/Generic/TemplateConstantArrayType.php b/src/Type/Generic/TemplateConstantArrayType.php new file mode 100644 index 0000000000..2684484d6e --- /dev/null +++ b/src/Type/Generic/TemplateConstantArrayType.php @@ -0,0 +1,54 @@ + */ + use TemplateTypeTrait; + use UndecidedComparisonCompoundTypeTrait; + + public function __construct( + TemplateTypeScope $scope, + TemplateTypeStrategy $templateTypeStrategy, + TemplateTypeVariance $templateTypeVariance, + string $name, + ConstantArrayType $bound + ) + { + parent::__construct($bound->getKeyTypes(), $bound->getValueTypes(), $bound->getNextAutoIndex(), $bound->getOptionalKeys()); + $this->scope = $scope; + $this->strategy = $templateTypeStrategy; + $this->variance = $templateTypeVariance; + $this->name = $name; + $this->bound = $bound; + } + + public function traverse(callable $cb): Type + { + $newBound = $cb($this->getBound()); + if ($this->getBound() !== $newBound && $newBound instanceof ConstantArrayType) { + return new self( + $this->scope, + $this->strategy, + $this->variance, + $this->name, + $newBound + ); + } + + return $this; + } + + protected function shouldGeneralizeInferredType(): bool + { + return false; + } + +} diff --git a/src/Type/Generic/TemplateTypeFactory.php b/src/Type/Generic/TemplateTypeFactory.php index 4c125db586..252e434c76 100644 --- a/src/Type/Generic/TemplateTypeFactory.php +++ b/src/Type/Generic/TemplateTypeFactory.php @@ -3,8 +3,10 @@ namespace PHPStan\Type\Generic; use PHPStan\PhpDoc\Tag\TemplateTag; +use PHPStan\Type\ArrayType; use PHPStan\Type\BenevolentUnionType; use PHPStan\Type\BooleanType; +use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\FloatType; use PHPStan\Type\IntegerType; use PHPStan\Type\MixedType; @@ -38,6 +40,14 @@ public static function create(TemplateTypeScope $scope, string $name, ?Type $bou return new TemplateObjectWithoutClassType($scope, $strategy, $variance, $name, $bound); } + if ($bound instanceof ArrayType && ($boundClass === ArrayType::class || $bound instanceof TemplateType)) { + return new TemplateArrayType($scope, $strategy, $variance, $name, $bound); + } + + if ($bound instanceof ConstantArrayType && ($boundClass === ConstantArrayType::class || $bound instanceof TemplateType)) { + return new TemplateConstantArrayType($scope, $strategy, $variance, $name, $bound); + } + if ($bound instanceof StringType && ($boundClass === StringType::class || $bound instanceof TemplateType)) { return new TemplateStringType($scope, $strategy, $variance, $name, $bound); } diff --git a/tests/PHPStan/Analyser/data/generics.php b/tests/PHPStan/Analyser/data/generics.php index 8488158b12..2f0afe2259 100644 --- a/tests/PHPStan/Analyser/data/generics.php +++ b/tests/PHPStan/Analyser/data/generics.php @@ -1487,3 +1487,76 @@ function newClassString($type): void { assertType('T1 of object (function PHPStan\Generics\FunctionsAssertType\newClassString(), argument)', new $type); } + +/** + * @template T of array + * @param T $a + * @return T + */ +function arrayBound1(array $a): array +{ + return $a; +} + +/** + * @template T of array + * @param T $a + * @return T + */ +function arrayBound2(array $a): array +{ + return $a; +} + +/** + * @template T of list + * @param T $a + * @return T + */ +function arrayBound3(array $a): array +{ + return $a; +} + +/** + * @template T of list> + * @param T $a + * @return T + */ +function arrayBound4(array $a): array +{ + return $a; +} + +/** + * @template T of array + * @param T $a + * @return array + */ +function arrayBound5(array $a): array +{ + return $a; +} + +function (): void { + assertType('array(1 => true)', arrayBound1([1 => true])); + assertType("array('a', 'b', 'c')", arrayBound2(range('a', 'c'))); + assertType('array', arrayBound2([1, 2, 3])); + assertType('array(true, false, true)', arrayBound3([true, false, true])); + assertType("array(array('a' => 'a'), array('b' => 'b'), array('c' => 'c'))", arrayBound4([['a' => 'a'], ['b' => 'b'], ['c' => 'c']])); + assertType('array', arrayBound5(range('a', 'c'))); +}; + +/** + * @template T of array{0: string, 1: bool} + * @param T $a + * @return T + */ +function constantArrayBound(array $a): array +{ + return $a; +} + +function (): void { + assertType("array('string', true)", constantArrayBound(['string', true])); +}; diff --git a/tests/PHPStan/Rules/Generics/data/function-template.php b/tests/PHPStan/Rules/Generics/data/function-template.php index 038240fa27..11ed760df6 100644 --- a/tests/PHPStan/Rules/Generics/data/function-template.php +++ b/tests/PHPStan/Rules/Generics/data/function-template.php @@ -51,3 +51,15 @@ function resourceBound() { } + +/** @template T of array */ +function izumi() +{ + +} + +/** @template T of array{0: string, 1: bool} */ +function nakano() +{ + +} diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 644a61c0df..00e90c1725 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -497,6 +497,15 @@ public function testCallMethods(): void 'Parameter #1 $date of method Test\HelloWorld3::sayHello() expects array|int, DateTimeInterface given.', 1732, ], + [ + 'Parameter #1 $a of method Test\InvalidReturnTypeUsingArrayTemplateTypeBound::bar() expects array, array given.', + 1751, + ], + [ + 'Unable to resolve the template type T in call to method Test\InvalidReturnTypeUsingArrayTemplateTypeBound::bar()', + 1751, + 'See: https://phpstan.org/blog/solving-phpstan-error-unable-to-resolve-template-type', + ], ]); } @@ -775,6 +784,15 @@ public function testCallMethodsOnThisOnly(): void 'Parameter #1 $date of method Test\HelloWorld3::sayHello() expects array|int, DateTimeInterface given.', 1732, ], + [ + 'Parameter #1 $a of method Test\InvalidReturnTypeUsingArrayTemplateTypeBound::bar() expects array, array given.', + 1751, + ], + [ + 'Unable to resolve the template type T in call to method Test\InvalidReturnTypeUsingArrayTemplateTypeBound::bar()', + 1751, + 'See: https://phpstan.org/blog/solving-phpstan-error-unable-to-resolve-template-type', + ], ]); } diff --git a/tests/PHPStan/Rules/Methods/data/call-methods.php b/tests/PHPStan/Rules/Methods/data/call-methods.php index f2aa4b3ec5..f8838371a5 100644 --- a/tests/PHPStan/Rules/Methods/data/call-methods.php +++ b/tests/PHPStan/Rules/Methods/data/call-methods.php @@ -1732,3 +1732,23 @@ public function foo($d): void $this->sayHello($d); } } + +class InvalidReturnTypeUsingArrayTemplateTypeBound +{ + + /** + * @template T of array + * @param T $a + * @return T + */ + function bar(array $a): array + { + return $a; + } + + public function doBar() + { + $this->bar(range(1, 3)); + } + +} From be27584056cbfe25b6a0e875d3e42e1fcd7efb31 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 16 Sep 2021 21:49:18 +0200 Subject: [PATCH 0330/1284] ObjectType - make class line part of the cache key --- src/Type/ObjectType.php | 17 +++++++++++++- .../Analyser/AnalyserIntegrationTest.php | 6 +++++ tests/PHPStan/Analyser/data/bug-5639.php | 22 +++++++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Analyser/data/bug-5639.php diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index f52093a9f9..2214826e94 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -391,7 +391,15 @@ public function describe(VerbosityLevel $level): string $preciseNameCallback, $preciseWithSubtracted, function () use ($preciseWithSubtracted): string { - return $preciseWithSubtracted() . '-' . static::class . '-' . $this->describeAdditionalCacheKey(); + $reflection = $this->classReflection; + $line = ''; + if ($reflection !== null) { + $line .= '-'; + $line .= (string) $reflection->getNativeReflection()->getStartLine(); + $line .= '-'; + } + + return $preciseWithSubtracted() . '-' . static::class . '-' . $line . $this->describeAdditionalCacheKey(); } ); } @@ -412,6 +420,13 @@ private function describeCache(): string $description .= sprintf('~%s', $this->subtractedType->describe(VerbosityLevel::cache())); } + $reflection = $this->classReflection; + if ($reflection !== null) { + $description .= '-'; + $description .= (string) $reflection->getNativeReflection()->getStartLine(); + $description .= '-'; + } + return $description; } diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 68235a4c5a..0583d9c9b1 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -441,6 +441,12 @@ public function testBug5527(): void $this->assertCount(0, $errors); } + public function testBug5639(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-5639.php'); + $this->assertCount(0, $errors); + } + /** * @param string $file * @return \PHPStan\Analyser\Error[] diff --git a/tests/PHPStan/Analyser/data/bug-5639.php b/tests/PHPStan/Analyser/data/bug-5639.php new file mode 100644 index 0000000000..c0eeed3370 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-5639.php @@ -0,0 +1,22 @@ +message, $this->file, $this->line); + return $result; + } + } +} +else +{ + class Foo extends \Error { + function __toString(): string { + $result = \sprintf("%s\n\nin %s on line %s", $this->message, $this->file, $this->line); + return $result; + } + } +} From cc802c1bbf463e022bf9f8e902deea17bb1023aa Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 16 Sep 2021 21:57:35 +0200 Subject: [PATCH 0331/1284] Fix --- stubs/ext-ds.stub | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stubs/ext-ds.stub b/stubs/ext-ds.stub index e5514af2e3..df3a08d9e7 100644 --- a/stubs/ext-ds.stub +++ b/stubs/ext-ds.stub @@ -43,7 +43,7 @@ final class Deque implements Sequence /** * @return Deque */ - public function copy(): Deque + public function copy() { } @@ -392,7 +392,7 @@ interface Sequence extends Collection /** * @return Sequence */ - public function copy(): Sequence; + public function copy(); /** * @param TValue ...$values @@ -541,7 +541,7 @@ final class Vector implements Sequence /** * @return Vector */ - public function copy(): Vector + public function copy() { } From 2de65e60a043b8cf088361b55e0572bb83387f08 Mon Sep 17 00:00:00 2001 From: Can Vural Date: Thu, 16 Sep 2021 21:31:16 +0200 Subject: [PATCH 0332/1284] eval function can throw exception --- src/Analyser/NodeScopeResolver.php | 8 +++++++- tests/PHPStan/Analyser/NodeScopeResolverTest.php | 2 ++ .../Analyser/data/eval-implicit-throw.php | 16 ++++++++++++++++ .../PHPStan/Rules/Exceptions/data/dead-catch.php | 7 +++++++ 4 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Analyser/data/eval-implicit-throw.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index aec3926f8b..e900b481c1 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -2337,7 +2337,6 @@ static function () use ($expr, $rightResult): MutatingScope { $expr instanceof Expr\BitwiseNot || $expr instanceof Cast || $expr instanceof Expr\Clone_ - || $expr instanceof Expr\Eval_ || $expr instanceof Expr\Print_ || $expr instanceof Expr\UnaryMinus || $expr instanceof Expr\UnaryPlus @@ -2346,6 +2345,13 @@ static function () use ($expr, $rightResult): MutatingScope { $throwPoints = $result->getThrowPoints(); $hasYield = $result->hasYield(); + $scope = $result->getScope(); + } elseif ($expr instanceof Expr\Eval_) { + $result = $this->processExprNode($expr->expr, $scope, $nodeCallback, $context->enterDeep()); + $throwPoints = $result->getThrowPoints(); + $throwPoints[] = ThrowPoint::createImplicit($scope, $expr); + $hasYield = $result->hasYield(); + $scope = $result->getScope(); } elseif ($expr instanceof Expr\YieldFrom) { $result = $this->processExprNode($expr->expr, $scope, $nodeCallback, $context->enterDeep()); diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index bfdd577773..d0e7ffe0da 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -507,6 +507,8 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/missing-closure-native-return-typehint.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4741.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/more-type-strings.php'); + + yield from $this->gatherAssertTypes(__DIR__ . '/data/eval-implicit-throw.php'); } /** diff --git a/tests/PHPStan/Analyser/data/eval-implicit-throw.php b/tests/PHPStan/Analyser/data/eval-implicit-throw.php new file mode 100644 index 0000000000..a95aaca46e --- /dev/null +++ b/tests/PHPStan/Analyser/data/eval-implicit-throw.php @@ -0,0 +1,16 @@ + Date: Thu, 16 Sep 2021 22:29:54 +0200 Subject: [PATCH 0333/1284] Update phpdoc-parser Closes https://github.com/phpstan/phpstan/issues/5628 --- composer.json | 2 +- composer.lock | 18 ++++++------- .../Analyser/NodeScopeResolverTest.php | 1 + tests/PHPStan/Analyser/data/bug-5628.php | 27 +++++++++++++++++++ 4 files changed, 38 insertions(+), 10 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/bug-5628.php diff --git a/composer.json b/composer.json index 7619225be7..71f62fd27d 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "4.3.68", "phpstan/php-8-stubs": "^0.1.23", - "phpstan/phpdoc-parser": "^1.0", + "phpstan/phpdoc-parser": "^1.1.0", "react/child-process": "^0.6.1", "react/event-loop": "^1.1", "react/http": "^1.1", diff --git a/composer.lock b/composer.lock index d16953dc72..1d115a8232 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5f6ecea8ad1c410153bb884e8b26248e", + "content-hash": "07913e44f81b3c9d6fca5cd7a637b594", "packages": [ { "name": "clue/block-react", @@ -2177,16 +2177,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.0.0", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "9e8e6e79f86c2e4fdddb189e569276ce693b6dd7" + "reference": "605e3697d7e521a8c7f032662e13a8c104d60fbf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/9e8e6e79f86c2e4fdddb189e569276ce693b6dd7", - "reference": "9e8e6e79f86c2e4fdddb189e569276ce693b6dd7", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/605e3697d7e521a8c7f032662e13a8c104d60fbf", + "reference": "605e3697d7e521a8c7f032662e13a8c104d60fbf", "shasum": "" }, "require": { @@ -2195,8 +2195,8 @@ "require-dev": { "php-parallel-lint/php-parallel-lint": "^1.2", "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12.87", - "phpstan/phpstan-strict-rules": "^0.12.5", + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.0", "phpunit/phpunit": "^9.5", "symfony/process": "^5.2" }, @@ -2220,9 +2220,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.0.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.1.0" }, - "time": "2021-09-12T20:00:31+00:00" + "time": "2021-09-16T20:28:27+00:00" }, { "name": "psr/container", diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index d0e7ffe0da..e441801ed3 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -509,6 +509,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/more-type-strings.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/eval-implicit-throw.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5628.php'); } /** diff --git a/tests/PHPStan/Analyser/data/bug-5628.php b/tests/PHPStan/Analyser/data/bug-5628.php new file mode 100644 index 0000000000..c465156dd4 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-5628.php @@ -0,0 +1,27 @@ +getRandomSuit()); + } +} From 4f946ec91bc2ed57b4444391025be7239f5704de Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 16 Sep 2021 22:47:51 +0200 Subject: [PATCH 0334/1284] Update phpdoc-parser --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 71f62fd27d..1036569f86 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "4.3.68", "phpstan/php-8-stubs": "^0.1.23", - "phpstan/phpdoc-parser": "^1.1.0", + "phpstan/phpdoc-parser": "^1.2.0", "react/child-process": "^0.6.1", "react/event-loop": "^1.1", "react/http": "^1.1", diff --git a/composer.lock b/composer.lock index 1d115a8232..6470d9cfc4 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "07913e44f81b3c9d6fca5cd7a637b594", + "content-hash": "8b64858812516ba2637d2eceabc88a26", "packages": [ { "name": "clue/block-react", @@ -2177,16 +2177,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.1.0", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "605e3697d7e521a8c7f032662e13a8c104d60fbf" + "reference": "dbc093d7af60eff5cd575d2ed761b15ed40bd08e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/605e3697d7e521a8c7f032662e13a8c104d60fbf", - "reference": "605e3697d7e521a8c7f032662e13a8c104d60fbf", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/dbc093d7af60eff5cd575d2ed761b15ed40bd08e", + "reference": "dbc093d7af60eff5cd575d2ed761b15ed40bd08e", "shasum": "" }, "require": { @@ -2220,9 +2220,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.1.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.2.0" }, - "time": "2021-09-16T20:28:27+00:00" + "time": "2021-09-16T20:46:02+00:00" }, { "name": "psr/container", From e5bbb52fbc9139502f231999173ba6412e62587d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 16 Sep 2021 22:38:47 +0200 Subject: [PATCH 0335/1284] Level 9 with checkExplicitMixed --- conf/config.level9.neon | 5 +++++ conf/config.levelmax.neon | 2 +- src/Testing/LevelsTestCase.php | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 conf/config.level9.neon diff --git a/conf/config.level9.neon b/conf/config.level9.neon new file mode 100644 index 0000000000..efb3e30ffa --- /dev/null +++ b/conf/config.level9.neon @@ -0,0 +1,5 @@ +includes: + - config.level8.neon + +parameters: + checkExplicitMixed: true diff --git a/conf/config.levelmax.neon b/conf/config.levelmax.neon index 789cb34109..da48578fe3 100644 --- a/conf/config.levelmax.neon +++ b/conf/config.levelmax.neon @@ -1,2 +1,2 @@ includes: - - config.level8.neon + - config.level9.neon diff --git a/src/Testing/LevelsTestCase.php b/src/Testing/LevelsTestCase.php index 1a164ab44a..e30926172a 100644 --- a/src/Testing/LevelsTestCase.php +++ b/src/Testing/LevelsTestCase.php @@ -47,7 +47,7 @@ public function testLevels( $exceptions = []; - foreach (range(0, 8) as $level) { + foreach (range(0, 9) as $level) { unset($outputLines); exec(sprintf('%s %s clear-result-cache %s 2>&1', escapeshellarg(PHP_BINARY), $command, $configPath !== null ? '--configuration ' . escapeshellarg($configPath) : ''), $clearResultCacheOutputLines, $clearResultCacheExitCode); if ($clearResultCacheExitCode !== 0) { From dc0fe624b7df3d5fccd9e11a769e3b2404b53982 Mon Sep 17 00:00:00 2001 From: Jean-Luc Herren Date: Fri, 17 Sep 2021 13:00:40 +0200 Subject: [PATCH 0336/1284] TypeSpecifierContext - Do not create multiple equivalent objects Previously, ->negate() would create objects with negative values, which are harder to understand in the debugger, but behave the same way as their positive counterpart (e.g. -4 and 12 are equivalent in the lower 4 bits). --- src/Analyser/TypeSpecifierContext.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Analyser/TypeSpecifierContext.php b/src/Analyser/TypeSpecifierContext.php index 7a5feba27e..bbebcf18e6 100644 --- a/src/Analyser/TypeSpecifierContext.php +++ b/src/Analyser/TypeSpecifierContext.php @@ -12,6 +12,7 @@ class TypeSpecifierContext public const CONTEXT_FALSE = 0b0100; public const CONTEXT_FALSEY_BUT_NOT_FALSE = 0b1000; public const CONTEXT_FALSEY = self::CONTEXT_FALSE | self::CONTEXT_FALSEY_BUT_NOT_FALSE; + public const CONTEXT_BITMASK = 0b1111; private ?int $value; @@ -59,7 +60,7 @@ public function negate(): self if ($this->value === null) { throw new \PHPStan\ShouldNotHappenException(); } - return self::create(~$this->value); + return self::create(~$this->value & self::CONTEXT_BITMASK); } public function true(): bool From e594bbbab5909e827b29309f1d76215debdd4e8d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 17 Sep 2021 14:27:26 +0200 Subject: [PATCH 0337/1284] Fix --- .../Levels/data/arrayDimFetches-9.json | 7 +++ .../Levels/data/callableCalls-9-missing.json | 12 +++++ .../PHPStan/Levels/data/clone-9-missing.json | 12 +++++ tests/PHPStan/Levels/data/clone-9.json | 7 +++ .../data/constantAccesses-9-missing.json | 17 ++++++ .../Levels/data/methodCalls-9-missing.json | 52 +++++++++++++++++++ .../PHPStan/Levels/data/object-9-missing.json | 22 ++++++++ .../data/propertyAccesses-9-missing.json | 32 ++++++++++++ .../Levels/data/stringOffsetAccess-9.json | 22 ++++++++ 9 files changed, 183 insertions(+) create mode 100644 tests/PHPStan/Levels/data/arrayDimFetches-9.json create mode 100644 tests/PHPStan/Levels/data/callableCalls-9-missing.json create mode 100644 tests/PHPStan/Levels/data/clone-9-missing.json create mode 100644 tests/PHPStan/Levels/data/clone-9.json create mode 100644 tests/PHPStan/Levels/data/constantAccesses-9-missing.json create mode 100644 tests/PHPStan/Levels/data/methodCalls-9-missing.json create mode 100644 tests/PHPStan/Levels/data/object-9-missing.json create mode 100644 tests/PHPStan/Levels/data/propertyAccesses-9-missing.json create mode 100644 tests/PHPStan/Levels/data/stringOffsetAccess-9.json diff --git a/tests/PHPStan/Levels/data/arrayDimFetches-9.json b/tests/PHPStan/Levels/data/arrayDimFetches-9.json new file mode 100644 index 0000000000..fb1695ce25 --- /dev/null +++ b/tests/PHPStan/Levels/data/arrayDimFetches-9.json @@ -0,0 +1,7 @@ +[ + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 15, + "ignorable": true + } +] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/callableCalls-9-missing.json b/tests/PHPStan/Levels/data/callableCalls-9-missing.json new file mode 100644 index 0000000000..5c7f12b38d --- /dev/null +++ b/tests/PHPStan/Levels/data/callableCalls-9-missing.json @@ -0,0 +1,12 @@ +[ + { + "message": "Closure invoked with 0 parameters, 1 required.", + "line": 37, + "ignorable": true + }, + { + "message": "Trying to invoke int but it's not a callable.", + "line": 43, + "ignorable": true + } +] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/clone-9-missing.json b/tests/PHPStan/Levels/data/clone-9-missing.json new file mode 100644 index 0000000000..40e1203120 --- /dev/null +++ b/tests/PHPStan/Levels/data/clone-9-missing.json @@ -0,0 +1,12 @@ +[ + { + "message": "Cannot clone non-object variable $nullableInt of type int.", + "line": 34, + "ignorable": true + }, + { + "message": "Cannot clone non-object variable $nullableUnion of type int|Levels\\Cloning\\Foo.", + "line": 35, + "ignorable": true + } +] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/clone-9.json b/tests/PHPStan/Levels/data/clone-9.json new file mode 100644 index 0000000000..8148322a42 --- /dev/null +++ b/tests/PHPStan/Levels/data/clone-9.json @@ -0,0 +1,7 @@ +[ + { + "message": "Cannot clone non-object variable $mixed of type mixed.", + "line": 36, + "ignorable": true + } +] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/constantAccesses-9-missing.json b/tests/PHPStan/Levels/data/constantAccesses-9-missing.json new file mode 100644 index 0000000000..0cc5a3f5d4 --- /dev/null +++ b/tests/PHPStan/Levels/data/constantAccesses-9-missing.json @@ -0,0 +1,17 @@ +[ + { + "message": "Access to undefined constant Levels\\ConstantAccesses\\Foo::BAR_CONSTANT.", + "line": 53, + "ignorable": true + }, + { + "message": "Access to undefined constant Levels\\ConstantAccesses\\Bar|Levels\\ConstantAccesses\\Foo::BAR_CONSTANT.", + "line": 56, + "ignorable": true + }, + { + "message": "Access to undefined constant Levels\\ConstantAccesses\\Bar|Levels\\ConstantAccesses\\Foo::FOO_CONSTANT.", + "line": 55, + "ignorable": true + } +] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/methodCalls-9-missing.json b/tests/PHPStan/Levels/data/methodCalls-9-missing.json new file mode 100644 index 0000000000..47cdcab769 --- /dev/null +++ b/tests/PHPStan/Levels/data/methodCalls-9-missing.json @@ -0,0 +1,52 @@ +[ + { + "message": "Method Levels\\MethodCalls\\Foo::doFoo() invoked with 0 parameters, 1 required.", + "line": 53, + "ignorable": true + }, + { + "message": "Method Levels\\MethodCalls\\Foo::doFoo() invoked with 0 parameters, 1 required.", + "line": 56, + "ignorable": true + }, + { + "message": "Method Levels\\MethodCalls\\Foo::doFoo() invoked with 0 parameters, 1 required.", + "line": 59, + "ignorable": true + }, + { + "message": "Method Levels\\MethodCalls\\Foo::doFoo() invoked with 0 parameters, 1 required.", + "line": 162, + "ignorable": true + }, + { + "message": "Method Levels\\MethodCalls\\Foo::doFoo() invoked with 0 parameters, 1 required.", + "line": 166, + "ignorable": true + }, + { + "message": "Method Levels\\MethodCalls\\Foo::doFoo() invoked with 0 parameters, 1 required.", + "line": 170, + "ignorable": true + }, + { + "message": "Call to an undefined method Levels\\MethodCalls\\Bar|Levels\\MethodCalls\\Foo::doFoo().", + "line": 59, + "ignorable": true + }, + { + "message": "Call to an undefined method Levels\\MethodCalls\\Bar|Levels\\MethodCalls\\Foo::doFoo().", + "line": 60, + "ignorable": true + }, + { + "message": "Call to an undefined method Levels\\MethodCalls\\Bar|Levels\\MethodCalls\\Foo::doFoo().", + "line": 170, + "ignorable": true + }, + { + "message": "Call to an undefined method Levels\\MethodCalls\\Bar|Levels\\MethodCalls\\Foo::doFoo().", + "line": 171, + "ignorable": true + } +] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/object-9-missing.json b/tests/PHPStan/Levels/data/object-9-missing.json new file mode 100644 index 0000000000..4d1f2153ba --- /dev/null +++ b/tests/PHPStan/Levels/data/object-9-missing.json @@ -0,0 +1,22 @@ +[ + { + "message": "Call to an undefined method object::foo().", + "line": 25, + "ignorable": true + }, + { + "message": "Access to an undefined property object::$bar.", + "line": 26, + "ignorable": true + }, + { + "message": "Call to an undefined static method object::baz().", + "line": 28, + "ignorable": true + }, + { + "message": "Access to an undefined static property object::$dolor.", + "line": 29, + "ignorable": true + } +] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/propertyAccesses-9-missing.json b/tests/PHPStan/Levels/data/propertyAccesses-9-missing.json new file mode 100644 index 0000000000..1a8bc8b4b7 --- /dev/null +++ b/tests/PHPStan/Levels/data/propertyAccesses-9-missing.json @@ -0,0 +1,32 @@ +[ + { + "message": "Access to an undefined property Levels\\PropertyAccesses\\Foo::$bar.", + "line": 61, + "ignorable": true + }, + { + "message": "Access to an undefined property Levels\\PropertyAccesses\\Foo::$bar.", + "line": 166, + "ignorable": true + }, + { + "message": "Access to an undefined property Levels\\PropertyAccesses\\Bar|Levels\\PropertyAccesses\\Foo::$foo.", + "line": 63, + "ignorable": true + }, + { + "message": "Access to an undefined property Levels\\PropertyAccesses\\Bar|Levels\\PropertyAccesses\\Foo::$bar.", + "line": 64, + "ignorable": true + }, + { + "message": "Access to an undefined property Levels\\PropertyAccesses\\Bar|Levels\\PropertyAccesses\\Foo::$foo.", + "line": 169, + "ignorable": true + }, + { + "message": "Access to an undefined property Levels\\PropertyAccesses\\Bar|Levels\\PropertyAccesses\\Foo::$bar.", + "line": 170, + "ignorable": true + } +] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/stringOffsetAccess-9.json b/tests/PHPStan/Levels/data/stringOffsetAccess-9.json new file mode 100644 index 0000000000..bf290e21ef --- /dev/null +++ b/tests/PHPStan/Levels/data/stringOffsetAccess-9.json @@ -0,0 +1,22 @@ +[ + { + "message": "Cannot access offset 0 on mixed.", + "line": 39, + "ignorable": true + }, + { + "message": "Cannot access offset 'foo' on mixed.", + "line": 43, + "ignorable": true + }, + { + "message": "Cannot access offset 12.34 on mixed.", + "line": 47, + "ignorable": true + }, + { + "message": "Cannot access offset int|object on mixed.", + "line": 51, + "ignorable": true + } +] \ No newline at end of file From d19e518b2839de040b24d12b683b14ee16bd6639 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 17 Sep 2021 14:29:42 +0200 Subject: [PATCH 0338/1284] Inspect types for errors recursively --- conf/bleedingEdge.neon | 1 - conf/config.neon | 7 --- src/PhpDoc/TypeNodeResolver.php | 17 +++---- src/Rules/MissingTypehintCheck.php | 8 +--- src/Rules/PhpDoc/UnresolvableTypeHelper.php | 45 +++++++------------ .../Rules/Classes/ClassAttributesRuleTest.php | 2 +- .../ClassConstantAttributesRuleTest.php | 2 +- .../Rules/Classes/InstantiationRuleTest.php | 2 +- tests/PHPStan/Rules/Classes/MixinRuleTest.php | 2 +- .../ArrowFunctionAttributesRuleTest.php | 2 +- .../Rules/Functions/CallCallablesRuleTest.php | 2 +- .../CallToFunctionParametersRuleTest.php | 2 +- .../Functions/ClosureAttributesRuleTest.php | 2 +- .../Functions/FunctionAttributesRuleTest.php | 2 +- .../Functions/ParamAttributesRuleTest.php | 2 +- .../Rules/Methods/CallMethodsRuleTest.php | 2 +- .../Methods/CallStaticMethodsRuleTest.php | 2 +- .../Methods/MethodAttributesRuleTest.php | 2 +- ...MissingMethodParameterTypehintRuleTest.php | 18 +------- ...patibleClassConstantPhpDocTypeRuleTest.php | 2 +- .../PhpDoc/IncompatiblePhpDocTypeRuleTest.php | 11 +---- ...IncompatiblePropertyPhpDocTypeRuleTest.php | 2 +- .../InvalidPhpDocVarTagTypeRuleTest.php | 2 +- .../Properties/PropertyAttributesRuleTest.php | 2 +- 24 files changed, 41 insertions(+), 100 deletions(-) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 05343902ac..b142aeb162 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -15,7 +15,6 @@ parameters: skipCheckGenericClasses: [] rememberFunctionValues: true apiRules: true - deepInspectTypes: true neverInGenericReturnType: true finalByPhpDocTag: true classConstants: true diff --git a/conf/config.neon b/conf/config.neon index e9de357097..cc994f136b 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -35,7 +35,6 @@ parameters: skipCheckGenericClasses: [] rememberFunctionValues: false apiRules: false - deepInspectTypes: false neverInGenericReturnType: false finalByPhpDocTag: false classConstants: false @@ -210,7 +209,6 @@ parametersSchema: skipCheckGenericClasses: listOf(string()), rememberFunctionValues: bool(), apiRules: bool(), - deepInspectTypes: bool(), neverInGenericReturnType: bool(), finalByPhpDocTag: bool(), classConstants: bool(), @@ -407,8 +405,6 @@ services: - class: PHPStan\PhpDoc\TypeNodeResolver - arguments: - deepInspectTypes: %featureToggles.deepInspectTypes% - class: PHPStan\PhpDoc\TypeNodeResolverExtensionRegistryProvider @@ -845,7 +841,6 @@ services: checkGenericClassInNonGenericObjectType: %checkGenericClassInNonGenericObjectType% checkMissingCallableSignature: %checkMissingCallableSignature% skipCheckGenericClasses: %featureToggles.skipCheckGenericClasses% - deepInspectTypes: %featureToggles.deepInspectTypes% - class: PHPStan\Rules\NullsafeCheck @@ -855,8 +850,6 @@ services: - class: PHPStan\Rules\PhpDoc\UnresolvableTypeHelper - arguments: - deepInspectTypes: %featureToggles.deepInspectTypes% - class: PHPStan\Rules\Properties\LazyReadWritePropertiesExtensionProvider diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index 0533c2acfd..78e0ad68fa 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -75,17 +75,13 @@ class TypeNodeResolver private Container $container; - private bool $deepInspectTypes; - public function __construct( TypeNodeResolverExtensionRegistryProvider $extensionRegistryProvider, - Container $container, - bool $deepInspectTypes = false + Container $container ) { $this->extensionRegistryProvider = $extensionRegistryProvider; $this->container = $container; - $this->deepInspectTypes = $deepInspectTypes; } /** @api */ @@ -445,13 +441,10 @@ private function resolveGenericTypeNode(GenericTypeNode $typeNode, NameScope $na if (count($genericTypes) === 1) { // array $arrayType = new ArrayType(new MixedType(true), $genericTypes[0]); } elseif (count($genericTypes) === 2) { // array - $keyType = $genericTypes[0]; - if ($this->deepInspectTypes) { - $keyType = TypeCombinator::intersect($keyType, new UnionType([ - new IntegerType(), - new StringType(), - ])); - } + $keyType = TypeCombinator::intersect($genericTypes[0], new UnionType([ + new IntegerType(), + new StringType(), + ])); $arrayType = new ArrayType($keyType, $genericTypes[1]); } else { return new ErrorType(); diff --git a/src/Rules/MissingTypehintCheck.php b/src/Rules/MissingTypehintCheck.php index 9d72badbc0..9d8ac31dd9 100644 --- a/src/Rules/MissingTypehintCheck.php +++ b/src/Rules/MissingTypehintCheck.php @@ -40,8 +40,6 @@ class MissingTypehintCheck /** @var string[] */ private array $skipCheckGenericClasses; - private bool $deepInspectTypes; - /** * @param string[] $skipCheckGenericClasses */ @@ -50,8 +48,7 @@ public function __construct( bool $checkMissingIterableValueType, bool $checkGenericClassInNonGenericObjectType, bool $checkMissingCallableSignature, - array $skipCheckGenericClasses = [], - bool $deepInspectTypes = false + array $skipCheckGenericClasses = [] ) { $this->reflectionProvider = $reflectionProvider; @@ -59,7 +56,6 @@ public function __construct( $this->checkGenericClassInNonGenericObjectType = $checkGenericClassInNonGenericObjectType; $this->checkMissingCallableSignature = $checkMissingCallableSignature; $this->skipCheckGenericClasses = $skipCheckGenericClasses; - $this->deepInspectTypes = $deepInspectTypes; } /** @@ -95,7 +91,7 @@ public function getIterableTypesWithMissingValueTypehint(Type $type): array } $iterablesWithMissingValueTypehint[] = $type; } - if ($this->deepInspectTypes && !$type instanceof IntersectionType) { + if (!$type instanceof IntersectionType) { return $traverse($type); } diff --git a/src/Rules/PhpDoc/UnresolvableTypeHelper.php b/src/Rules/PhpDoc/UnresolvableTypeHelper.php index 36c58b7dbd..8b53af21d5 100644 --- a/src/Rules/PhpDoc/UnresolvableTypeHelper.php +++ b/src/Rules/PhpDoc/UnresolvableTypeHelper.php @@ -10,38 +10,23 @@ class UnresolvableTypeHelper { - private bool $deepInspectTypes; - - public function __construct(bool $deepInspectTypes) - { - $this->deepInspectTypes = $deepInspectTypes; - } - public function containsUnresolvableType(Type $type): bool { - if ($this->deepInspectTypes) { - $containsUnresolvable = false; - TypeTraverser::map($type, static function (Type $type, callable $traverse) use (&$containsUnresolvable): Type { - if ($type instanceof ErrorType) { - $containsUnresolvable = true; - return $type; - } - if ($type instanceof NeverType && !$type->isExplicit()) { - $containsUnresolvable = true; - return $type; - } - - return $traverse($type); - }); - - return $containsUnresolvable; - } - - if ($type instanceof ErrorType) { - return true; - } - - return $type instanceof NeverType && !$type->isExplicit(); + $containsUnresolvable = false; + TypeTraverser::map($type, static function (Type $type, callable $traverse) use (&$containsUnresolvable): Type { + if ($type instanceof ErrorType) { + $containsUnresolvable = true; + return $type; + } + if ($type instanceof NeverType && !$type->isExplicit()) { + $containsUnresolvable = true; + return $type; + } + + return $traverse($type); + }); + + return $containsUnresolvable; } } diff --git a/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php b/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php index 8687ce1aa1..64cb2a425a 100644 --- a/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php @@ -28,7 +28,7 @@ protected function getRule(): Rule new RuleLevelHelper($reflectionProvider, true, false, true), new NullsafeCheck(), new PhpVersion(80000), - new UnresolvableTypeHelper(true), + new UnresolvableTypeHelper(), true, true, true, diff --git a/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php b/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php index 84e7e2bede..124770a220 100644 --- a/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php @@ -28,7 +28,7 @@ protected function getRule(): Rule new RuleLevelHelper($reflectionProvider, true, false, true), new NullsafeCheck(), new PhpVersion(80000), - new UnresolvableTypeHelper(true), + new UnresolvableTypeHelper(), true, true, true, diff --git a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php index da6f13eb7d..af33293ae9 100644 --- a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php +++ b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php @@ -20,7 +20,7 @@ protected function getRule(): \PHPStan\Rules\Rule $broker = $this->createReflectionProvider(); return new InstantiationRule( $broker, - new FunctionCallParametersCheck(new RuleLevelHelper($broker, true, false, true, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(true), true, true, true, true, true), + new FunctionCallParametersCheck(new RuleLevelHelper($broker, true, false, true, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), true, true, true, true, true), new ClassCaseSensitivityCheck($broker) ); } diff --git a/tests/PHPStan/Rules/Classes/MixinRuleTest.php b/tests/PHPStan/Rules/Classes/MixinRuleTest.php index 2c214fa617..c94714b072 100644 --- a/tests/PHPStan/Rules/Classes/MixinRuleTest.php +++ b/tests/PHPStan/Rules/Classes/MixinRuleTest.php @@ -26,7 +26,7 @@ protected function getRule(): Rule new ClassCaseSensitivityCheck($reflectionProvider), new GenericObjectTypeCheck(), new MissingTypehintCheck($reflectionProvider, true, true, true), - new UnresolvableTypeHelper(true), + new UnresolvableTypeHelper(), true ); } diff --git a/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php index 63c9647b55..9331bf4ba0 100644 --- a/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php @@ -28,7 +28,7 @@ protected function getRule(): Rule new RuleLevelHelper($reflectionProvider, true, false, true), new NullsafeCheck(), new PhpVersion(80000), - new UnresolvableTypeHelper(true), + new UnresolvableTypeHelper(), true, true, true, diff --git a/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php b/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php index 9ec1a0f40a..3fb303f6d9 100644 --- a/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php @@ -25,7 +25,7 @@ protected function getRule(): \PHPStan\Rules\Rule $ruleLevelHelper, new NullsafeCheck(), new PhpVersion(80000), - new UnresolvableTypeHelper(true), + new UnresolvableTypeHelper(), true, true, true, diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index cfdaaad4ba..04b005e767 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -23,7 +23,7 @@ protected function getRule(): \PHPStan\Rules\Rule $broker = $this->createReflectionProvider(); return new CallToFunctionParametersRule( $broker, - new FunctionCallParametersCheck(new RuleLevelHelper($broker, true, false, true, $this->checkExplicitMixed), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(true), true, true, true, true, true) + new FunctionCallParametersCheck(new RuleLevelHelper($broker, true, false, true, $this->checkExplicitMixed), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), true, true, true, true, true) ); } diff --git a/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php index eed51d56d6..b4d9b5f166 100644 --- a/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php @@ -28,7 +28,7 @@ protected function getRule(): Rule new RuleLevelHelper($reflectionProvider, true, false, true), new NullsafeCheck(), new PhpVersion(80000), - new UnresolvableTypeHelper(true), + new UnresolvableTypeHelper(), true, true, true, diff --git a/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php index 0833136a92..32e84bf76b 100644 --- a/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php @@ -28,7 +28,7 @@ protected function getRule(): Rule new RuleLevelHelper($reflectionProvider, true, false, true), new NullsafeCheck(), new PhpVersion(80000), - new UnresolvableTypeHelper(true), + new UnresolvableTypeHelper(), true, true, true, diff --git a/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php index 648835f7a1..aa63e32e66 100644 --- a/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php @@ -28,7 +28,7 @@ protected function getRule(): Rule new RuleLevelHelper($reflectionProvider, true, false, true), new NullsafeCheck(), new PhpVersion(80000), - new UnresolvableTypeHelper(true), + new UnresolvableTypeHelper(), true, true, true, diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 00e90c1725..cf22f369c9 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -40,7 +40,7 @@ protected function getRule(): Rule $ruleLevelHelper = new RuleLevelHelper($broker, $this->checkNullables, $this->checkThisOnly, $this->checkUnionTypes, $this->checkExplicitMixed); return new CallMethodsRule( $broker, - new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new PhpVersion($this->phpVersion), new UnresolvableTypeHelper(true), true, true, true, true, $this->checkNeverInGenericReturnType), + new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new PhpVersion($this->phpVersion), new UnresolvableTypeHelper(), true, true, true, true, $this->checkNeverInGenericReturnType), $ruleLevelHelper, true, true diff --git a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php index bcda414fe4..a07b81786f 100644 --- a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php @@ -24,7 +24,7 @@ protected function getRule(): \PHPStan\Rules\Rule $ruleLevelHelper = new RuleLevelHelper($broker, true, $this->checkThisOnly, true, false); return new CallStaticMethodsRule( $broker, - new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(true), true, true, true, true, true), + new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), true, true, true, true, true), $ruleLevelHelper, new ClassCaseSensitivityCheck($broker), true, diff --git a/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php b/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php index 4639910662..52c329544f 100644 --- a/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php @@ -28,7 +28,7 @@ protected function getRule(): Rule new RuleLevelHelper($reflectionProvider, true, false, true), new NullsafeCheck(), new PhpVersion(80000), - new UnresolvableTypeHelper(true), + new UnresolvableTypeHelper(), true, true, true, diff --git a/tests/PHPStan/Rules/Methods/MissingMethodParameterTypehintRuleTest.php b/tests/PHPStan/Rules/Methods/MissingMethodParameterTypehintRuleTest.php index e940975ddc..f8ec513525 100644 --- a/tests/PHPStan/Rules/Methods/MissingMethodParameterTypehintRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MissingMethodParameterTypehintRuleTest.php @@ -10,13 +10,10 @@ class MissingMethodParameterTypehintRuleTest extends \PHPStan\Testing\RuleTestCase { - /** @var bool */ - private $deepInspectTypes = false; - protected function getRule(): \PHPStan\Rules\Rule { $broker = $this->createReflectionProvider(); - return new MissingMethodParameterTypehintRule(new MissingTypehintCheck($broker, true, true, true, [], $this->deepInspectTypes)); + return new MissingMethodParameterTypehintRule(new MissingTypehintCheck($broker, true, true, true, [])); } public function testRule(): void @@ -95,20 +92,8 @@ public function testPromotedProperties(): void ]); } - public function testDoNotDeepInspectTypes(): void - { - $this->analyse([__DIR__ . '/data/deep-inspect-types.php'], [ - [ - 'Method DeepInspectTypes\Foo::doBar() has parameter $bars with generic class DeepInspectTypes\Bar but does not specify its types: T', - 17, - MissingTypehintCheck::TURN_OFF_NON_GENERIC_CHECK_TIP, - ], - ]); - } - public function testDeepInspectTypes(): void { - $this->deepInspectTypes = true; $this->analyse([__DIR__ . '/data/deep-inspect-types.php'], [ [ 'Method DeepInspectTypes\Foo::doFoo() has parameter $foo with no value type specified in iterable type iterable.', @@ -125,7 +110,6 @@ public function testDeepInspectTypes(): void public function testBug3723(): void { - $this->deepInspectTypes = false; $this->analyse([__DIR__ . '/data/bug-3723.php'], []); } diff --git a/tests/PHPStan/Rules/PhpDoc/IncompatibleClassConstantPhpDocTypeRuleTest.php b/tests/PHPStan/Rules/PhpDoc/IncompatibleClassConstantPhpDocTypeRuleTest.php index f2d9d347f5..71e78e7ef6 100644 --- a/tests/PHPStan/Rules/PhpDoc/IncompatibleClassConstantPhpDocTypeRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/IncompatibleClassConstantPhpDocTypeRuleTest.php @@ -14,7 +14,7 @@ class IncompatibleClassConstantPhpDocTypeRuleTest extends RuleTestCase protected function getRule(): Rule { - return new IncompatibleClassConstantPhpDocTypeRule(new GenericObjectTypeCheck(), new UnresolvableTypeHelper(true)); + return new IncompatibleClassConstantPhpDocTypeRule(new GenericObjectTypeCheck(), new UnresolvableTypeHelper()); } public function testRule(): void diff --git a/tests/PHPStan/Rules/PhpDoc/IncompatiblePhpDocTypeRuleTest.php b/tests/PHPStan/Rules/PhpDoc/IncompatiblePhpDocTypeRuleTest.php index cfdf2131fc..163f826ac9 100644 --- a/tests/PHPStan/Rules/PhpDoc/IncompatiblePhpDocTypeRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/IncompatiblePhpDocTypeRuleTest.php @@ -11,15 +11,12 @@ class IncompatiblePhpDocTypeRuleTest extends \PHPStan\Testing\RuleTestCase { - /** @var bool */ - private $deepInspectTypes = false; - protected function getRule(): \PHPStan\Rules\Rule { return new IncompatiblePhpDocTypeRule( self::getContainer()->getByType(FileTypeMapper::class), new GenericObjectTypeCheck(), - new UnresolvableTypeHelper($this->deepInspectTypes) + new UnresolvableTypeHelper() ); } @@ -156,7 +153,6 @@ public function testBug4643(): void public function testBug3753(): void { - $this->deepInspectTypes = true; $this->analyse([__DIR__ . '/data/bug-3753.php'], [ [ 'PHPDoc tag @param for parameter $foo contains unresolvable type.', @@ -169,9 +165,4 @@ public function testBug3753(): void ]); } - public function testBug3753NotDeepInspectTypes(): void - { - $this->analyse([__DIR__ . '/data/bug-3753.php'], []); - } - } diff --git a/tests/PHPStan/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRuleTest.php b/tests/PHPStan/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRuleTest.php index 42ee5eee86..164c03193e 100644 --- a/tests/PHPStan/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRuleTest.php @@ -13,7 +13,7 @@ class IncompatiblePropertyPhpDocTypeRuleTest extends \PHPStan\Testing\RuleTestCa protected function getRule(): Rule { - return new IncompatiblePropertyPhpDocTypeRule(new GenericObjectTypeCheck(), new UnresolvableTypeHelper(true)); + return new IncompatiblePropertyPhpDocTypeRule(new GenericObjectTypeCheck(), new UnresolvableTypeHelper()); } public function testRule(): void diff --git a/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php b/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php index 1512dc6309..cdeb22e447 100644 --- a/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php @@ -24,7 +24,7 @@ protected function getRule(): Rule new ClassCaseSensitivityCheck($broker), new GenericObjectTypeCheck(), new MissingTypehintCheck($broker, true, true, true), - new UnresolvableTypeHelper(true), + new UnresolvableTypeHelper(), true, true ); diff --git a/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php b/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php index ea787b22d9..f4e02a304c 100644 --- a/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php @@ -28,7 +28,7 @@ protected function getRule(): Rule new RuleLevelHelper($reflectionProvider, true, false, true), new NullsafeCheck(), new PhpVersion(80000), - new UnresolvableTypeHelper(true), + new UnresolvableTypeHelper(), true, true, true, From d7b7f74dc809f86eadd025e5b17b9d32014dcffe Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 17 Sep 2021 14:37:16 +0200 Subject: [PATCH 0339/1284] New empty() and null-coalesce (??) rules --- conf/bleedingEdge.neon | 1 - conf/config.level1.neon | 21 +++------------------ conf/config.neon | 2 -- 3 files changed, 3 insertions(+), 21 deletions(-) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index b142aeb162..635fe72cf8 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -2,7 +2,6 @@ parameters: featureToggles: bleedingEdge: true randomIntParameters: true - nullCoalesce: true fileWhitespace: true unusedClassElements: true readComposerPhpVersion: true diff --git a/conf/config.level1.neon b/conf/config.level1.neon index 05cb8715b9..3b5f68d64a 100644 --- a/conf/config.level1.neon +++ b/conf/config.level1.neon @@ -11,21 +11,6 @@ rules: - PHPStan\Rules\Classes\UnusedConstructorParametersRule - PHPStan\Rules\Constants\ConstantRule - PHPStan\Rules\Functions\UnusedClosureUsesRule - -conditionalTags: - PHPStan\Rules\Variables\EmptyRule: - phpstan.rules.rule: %featureToggles.nullCoalesce% - PHPStan\Rules\Variables\IssetRule: - phpstan.rules.rule: %featureToggles.nullCoalesce% - PHPStan\Rules\Variables\NullCoalesceRule: - phpstan.rules.rule: %featureToggles.nullCoalesce% - -services: - - - class: PHPStan\Rules\Variables\EmptyRule - - - - class: PHPStan\Rules\Variables\IssetRule - - - - class: PHPStan\Rules\Variables\NullCoalesceRule + - PHPStan\Rules\Variables\EmptyRule + - PHPStan\Rules\Variables\IssetRule + - PHPStan\Rules\Variables\NullCoalesceRule diff --git a/conf/config.neon b/conf/config.neon index cc994f136b..3131254362 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -22,7 +22,6 @@ parameters: bleedingEdge: false disableRuntimeReflectionProvider: false randomIntParameters: false - nullCoalesce: false fileWhitespace: false unusedClassElements: false readComposerPhpVersion: false @@ -196,7 +195,6 @@ parametersSchema: bleedingEdge: bool(), disableRuntimeReflectionProvider: bool(), randomIntParameters: bool(), - nullCoalesce: bool(), fileWhitespace: bool(), unusedClassElements: bool(), readComposerPhpVersion: bool(), From 88209d4906d885c8ac6de5054827dd0608cc132a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 17 Sep 2021 14:40:54 +0200 Subject: [PATCH 0340/1284] Fix --- .../Rules/Methods/MissingMethodReturnTypehintRuleTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php b/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php index ced521e97f..2216c37618 100644 --- a/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MissingMethodReturnTypehintRuleTest.php @@ -13,7 +13,7 @@ class MissingMethodReturnTypehintRuleTest extends \PHPStan\Testing\RuleTestCase protected function getRule(): \PHPStan\Rules\Rule { $broker = $this->createReflectionProvider(); - return new MissingMethodReturnTypehintRule(new MissingTypehintCheck($broker, true, true, true, [], true)); + return new MissingMethodReturnTypehintRule(new MissingTypehintCheck($broker, true, true, true, [])); } public function testRule(): void From 116d1ec59fce8e53add24ce94756df56c9c5c63f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 17 Sep 2021 14:43:49 +0200 Subject: [PATCH 0341/1284] Find unused private constants, methods, and properties --- conf/bleedingEdge.neon | 1 - conf/config.level4.neon | 18 ++++-------------- conf/config.neon | 2 -- 3 files changed, 4 insertions(+), 17 deletions(-) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 635fe72cf8..8171700845 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -3,7 +3,6 @@ parameters: bleedingEdge: true randomIntParameters: true fileWhitespace: true - unusedClassElements: true readComposerPhpVersion: true dateTimeInstantiation: true checkLogicalAndConstantCondition: true diff --git a/conf/config.level4.neon b/conf/config.level4.neon index ef6b167142..60b726545d 100644 --- a/conf/config.level4.neon +++ b/conf/config.level4.neon @@ -6,6 +6,8 @@ rules: - PHPStan\Rules\Comparison\NumberComparisonOperatorsConstantConditionRule - PHPStan\Rules\DeadCode\NoopRule - PHPStan\Rules\DeadCode\UnreachableStatementRule + - PHPStan\Rules\DeadCode\UnusedPrivateConstantRule + - PHPStan\Rules\DeadCode\UnusedPrivateMethodRule - PHPStan\Rules\Exceptions\CatchWithUnthrownExceptionRule - PHPStan\Rules\Exceptions\OverwrittenExitPointByFinallyRule - PHPStan\Rules\Functions\CallToFunctionStatementWithoutSideEffectsRule @@ -18,14 +20,6 @@ rules: - PHPStan\Rules\TooWideTypehints\TooWideClosureReturnTypehintRule - PHPStan\Rules\TooWideTypehints\TooWideFunctionReturnTypehintRule -conditionalTags: - PHPStan\Rules\DeadCode\UnusedPrivateConstantRule: - phpstan.rules.rule: %featureToggles.unusedClassElements% - PHPStan\Rules\DeadCode\UnusedPrivateMethodRule: - phpstan.rules.rule: %featureToggles.unusedClassElements% - PHPStan\Rules\DeadCode\UnusedPrivatePropertyRule: - phpstan.rules.rule: %featureToggles.unusedClassElements% - parameters: checkAdvancedIsset: true @@ -61,18 +55,14 @@ services: tags: - phpstan.rules.rule - - - class: PHPStan\Rules\DeadCode\UnusedPrivateConstantRule - - - - class: PHPStan\Rules\DeadCode\UnusedPrivateMethodRule - - class: PHPStan\Rules\DeadCode\UnusedPrivatePropertyRule arguments: alwaysWrittenTags: %propertyAlwaysWrittenTags% alwaysReadTags: %propertyAlwaysReadTags% checkUninitializedProperties: %checkUninitializedProperties% + tags: + - phpstan.rules.rule - class: PHPStan\Rules\Comparison\ElseIfConstantConditionRule diff --git a/conf/config.neon b/conf/config.neon index 3131254362..c2aecae121 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -23,7 +23,6 @@ parameters: disableRuntimeReflectionProvider: false randomIntParameters: false fileWhitespace: false - unusedClassElements: false readComposerPhpVersion: false dateTimeInstantiation: false checkLogicalAndConstantCondition: false @@ -196,7 +195,6 @@ parametersSchema: disableRuntimeReflectionProvider: bool(), randomIntParameters: bool(), fileWhitespace: bool(), - unusedClassElements: bool(), readComposerPhpVersion: bool(), dateTimeInstantiation: bool(), checkLogicalAndConstantCondition: bool(), From 1e717d73c22061a2b275fb73fd27c704588b2be1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 17 Sep 2021 16:00:51 +0200 Subject: [PATCH 0342/1284] Updated BetterReflection with trait fixes --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 1036569f86..39f9fbea60 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "nette/utils": "^3.1.3", "nikic/php-parser": "dev-master as 4.12.0", "ondram/ci-detector": "^3.4.0", - "ondrejmirtes/better-reflection": "4.3.68", + "ondrejmirtes/better-reflection": "4.3.69", "phpstan/php-8-stubs": "^0.1.23", "phpstan/phpdoc-parser": "^1.2.0", "react/child-process": "^0.6.1", diff --git a/composer.lock b/composer.lock index 6470d9cfc4..4130445744 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "8b64858812516ba2637d2eceabc88a26", + "content-hash": "7b2e806b9d571e647d209a79eb6fe8c0", "packages": [ { "name": "clue/block-react", @@ -2075,16 +2075,16 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "4.3.68", + "version": "4.3.69", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "a90c6bca67001b61346f47f143bb9721a8ef88d4" + "reference": "370878a62e8ed064843fa2f91f896189cb5e9704" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/a90c6bca67001b61346f47f143bb9721a8ef88d4", - "reference": "a90c6bca67001b61346f47f143bb9721a8ef88d4", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/370878a62e8ed064843fa2f91f896189cb5e9704", + "reference": "370878a62e8ed064843fa2f91f896189cb5e9704", "shasum": "" }, "require": { @@ -2139,9 +2139,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/4.3.68" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/4.3.69" }, - "time": "2021-09-15T20:00:14+00:00" + "time": "2021-09-17T13:57:28+00:00" }, { "name": "phpstan/php-8-stubs", From 11c888ca19c9d2a592b560b0c37fe32888b8d48b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Pudil?= Date: Mon, 20 Sep 2021 10:41:05 +0200 Subject: [PATCH 0343/1284] Distinguish circular type aliases from invalid type definitions --- src/Rules/Classes/LocalTypeAliasesRule.php | 9 ++++++++- src/Type/CircularTypeAliasErrorType.php | 9 +++++++++ src/Type/TypeAlias.php | 2 +- src/Type/TypeAliasResolver.php | 2 +- tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php | 4 ++++ tests/PHPStan/Rules/Classes/data/local-type-aliases.php | 7 +++++++ 6 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 src/Type/CircularTypeAliasErrorType.php diff --git a/src/Rules/Classes/LocalTypeAliasesRule.php b/src/Rules/Classes/LocalTypeAliasesRule.php index 9773b79dc6..51c69d37c8 100644 --- a/src/Rules/Classes/LocalTypeAliasesRule.php +++ b/src/Rules/Classes/LocalTypeAliasesRule.php @@ -11,6 +11,7 @@ use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; +use PHPStan\Type\CircularTypeAliasErrorType; use PHPStan\Type\ErrorType; use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\ObjectType; @@ -137,12 +138,18 @@ public function processNode(Node $node, Scope $scope): array return $type; } - if ($type instanceof ErrorType) { + if ($type instanceof CircularTypeAliasErrorType) { $errors[] = RuleErrorBuilder::message(sprintf('Circular definition detected in type alias %s.', $aliasName))->build(); $foundError = true; return $type; } + if ($type instanceof ErrorType) { + $errors[] = RuleErrorBuilder::message(sprintf('Invalid type definition detected in type alias %s.', $aliasName))->build(); + $foundError = true; + return $type; + } + return $traverse($type); }); } diff --git a/src/Type/CircularTypeAliasErrorType.php b/src/Type/CircularTypeAliasErrorType.php new file mode 100644 index 0000000000..929d3c6aec --- /dev/null +++ b/src/Type/CircularTypeAliasErrorType.php @@ -0,0 +1,9 @@ +resolvedType = new ErrorType(); + $self->resolvedType = new CircularTypeAliasErrorType(); return $self; } diff --git a/src/Type/TypeAliasResolver.php b/src/Type/TypeAliasResolver.php index 1ce2e8f215..3373f8d0b8 100644 --- a/src/Type/TypeAliasResolver.php +++ b/src/Type/TypeAliasResolver.php @@ -123,7 +123,7 @@ private function resolveLocalTypeAlias(string $aliasName, NameScope $nameScope): $unresolvedAlias = $localTypeAliases[$aliasName]; $resolvedAliasType = $unresolvedAlias->resolve($this->typeNodeResolver); } catch (\PHPStan\Type\CircularTypeAliasDefinitionException $e) { - $resolvedAliasType = new ErrorType(); + $resolvedAliasType = new CircularTypeAliasErrorType(); } $this->resolvedLocalTypeAliases[$aliasNameInClassScope] = $resolvedAliasType; diff --git a/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php b/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php index 11262e5187..6353bb0e57 100644 --- a/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php @@ -84,6 +84,10 @@ public function testRule(): void 'Circular definition detected in type alias CircularTypeAliasImport1.', 47, ], + [ + 'Invalid type definition detected in type alias InvalidTypeAlias.', + 62, + ], ]); } diff --git a/tests/PHPStan/Rules/Classes/data/local-type-aliases.php b/tests/PHPStan/Rules/Classes/data/local-type-aliases.php index 316a52c323..44ed116a70 100644 --- a/tests/PHPStan/Rules/Classes/data/local-type-aliases.php +++ b/tests/PHPStan/Rules/Classes/data/local-type-aliases.php @@ -55,3 +55,10 @@ class Qux class Generic { } + +/** + * @phpstan-type InvalidTypeAlias invalid-type-definition + */ +class Invalid +{ +} From 0837be403f590f857a9ba117fbe236e4ce7dcc99 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 20 Sep 2021 10:42:26 +0200 Subject: [PATCH 0344/1284] Process do-while loop condition even if the loop always terminates --- src/Analyser/NodeScopeResolver.php | 2 ++ .../DeadCode/data/unused-private-constant.php | 22 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index e900b481c1..c6a83edc66 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -983,6 +983,8 @@ private function processStmtNode( $hasYield = $condResult->hasYield(); $throwPoints = $condResult->getThrowPoints(); $finalScope = $condResult->getFalseyScope(); + } else { + $this->processExprNode($stmt->cond, $bodyScope, $nodeCallback, ExpressionContext::createDeep()); } foreach ($bodyScopeResult->getExitPointsByType(Break_::class) as $breakExitPoint) { $finalScope = $breakExitPoint->getScope()->mergeWith($finalScope); diff --git a/tests/PHPStan/Rules/DeadCode/data/unused-private-constant.php b/tests/PHPStan/Rules/DeadCode/data/unused-private-constant.php index ca5e53fba9..88531598b2 100644 --- a/tests/PHPStan/Rules/DeadCode/data/unused-private-constant.php +++ b/tests/PHPStan/Rules/DeadCode/data/unused-private-constant.php @@ -22,3 +22,25 @@ class TestExtension private const UNUSED = 2; } + + +final class P +{ + + private const JSON_OBJECT_START = 17; + private const JSON_OBJECT_END = 18; + + public function ignoreObjectBlock(): void + { + do { + $code = doFoo(); + + // recursively ignore nested objects + if ($code !== self::JSON_OBJECT_START) { + continue; + } + + $this->ignoreObjectBlock(); + } while ($code !== self::JSON_OBJECT_END); + } +} From 0cfeb1b93021e293481e3071196b1d360dc04962 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 20 Sep 2021 10:56:33 +0200 Subject: [PATCH 0345/1284] Process attribute arguments recursively --- src/Analyser/NodeScopeResolver.php | 12 ++++---- .../UnusedPrivateConstantRuleTest.php | 9 ++++++ .../PHPStan/Rules/DeadCode/data/bug-5651.php | 30 +++++++++++++++++++ 3 files changed, 45 insertions(+), 6 deletions(-) create mode 100644 tests/PHPStan/Rules/DeadCode/data/bug-5651.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index c6a83edc66..89187d27ac 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -408,7 +408,7 @@ private function processStmtNode( foreach ($stmt->attrGroups as $attrGroup) { foreach ($attrGroup->attrs as $attr) { foreach ($attr->args as $arg) { - $nodeCallback($arg->value, $scope); + $this->processExprNode($arg->value, $scope, $nodeCallback, ExpressionContext::createDeep()); } } } @@ -469,7 +469,7 @@ private function processStmtNode( foreach ($stmt->attrGroups as $attrGroup) { foreach ($attrGroup->attrs as $attr) { foreach ($attr->args as $arg) { - $nodeCallback($arg->value, $scope); + $this->processExprNode($arg->value, $scope, $nodeCallback, ExpressionContext::createDeep()); } } } @@ -644,7 +644,7 @@ private function processStmtNode( foreach ($stmt->attrGroups as $attrGroup) { foreach ($attrGroup->attrs as $attr) { foreach ($attr->args as $arg) { - $nodeCallback($arg->value, $classScope); + $this->processExprNode($arg->value, $classScope, $nodeCallback, ExpressionContext::createDeep()); } } } @@ -660,7 +660,7 @@ private function processStmtNode( foreach ($stmt->attrGroups as $attrGroup) { foreach ($attrGroup->attrs as $attr) { foreach ($attr->args as $arg) { - $nodeCallback($arg->value, $scope); + $this->processExprNode($arg->value, $scope, $nodeCallback, ExpressionContext::createDeep()); } } } @@ -1378,7 +1378,7 @@ private function processStmtNode( foreach ($stmt->attrGroups as $attrGroup) { foreach ($attrGroup->attrs as $attr) { foreach ($attr->args as $arg) { - $nodeCallback($arg->value, $scope); + $this->processExprNode($arg->value, $scope, $nodeCallback, ExpressionContext::createDeep()); } } } @@ -3068,7 +3068,7 @@ private function processParamNode( foreach ($param->attrGroups as $attrGroup) { foreach ($attrGroup->attrs as $attr) { foreach ($attr->args as $arg) { - $nodeCallback($arg->value, $scope); + $this->processExprNode($arg->value, $scope, $nodeCallback, ExpressionContext::createDeep()); } } } diff --git a/tests/PHPStan/Rules/DeadCode/UnusedPrivateConstantRuleTest.php b/tests/PHPStan/Rules/DeadCode/UnusedPrivateConstantRuleTest.php index a6e12e7cab..305c37cf13 100644 --- a/tests/PHPStan/Rules/DeadCode/UnusedPrivateConstantRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/UnusedPrivateConstantRuleTest.php @@ -46,4 +46,13 @@ public function testRule(): void ]); } + public function testBug5651(): void + { + if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->analyse([__DIR__ . '/data/bug-5651.php'], []); + } + } diff --git a/tests/PHPStan/Rules/DeadCode/data/bug-5651.php b/tests/PHPStan/Rules/DeadCode/data/bug-5651.php new file mode 100644 index 0000000000..88068fd018 --- /dev/null +++ b/tests/PHPStan/Rules/DeadCode/data/bug-5651.php @@ -0,0 +1,30 @@ +values = $values; + } +} + +class HelloWorld +{ + private const BAR = 'bar'; + + #[MyAttribute(['foo' => self::BAR])] + public function sayHello(): void + { + + } +} From 9980d21724a3037c3353c7947d49a22f0d0d8e0f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 20 Sep 2021 11:10:38 +0200 Subject: [PATCH 0346/1284] Fixes --- .../Analyser/AnalyserIntegrationTest.php | 6 +-- .../ExtendsClassWithUnknownPropertyType.php | 5 +++ tests/PHPStan/Analyser/data/bug-4734.php | 16 ++++++++ .../Analyser/data/nested-namespaces.php | 16 ++++++++ ...ty-assign-intersection-static-type-bug.php | 8 ++++ .../Analyser/data/two-same-classes.php | 40 +++++++++++++++++++ 6 files changed, 88 insertions(+), 3 deletions(-) diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 0583d9c9b1..1a7bebb039 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -233,15 +233,15 @@ public function testTwoSameClassesInSingleFile(): void $error = $errors[2]; $this->assertSame('If condition is always false.', $error->getMessage()); - $this->assertSame(18, $error->getLine()); + $this->assertSame(26, $error->getLine()); $error = $errors[3]; $this->assertSame('Property TwoSame\Foo::$prop (int) does not accept default value of type string.', $error->getMessage()); - $this->assertSame(25, $error->getLine()); + $this->assertSame(33, $error->getLine()); $error = $errors[4]; $this->assertSame('Property TwoSame\Foo::$prop2 (int) does not accept default value of type string.', $error->getMessage()); - $this->assertSame(28, $error->getLine()); + $this->assertSame(36, $error->getLine()); } public function testBug3405(): void diff --git a/tests/PHPStan/Analyser/data/ExtendsClassWithUnknownPropertyType.php b/tests/PHPStan/Analyser/data/ExtendsClassWithUnknownPropertyType.php index 6144a76d4c..18658ab9a0 100644 --- a/tests/PHPStan/Analyser/data/ExtendsClassWithUnknownPropertyType.php +++ b/tests/PHPStan/Analyser/data/ExtendsClassWithUnknownPropertyType.php @@ -11,4 +11,9 @@ public function doFoo(): void $this->foo->foo(); } + public function setFoo(self $foo): void + { + $this->foo = $foo; + } + } diff --git a/tests/PHPStan/Analyser/data/bug-4734.php b/tests/PHPStan/Analyser/data/bug-4734.php index 6b231e0f9b..8f7d6af714 100644 --- a/tests/PHPStan/Analyser/data/bug-4734.php +++ b/tests/PHPStan/Analyser/data/bug-4734.php @@ -15,6 +15,22 @@ class Foo * @var bool */ private $httpMethodParameterOverride2 = true; + + /** + * @return bool + */ + public static function isHttpMethodParameterOverride(): bool + { + return self::$httpMethodParameterOverride; + } + + /** + * @return bool + */ + public function isHttpMethodParameterOverride2(): bool + { + return $this->httpMethodParameterOverride2; + } } class Bar diff --git a/tests/PHPStan/Analyser/data/nested-namespaces.php b/tests/PHPStan/Analyser/data/nested-namespaces.php index 0f8bb769f4..ff8818d32c 100644 --- a/tests/PHPStan/Analyser/data/nested-namespaces.php +++ b/tests/PHPStan/Analyser/data/nested-namespaces.php @@ -17,5 +17,21 @@ public function __construct(boo $boo, baz $baz) { $this->boo = $boo; $this->baz = $baz; } + + /** + * @return boo + */ + public function getBoo(): boo + { + return $this->boo; + } + + /** + * @return mixed + */ + public function getBaz() + { + return $this->baz; + } } } diff --git a/tests/PHPStan/Analyser/data/property-assign-intersection-static-type-bug.php b/tests/PHPStan/Analyser/data/property-assign-intersection-static-type-bug.php index 7b66441340..ff67a805f3 100644 --- a/tests/PHPStan/Analyser/data/property-assign-intersection-static-type-bug.php +++ b/tests/PHPStan/Analyser/data/property-assign-intersection-static-type-bug.php @@ -13,6 +13,14 @@ public function __construct(string $foo) $this->foo = $foo; } + + /** + * @return string + */ + public function getFoo(): string + { + return $this->foo; + } } class Frontend extends Base diff --git a/tests/PHPStan/Analyser/data/two-same-classes.php b/tests/PHPStan/Analyser/data/two-same-classes.php index a5cf574043..872edbed82 100644 --- a/tests/PHPStan/Analyser/data/two-same-classes.php +++ b/tests/PHPStan/Analyser/data/two-same-classes.php @@ -13,6 +13,14 @@ public function doFoo(): void echo self::FOO_CONST; } + /** + * @return string + */ + public function getProp() + { + return $this->prop; + } + } if (rand(0, 0)) { @@ -32,5 +40,37 @@ public function doFoo(): void echo self::FOO_CONST; } + /** + * @return int + */ + public function getProp() + { + return $this->prop; + } + + /** + * @param int $prop + */ + public function setProp($prop): void + { + $this->prop = $prop; + } + + /** + * @return int + */ + public function getProp2() + { + return $this->prop2; + } + + /** + * @param int $prop2 + */ + public function setProp2($prop2): void + { + $this->prop2 = $prop2; + } + } } From 9e15783633b2efe1e96304cdbcb6a99fc1141645 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 20 Sep 2021 11:31:19 +0200 Subject: [PATCH 0347/1284] Regression tests Closes https://github.com/phpstan/phpstan/issues/4886 Closes https://github.com/phpstan/phpstan/issues/4211 Closes https://github.com/phpstan/phpstan/issues/3514 Closes https://github.com/phpstan/phpstan/issues/3465 --- .../Rules/Methods/CallMethodsRuleTest.php | 24 ++++++++++++++ .../Methods/CallStaticMethodsRuleTest.php | 6 ++++ tests/PHPStan/Rules/Methods/data/bug-3465.php | 20 ++++++++++++ tests/PHPStan/Rules/Methods/data/bug-3514.php | 21 +++++++++++++ tests/PHPStan/Rules/Methods/data/bug-4211.php | 28 +++++++++++++++++ tests/PHPStan/Rules/Methods/data/bug-4886.php | 31 +++++++++++++++++++ 6 files changed, 130 insertions(+) create mode 100644 tests/PHPStan/Rules/Methods/data/bug-3465.php create mode 100644 tests/PHPStan/Rules/Methods/data/bug-3514.php create mode 100644 tests/PHPStan/Rules/Methods/data/bug-4211.php create mode 100644 tests/PHPStan/Rules/Methods/data/bug-4886.php diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index cf22f369c9..2e3c6c9204 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -2181,4 +2181,28 @@ public function testBug5562(): void $this->analyse([__DIR__ . '/data/bug-5562.php'], []); } + public function testBug4211(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/bug-4211.php'], []); + } + + public function testBug3514(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/bug-3514.php'], []); + } + + public function testBug3465(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/bug-3465.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php index a07b81786f..e5943c2fbd 100644 --- a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php @@ -451,4 +451,10 @@ public function testBug5536(): void $this->analyse([__DIR__ . '/data/bug-5536.php'], []); } + public function testBug4886(): void + { + $this->checkThisOnly = false; + $this->analyse([__DIR__ . '/data/bug-4886.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-3465.php b/tests/PHPStan/Rules/Methods/data/bug-3465.php new file mode 100644 index 0000000000..96cc78936a --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-3465.php @@ -0,0 +1,20 @@ +setValue(); +}; diff --git a/tests/PHPStan/Rules/Methods/data/bug-3514.php b/tests/PHPStan/Rules/Methods/data/bug-3514.php new file mode 100644 index 0000000000..55a7156847 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-3514.php @@ -0,0 +1,21 @@ +myRenamedMethod(); + } +} diff --git a/tests/PHPStan/Rules/Methods/data/bug-4211.php b/tests/PHPStan/Rules/Methods/data/bug-4211.php new file mode 100644 index 0000000000..bb8c6270c1 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-4211.php @@ -0,0 +1,28 @@ +format('j. n. Y'); + } +} + +class HelloWorld +{ + use HelloWorldTraitTest, HelloWorldTrait { + sayHello as hello; + } + + public function sayHello(DateTimeImmutable $date): void { + $this->hello($date); + } +} diff --git a/tests/PHPStan/Rules/Methods/data/bug-4886.php b/tests/PHPStan/Rules/Methods/data/bug-4886.php new file mode 100644 index 0000000000..b29e43c9b0 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-4886.php @@ -0,0 +1,31 @@ + Date: Mon, 20 Sep 2021 11:49:42 +0200 Subject: [PATCH 0348/1284] Set the correct method visibility when entering the method in regard to trait adaptation --- src/Analyser/NodeScopeResolver.php | 38 ++++++++++++++++--- .../Methods/OverridingMethodRuleTest.php | 6 +++ tests/PHPStan/Rules/Methods/data/bug-4516.php | 20 ++++++++++ 3 files changed, 59 insertions(+), 5 deletions(-) create mode 100644 tests/PHPStan/Rules/Methods/data/bug-4516.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 89187d27ac..c95ef3eef7 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -3649,7 +3649,7 @@ private function processTraitUse(Node\Stmt\TraitUse $node, MutatingScope $classS continue; } $parserNodes = $this->parser->parseFile($fileName); - $this->processNodesForTraitUse($parserNodes, $traitReflection, $classScope, $nodeCallback); + $this->processNodesForTraitUse($parserNodes, $traitReflection, $classScope, $node->adaptations, $nodeCallback); } } @@ -3657,13 +3657,41 @@ private function processTraitUse(Node\Stmt\TraitUse $node, MutatingScope $classS * @param \PhpParser\Node[]|\PhpParser\Node|scalar $node * @param ClassReflection $traitReflection * @param \PHPStan\Analyser\MutatingScope $scope + * @param Node\Stmt\TraitUseAdaptation[] $adaptations * @param callable(\PhpParser\Node $node, Scope $scope): void $nodeCallback */ - private function processNodesForTraitUse($node, ClassReflection $traitReflection, MutatingScope $scope, callable $nodeCallback): void + private function processNodesForTraitUse($node, ClassReflection $traitReflection, MutatingScope $scope, array $adaptations, callable $nodeCallback): void { if ($node instanceof Node) { if ($node instanceof Node\Stmt\Trait_ && $traitReflection->getName() === (string) $node->namespacedName && $traitReflection->getNativeReflection()->getStartLine() === $node->getStartLine()) { - $this->processStmtNodes($node, $node->stmts, $scope->enterTrait($traitReflection), $nodeCallback); + $methodModifiers = []; + foreach ($adaptations as $adaptation) { + if (!$adaptation instanceof Node\Stmt\TraitUseAdaptation\Alias) { + continue; + } + + if ($adaptation->newModifier === null) { + continue; + } + + $methodModifiers[$adaptation->method->toLowerString()] = $adaptation->newModifier; + } + + $stmts = $node->stmts; + foreach ($stmts as $i => $stmt) { + if (!$stmt instanceof Node\Stmt\ClassMethod) { + continue; + } + $methodName = $stmt->name->toLowerString(); + if (!array_key_exists($methodName, $methodModifiers)) { + continue; + } + + $methodAst = clone $stmt; + $methodAst->flags = ($methodAst->flags & ~ Node\Stmt\Class_::VISIBILITY_MODIFIER_MASK) | $methodModifiers[$methodName]; + $stmts[$i] = $methodAst; + } + $this->processStmtNodes($node, $stmts, $scope->enterTrait($traitReflection), $nodeCallback); return; } if ($node instanceof Node\Stmt\ClassLike) { @@ -3674,11 +3702,11 @@ private function processNodesForTraitUse($node, ClassReflection $traitReflection } foreach ($node->getSubNodeNames() as $subNodeName) { $subNode = $node->{$subNodeName}; - $this->processNodesForTraitUse($subNode, $traitReflection, $scope, $nodeCallback); + $this->processNodesForTraitUse($subNode, $traitReflection, $scope, $adaptations, $nodeCallback); } } elseif (is_array($node)) { foreach ($node as $subNode) { - $this->processNodesForTraitUse($subNode, $traitReflection, $scope, $nodeCallback); + $this->processNodesForTraitUse($subNode, $traitReflection, $scope, $adaptations, $nodeCallback); } } } diff --git a/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php b/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php index 06083ccdf0..939caa2cd4 100644 --- a/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php +++ b/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php @@ -510,4 +510,10 @@ public function testParameterTypeWidening(int $phpVersionId, array $errors): voi $this->analyse([__DIR__ . '/data/parameter-type-widening.php'], $errors); } + public function testBug4516(): void + { + $this->phpVersionId = PHP_VERSION_ID; + $this->analyse([__DIR__ . '/data/bug-4516.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-4516.php b/tests/PHPStan/Rules/Methods/data/bug-4516.php new file mode 100644 index 0000000000..df3b34a4c7 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-4516.php @@ -0,0 +1,20 @@ + Date: Mon, 20 Sep 2021 13:21:19 +0200 Subject: [PATCH 0349/1284] Fix --- .../PHPStan/Parallel/ParallelAnalyserIntegrationTest.php | 9 +++++++-- tests/PHPStan/Parallel/data/trait-definition.php | 5 +++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/tests/PHPStan/Parallel/ParallelAnalyserIntegrationTest.php b/tests/PHPStan/Parallel/ParallelAnalyserIntegrationTest.php index 6ee2b4746e..31324a79b7 100644 --- a/tests/PHPStan/Parallel/ParallelAnalyserIntegrationTest.php +++ b/tests/PHPStan/Parallel/ParallelAnalyserIntegrationTest.php @@ -51,7 +51,7 @@ public function testRun(string $command): void $this->assertJsonStringEqualsJsonString(Json::encode([ 'totals' => [ 'errors' => 0, - 'file_errors' => 3, + 'file_errors' => 4, ], 'files' => [ sprintf('%s (in context of class ParallelAnalyserIntegrationTest\\Bar)', $filePath) => [ @@ -65,7 +65,7 @@ public function testRun(string $command): void ], ], sprintf('%s (in context of class ParallelAnalyserIntegrationTest\\Foo)', $filePath) => [ - 'errors' => 2, + 'errors' => 3, 'messages' => [ [ 'message' => 'Method ParallelAnalyserIntegrationTest\\Foo::doFoo() has no return type specified.', @@ -77,6 +77,11 @@ public function testRun(string $command): void 'line' => 10, 'ignorable' => true, ], + [ + 'message' => 'Access to an undefined property ParallelAnalyserIntegrationTest\\Foo::$test.', + 'line' => 15, + 'ignorable' => true, + ], ], ], ], diff --git a/tests/PHPStan/Parallel/data/trait-definition.php b/tests/PHPStan/Parallel/data/trait-definition.php index edf01e73f8..499e4040f7 100644 --- a/tests/PHPStan/Parallel/data/trait-definition.php +++ b/tests/PHPStan/Parallel/data/trait-definition.php @@ -10,4 +10,9 @@ public function doFoo() $this->test = 1; } + public function getFoo(): int + { + return $this->test; + } + } From d484ea890dd09bbd1e2e214f2b5a0ee34f5eab75 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 20 Sep 2021 13:33:34 +0200 Subject: [PATCH 0350/1284] Fix unreachable code after do-while loop --- src/Analyser/NodeScopeResolver.php | 6 +-- .../DeadCode/UnreachableStatementRuleTest.php | 6 +++ .../PHPStan/Rules/DeadCode/data/bug-4370.php | 42 +++++++++++++++++++ 3 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 tests/PHPStan/Rules/DeadCode/data/bug-4370.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index c95ef3eef7..6093995e95 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -963,6 +963,9 @@ private function processStmtNode( $bodyScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, $nodeCallback)->filterOutLoopExitPoints(); $bodyScope = $bodyScopeResult->getScope(); + foreach ($bodyScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { + $bodyScope = $bodyScope->mergeWith($continueExitPoint->getScope()); + } $condBooleanType = $bodyScope->getType($stmt->cond)->toBoolean(); $alwaysIterates = $condBooleanType instanceof ConstantBooleanType && $condBooleanType->getValue(); @@ -971,9 +974,6 @@ private function processStmtNode( } else { $alwaysTerminating = $bodyScopeResult->isAlwaysTerminating(); } - foreach ($bodyScopeResult->getExitPointsByType(Continue_::class) as $continueExitPoint) { - $bodyScope = $bodyScope->mergeWith($continueExitPoint->getScope()); - } $finalScope = $alwaysTerminating ? $finalScope : $bodyScope->mergeWith($finalScope); if ($finalScope === null) { $finalScope = $scope; diff --git a/tests/PHPStan/Rules/DeadCode/UnreachableStatementRuleTest.php b/tests/PHPStan/Rules/DeadCode/UnreachableStatementRuleTest.php index 16692000ca..a3c2fb7db2 100644 --- a/tests/PHPStan/Rules/DeadCode/UnreachableStatementRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/UnreachableStatementRuleTest.php @@ -116,4 +116,10 @@ public function testBug2913(): void $this->analyse([__DIR__ . '/data/bug-2913.php'], []); } + public function testBug4370(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-4370.php'], []); + } + } diff --git a/tests/PHPStan/Rules/DeadCode/data/bug-4370.php b/tests/PHPStan/Rules/DeadCode/data/bug-4370.php new file mode 100644 index 0000000000..d7c2371985 --- /dev/null +++ b/tests/PHPStan/Rules/DeadCode/data/bug-4370.php @@ -0,0 +1,42 @@ +lex(); + + // recursively ignore nested objects + if ($code !== self::JSON_OBJECT_START) { + continue; + } + + $this->ignoreObjectBlock($lexer); + } while ($code !== self::JSON_OBJECT_END); + + return $lexer->lex(); + } +} From 1c6ce5dfe4d16ad25ddef96a88541f7e44a3204f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 20 Sep 2021 17:00:53 +0200 Subject: [PATCH 0351/1284] Update nikic/php-parser --- ...rviceLocatorDynamicReturnTypeExtension.php | 8 +-- composer.json | 4 +- composer.lock | 41 ++++++--------- src/Analyser/MutatingScope.php | 20 ++++---- src/Analyser/NodeScopeResolver.php | 50 +++++++++---------- src/Analyser/NullsafeOperatorHelper.php | 4 +- src/Analyser/Scope.php | 2 +- src/Analyser/TypeSpecifier.php | 40 +++++++-------- src/Dependency/ExportedNodeResolver.php | 21 ++++++-- .../ContainerDynamicReturnTypeExtension.php | 8 +-- ...eGetInternalDynamicReturnTypeExtension.php | 4 +- src/Node/ClassPropertyNode.php | 8 ++- .../SourceLocator/CachingVisitor.php | 4 +- src/Rules/Classes/InstantiationRule.php | 4 +- .../ImpossibleCheckTypeFunctionCallRule.php | 4 +- .../Comparison/ImpossibleCheckTypeHelper.php | 16 +++--- .../ImpossibleCheckTypeMethodCallRule.php | 4 +- ...mpossibleCheckTypeStaticMethodCallRule.php | 4 +- src/Rules/DateTimeInstantiationRule.php | 4 +- src/Rules/Debug/DumpTypeRule.php | 4 +- src/Rules/Debug/FileAssertRule.php | 6 +-- src/Rules/FunctionCallParametersCheck.php | 2 +- src/Rules/FunctionDefinitionCheck.php | 2 +- src/Rules/Functions/CallCallablesRule.php | 2 +- .../CallToFunctionParametersRule.php | 2 +- src/Rules/Functions/PrintfParametersRule.php | 2 +- .../Functions/RandomIntParametersRule.php | 4 +- src/Rules/Methods/CallMethodsRule.php | 2 +- src/Rules/Methods/CallStaticMethodsRule.php | 2 +- .../Regexp/RegularExpressionPatternRule.php | 4 +- src/Rules/UnusedFunctionParametersCheck.php | 2 +- src/Rules/Variables/CompactVariablesRule.php | 2 +- src/Testing/TypeInferenceTestCase.php | 14 +++--- src/Type/ParserNodeTypeToPHPStanType.php | 12 ++++- ...gumentBasedFunctionReturnTypeExtension.php | 4 +- ...rrayCombineFunctionReturnTypeExtension.php | 6 +-- ...ArrayCurrentDynamicReturnTypeExtension.php | 4 +- .../ArrayFillFunctionReturnTypeExtension.php | 8 +-- ...rayFillKeysFunctionReturnTypeExtension.php | 6 +-- ...rFunctionReturnTypeReturnTypeExtension.php | 6 +-- .../ArrayFlipFunctionReturnTypeExtension.php | 4 +- .../ArrayKeyDynamicReturnTypeExtension.php | 4 +- ...yExistsFunctionTypeSpecifyingExtension.php | 6 +-- ...rrayKeyFirstDynamicReturnTypeExtension.php | 4 +- ...ArrayKeyLastDynamicReturnTypeExtension.php | 4 +- ...KeysFunctionDynamicReturnTypeExtension.php | 2 +- .../ArrayMapFunctionReturnTypeExtension.php | 8 +-- ...ergeFunctionDynamicReturnTypeExtension.php | 4 +- .../ArrayNextDynamicReturnTypeExtension.php | 4 +- ...terFunctionsDynamicReturnTypeExtension.php | 4 +- .../ArrayPopFunctionReturnTypeExtension.php | 4 +- .../ArrayRandFunctionReturnTypeExtension.php | 8 +-- ...ArrayReduceFunctionReturnTypeExtension.php | 12 ++--- ...rrayReverseFunctionReturnTypeExtension.php | 4 +- ...archFunctionDynamicReturnTypeExtension.php | 8 +-- .../ArrayShiftFunctionReturnTypeExtension.php | 4 +- .../ArraySliceFunctionReturnTypeExtension.php | 14 +++--- ...ySumFunctionDynamicReturnTypeExtension.php | 4 +- ...luesFunctionDynamicReturnTypeExtension.php | 2 +- .../AssertFunctionTypeSpecifyingExtension.php | 4 +- ...codeDynamicFunctionReturnTypeExtension.php | 4 +- .../BcMathStringOrNullReturnTypeExtension.php | 24 ++++----- ...sExistsFunctionTypeSpecifyingExtension.php | 6 +-- .../ClosureBindDynamicReturnTypeExtension.php | 2 +- ...FromCallableDynamicReturnTypeExtension.php | 6 +-- .../CompactFunctionReturnTypeExtension.php | 4 +- .../Php/CountFunctionReturnTypeExtension.php | 10 ++-- .../CountFunctionTypeSpecifyingExtension.php | 6 +-- src/Type/Php/CurlInitReturnTypeExtension.php | 2 +- .../Php/DateFunctionReturnTypeExtension.php | 4 +- ...eIntervalConstructorThrowTypeExtension.php | 4 +- .../DateTimeConstructorThrowTypeExtension.php | 4 +- .../DateTimeDynamicReturnTypeExtension.php | 6 +-- .../DefineConstantTypeSpecifyingExtension.php | 6 +-- ...DefinedConstantTypeSpecifyingExtension.php | 4 +- .../Php/DsMapDynamicReturnTypeExtension.php | 4 +- ...lodeFunctionDynamicReturnTypeExtension.php | 8 +-- .../FilterVarDynamicReturnTypeExtension.php | 6 +-- .../GetClassDynamicReturnTypeExtension.php | 2 +- ...lassDynamicFunctionReturnTypeExtension.php | 4 +- ...fdayDynamicFunctionReturnTypeExtension.php | 4 +- .../Php/HashFunctionsReturnTypeExtension.php | 4 +- .../HashHmacFunctionsReturnTypeExtension.php | 4 +- .../Php/HrtimeFunctionReturnTypeExtension.php | 4 +- .../ImplodeFunctionReturnTypeExtension.php | 2 +- ...InArrayFunctionTypeSpecifyingExtension.php | 8 +-- src/Type/Php/IntdivThrowTypeExtension.php | 6 +-- .../IsAFunctionTypeSpecifyingExtension.php | 20 ++++---- ...IsArrayFunctionTypeSpecifyingExtension.php | 4 +- .../IsBoolFunctionTypeSpecifyingExtension.php | 4 +- ...allableFunctionTypeSpecifyingExtension.php | 4 +- ...untableFunctionTypeSpecifyingExtension.php | 4 +- ...IsFloatFunctionTypeSpecifyingExtension.php | 4 +- .../IsIntFunctionTypeSpecifyingExtension.php | 4 +- ...terableFunctionTypeSpecifyingExtension.php | 4 +- .../IsNullFunctionTypeSpecifyingExtension.php | 4 +- ...NumericFunctionTypeSpecifyingExtension.php | 4 +- ...sObjectFunctionTypeSpecifyingExtension.php | 4 +- ...esourceFunctionTypeSpecifyingExtension.php | 4 +- ...sScalarFunctionTypeSpecifyingExtension.php | 4 +- ...sStringFunctionTypeSpecifyingExtension.php | 4 +- ...classOfFunctionTypeSpecifyingExtension.php | 12 ++--- ...ThrowOnErrorDynamicReturnTypeExtension.php | 4 +- src/Type/Php/JsonThrowTypeExtension.php | 4 +- ...ertEncodingFunctionReturnTypeExtension.php | 4 +- .../Php/MbFunctionsReturnTypeExtension.php | 4 +- ...uteCharacterDynamicReturnTypeExtension.php | 4 +- .../MethodExistsTypeSpecifyingExtension.php | 8 +-- .../MicrotimeFunctionReturnTypeExtension.php | 4 +- .../Php/MinMaxFunctionReturnTypeExtension.php | 12 ++--- ...mptyStringFunctionsReturnTypeExtension.php | 2 +- ...rmatFunctionDynamicReturnTypeExtension.php | 6 +-- ...eUrlFunctionDynamicReturnTypeExtension.php | 8 +-- ...infoFunctionDynamicReturnTypeExtension.php | 2 +- .../Php/PowFunctionReturnTypeExtension.php | 6 +-- .../PregSplitDynamicReturnTypeExtension.php | 2 +- .../PropertyExistsTypeSpecifyingExtension.php | 10 ++-- .../RandomIntFunctionReturnTypeExtension.php | 10 ++-- .../Php/RangeFunctionReturnTypeExtension.php | 8 +-- ...tionClassConstructorThrowTypeExtension.php | 4 +- ...assIsSubclassOfTypeSpecifyingExtension.php | 4 +- ...nFunctionConstructorThrowTypeExtension.php | 4 +- ...GetAttributesMethodReturnTypeExtension.php | 6 +-- ...ionMethodConstructorThrowTypeExtension.php | 6 +-- ...nPropertyConstructorThrowTypeExtension.php | 6 +-- ...aceFunctionsDynamicReturnTypeExtension.php | 4 +- ...LElementAsXMLMethodReturnTypeExtension.php | 2 +- ...MLElementConstructorThrowTypeExtension.php | 4 +- ...LElementXpathMethodReturnTypeExtension.php | 4 +- ...intfFunctionDynamicReturnTypeExtension.php | 2 +- .../Php/StrPadFunctionReturnTypeExtension.php | 2 +- .../StrRepeatFunctionReturnTypeExtension.php | 2 +- .../StrSplitFunctionReturnTypeExtension.php | 12 ++--- .../Php/StrTokFunctionReturnTypeExtension.php | 4 +- ...ountFunctionDynamicReturnTypeExtension.php | 4 +- .../Php/StrlenFunctionReturnTypeExtension.php | 2 +- .../StrtotimeFunctionReturnTypeExtension.php | 4 +- ...trvalFamilyFunctionReturnTypeExtension.php | 4 +- .../Php/SubstrDynamicReturnTypeExtension.php | 2 +- ...ingFunctionsDynamicReturnTypeExtension.php | 2 +- ...portFunctionDynamicReturnTypeExtension.php | 6 +-- ...pareFunctionDynamicReturnTypeExtension.php | 12 ++--- ...tionClassMethodTypeSpecifyingExtension.php | 2 +- ...assStaticMethodTypeSpecifyingExtension.php | 2 +- 144 files changed, 452 insertions(+), 438 deletions(-) diff --git a/build/PHPStan/Build/ServiceLocatorDynamicReturnTypeExtension.php b/build/PHPStan/Build/ServiceLocatorDynamicReturnTypeExtension.php index 8ad0e108c1..1a1207e723 100644 --- a/build/PHPStan/Build/ServiceLocatorDynamicReturnTypeExtension.php +++ b/build/PHPStan/Build/ServiceLocatorDynamicReturnTypeExtension.php @@ -30,17 +30,17 @@ public function isMethodSupported(MethodReflection $methodReflection): bool public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type { - if (count($methodCall->args) === 0) { + if (count($methodCall->getArgs()) === 0) { return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); } - $argType = $scope->getType($methodCall->args[0]->value); + $argType = $scope->getType($methodCall->getArgs()[0]->value); if (!$argType instanceof ConstantStringType) { return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); } $type = new ObjectType($argType->getValue()); - if ($methodReflection->getName() === 'getByType' && count($methodCall->args) >= 2) { - $argType = $scope->getType($methodCall->args[1]->value); + if ($methodReflection->getName() === 'getByType' && count($methodCall->getArgs()) >= 2) { + $argType = $scope->getType($methodCall->getArgs()[1]->value); if ($argType instanceof ConstantBooleanType && $argType->getValue()) { $type = TypeCombinator::addNull($type); } diff --git a/composer.json b/composer.json index 39f9fbea60..88aaae4948 100644 --- a/composer.json +++ b/composer.json @@ -21,9 +21,9 @@ "nette/neon": "^3.0", "nette/schema": "^1.0", "nette/utils": "^3.1.3", - "nikic/php-parser": "dev-master as 4.12.0", + "nikic/php-parser": "4.13.0", "ondram/ci-detector": "^3.4.0", - "ondrejmirtes/better-reflection": "4.3.69", + "ondrejmirtes/better-reflection": "4.3.70", "phpstan/php-8-stubs": "^0.1.23", "phpstan/phpdoc-parser": "^1.2.0", "react/child-process": "^0.6.1", diff --git a/composer.lock b/composer.lock index 4130445744..a414f5e41e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "7b2e806b9d571e647d209a79eb6fe8c0", + "content-hash": "c1d91467e312e364a2d0be0d50163a50", "packages": [ { "name": "clue/block-react", @@ -1946,16 +1946,16 @@ }, { "name": "nikic/php-parser", - "version": "dev-master", + "version": "v4.13.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "9aebf377fcdf205b2156cb78c0bd6e7b2003f106" + "reference": "50953a2691a922aa1769461637869a0a2faa3f53" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/9aebf377fcdf205b2156cb78c0bd6e7b2003f106", - "reference": "9aebf377fcdf205b2156cb78c0bd6e7b2003f106", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/50953a2691a922aa1769461637869a0a2faa3f53", + "reference": "50953a2691a922aa1769461637869a0a2faa3f53", "shasum": "" }, "require": { @@ -1966,7 +1966,6 @@ "ircmaxell/php-yacc": "^0.0.7", "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" }, - "default-branch": true, "bin": [ "bin/php-parse" ], @@ -1997,9 +1996,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/master" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.13.0" }, - "time": "2021-08-08T17:12:44+00:00" + "time": "2021-09-20T12:20:58+00:00" }, { "name": "ondram/ci-detector", @@ -2075,22 +2074,22 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "4.3.69", + "version": "4.3.70", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "370878a62e8ed064843fa2f91f896189cb5e9704" + "reference": "ec87deadc70f01c6d66ed22d653b7292cccdcbc9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/370878a62e8ed064843fa2f91f896189cb5e9704", - "reference": "370878a62e8ed064843fa2f91f896189cb5e9704", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/ec87deadc70f01c6d66ed22d653b7292cccdcbc9", + "reference": "ec87deadc70f01c6d66ed22d653b7292cccdcbc9", "shasum": "" }, "require": { "ext-json": "*", "jetbrains/phpstorm-stubs": "dev-master#0a73df114cdea7f30c8b5f6fbfbf8e6839a89e88", - "nikic/php-parser": "^4.12.0", + "nikic/php-parser": "^4.13.0", "php": ">=7.1.0" }, "require-dev": { @@ -2139,9 +2138,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/4.3.69" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/4.3.70" }, - "time": "2021-09-17T13:57:28+00:00" + "time": "2021-09-20T14:59:55+00:00" }, { "name": "phpstan/php-8-stubs", @@ -6376,18 +6375,10 @@ "time": "2021-03-09T10:59:23+00:00" } ], - "aliases": [ - { - "package": "nikic/php-parser", - "version": "9999999-dev", - "alias": "4.12.0", - "alias_normalized": "4.12.0.0" - } - ], + "aliases": [], "minimum-stability": "dev", "stability-flags": { - "jetbrains/phpstorm-stubs": 20, - "nikic/php-parser": 20 + "jetbrains/phpstorm-stubs": 20 }, "prefer-stable": true, "prefer-lowest": false, diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index f42133ca9c..39c8eac68a 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -1505,15 +1505,15 @@ private function resolveType(Expr $node): Type if ( $functionName === 'array_map' && $argOrder === 0 - && isset($funcCall->args[1]) + && isset($funcCall->getArgs()[1]) ) { - if (!isset($funcCall->args[2])) { + if (!isset($funcCall->getArgs()[2])) { $callableParameters = [ - new DummyParameter('item', $this->getType($funcCall->args[1]->value)->getIterableValueType(), false, PassedByReference::createNo(), false, null), + new DummyParameter('item', $this->getType($funcCall->getArgs()[1]->value)->getIterableValueType(), false, PassedByReference::createNo(), false, null), ]; } else { $callableParameters = []; - foreach ($funcCall->args as $i => $funcCallArg) { + foreach ($funcCall->getArgs() as $i => $funcCallArg) { if ($i === 0) { continue; } @@ -2269,7 +2269,7 @@ private function resolveType(Expr $node): Type return ParametersAcceptorSelector::selectFromArgs( $this, - $node->args, + $node->getArgs(), $calledOnType->getCallableParametersAcceptors($this) )->getReturnType(); } @@ -2289,7 +2289,7 @@ private function resolveType(Expr $node): Type return ParametersAcceptorSelector::selectFromArgs( $this, - $node->args, + $node->getArgs(), $functionReflection->getVariants() )->getReturnType(); } @@ -3358,7 +3358,7 @@ public function isParameterValueNullable(Node\Param $parameter): bool /** * @api - * @param \PhpParser\Node\Name|\PhpParser\Node\Identifier|\PhpParser\Node\NullableType|\PhpParser\Node\UnionType|null $type + * @param \PhpParser\Node\Name|\PhpParser\Node\Identifier|\PhpParser\Node\ComplexType|null $type * @param bool $isNullable * @param bool $isVariadic * @return Type @@ -4891,7 +4891,7 @@ private function exactInstantiation(New_ $node, string $className): ?Type $methodCall = new Expr\StaticCall( new Name($resolvedClassName), new Node\Identifier($constructorMethod->getName()), - $node->args + $node->getArgs() ); foreach ($this->dynamicReturnTypeExtensionRegistry->getDynamicStaticMethodReturnTypeExtensionsForClass($classReflection->getName()) as $dynamicStaticMethodReturnTypeExtension) { @@ -4958,7 +4958,7 @@ private function exactInstantiation(New_ $node, string $className): ?Type $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( $this, - $methodCall->args, + $methodCall->getArgs(), $constructorMethod->getVariants() ); @@ -5070,7 +5070,7 @@ private function methodCallReturnType(Type $typeWithMethod, string $methodName, return ParametersAcceptorSelector::selectFromArgs( $this, - $methodCall->args, + $methodCall->getArgs(), $methodReflection->getVariants() )->getReturnType(); } diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 6093995e95..ad1ca635e3 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -1788,7 +1788,7 @@ function (MutatingScope $scope) use ($expr, $nodeCallback, $context): Expression if ($nameType->isCallable()->yes()) { $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( $scope, - $expr->args, + $expr->getArgs(), $nameType->getCallableParametersAcceptors($scope) ); } @@ -1799,11 +1799,11 @@ function (MutatingScope $scope) use ($expr, $nodeCallback, $context): Expression $functionReflection = $this->reflectionProvider->getFunction($expr->name, $scope); $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( $scope, - $expr->args, + $expr->getArgs(), $functionReflection->getVariants() ); } - $result = $this->processArgs($functionReflection, $parametersAcceptor, $expr->args, $scope, $nodeCallback, $context); + $result = $this->processArgs($functionReflection, $parametersAcceptor, $expr->getArgs(), $scope, $nodeCallback, $context); $scope = $result->getScope(); $hasYield = $result->hasYield(); $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); @@ -1830,9 +1830,9 @@ function (MutatingScope $scope) use ($expr, $nodeCallback, $context): Expression if ( isset($functionReflection) && in_array($functionReflection->getName(), ['array_pop', 'array_shift'], true) - && count($expr->args) >= 1 + && count($expr->getArgs()) >= 1 ) { - $arrayArg = $expr->args[0]->value; + $arrayArg = $expr->getArgs()[0]->value; $constantArrays = TypeUtils::getConstantArrays($scope->getType($arrayArg)); $scope = $scope->invalidateExpression($arrayArg); if (count($constantArrays) > 0) { @@ -1861,10 +1861,10 @@ function (MutatingScope $scope) use ($expr, $nodeCallback, $context): Expression if ( isset($functionReflection) && in_array($functionReflection->getName(), ['array_push', 'array_unshift'], true) - && count($expr->args) >= 2 + && count($expr->getArgs()) >= 2 ) { $argumentTypes = []; - foreach (array_slice($expr->args, 1) as $callArg) { + foreach (array_slice($expr->getArgs(), 1) as $callArg) { $callArgType = $scope->getType($callArg->value); if ($callArg->unpack) { $iterableValueType = $callArgType->getIterableValueType(); @@ -1881,7 +1881,7 @@ function (MutatingScope $scope) use ($expr, $nodeCallback, $context): Expression $argumentTypes[] = $callArgType; } - $arrayArg = $expr->args[0]->value; + $arrayArg = $expr->getArgs()[0]->value; $originalArrayType = $scope->getType($arrayArg); $constantArrays = TypeUtils::getConstantArrays($originalArrayType); if ( @@ -1938,7 +1938,7 @@ function (MutatingScope $scope) use ($expr, $nodeCallback, $context): Expression } if (isset($functionReflection) && $functionReflection->hasSideEffects()->yes()) { - foreach ($expr->args as $arg) { + foreach ($expr->getArgs() as $arg) { $scope = $scope->invalidateExpression($arg->value, true); } } @@ -1949,9 +1949,9 @@ function (MutatingScope $scope) use ($expr, $nodeCallback, $context): Expression ($expr->var instanceof Expr\Closure || $expr->var instanceof Expr\ArrowFunction) && $expr->name instanceof Node\Identifier && strtolower($expr->name->name) === 'call' - && isset($expr->args[0]) + && isset($expr->getArgs()[0]) ) { - $closureCallScope = $scope->enterClosureCall($scope->getType($expr->args[0]->value)); + $closureCallScope = $scope->enterClosureCall($scope->getType($expr->getArgs()[0]->value)); } $result = $this->processExprNode($expr->var, $closureCallScope ?? $scope, $nodeCallback, $context->enterDeep()); @@ -1974,7 +1974,7 @@ function (MutatingScope $scope) use ($expr, $nodeCallback, $context): Expression if ($methodReflection !== null) { $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( $scope, - $expr->args, + $expr->getArgs(), $methodReflection->getVariants() ); $methodThrowPoint = $this->getMethodThrowPoint($methodReflection, $parametersAcceptor, $expr, $scope); @@ -1983,13 +1983,13 @@ function (MutatingScope $scope) use ($expr, $nodeCallback, $context): Expression } } } - $result = $this->processArgs($methodReflection, $parametersAcceptor, $expr->args, $scope, $nodeCallback, $context); + $result = $this->processArgs($methodReflection, $parametersAcceptor, $expr->getArgs(), $scope, $nodeCallback, $context); $scope = $result->getScope(); if ($methodReflection !== null) { $hasSideEffects = $methodReflection->hasSideEffects(); if ($hasSideEffects->yes()) { $scope = $scope->invalidateExpression($expr->var, true); - foreach ($expr->args as $arg) { + foreach ($expr->getArgs() as $arg) { $scope = $scope->invalidateExpression($arg->value, true); } } @@ -2058,7 +2058,7 @@ static function () use ($scope, $expr): MutatingScope { $methodReflection = $classReflection->getMethod($methodName, $scope); $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( $scope, - $expr->args, + $expr->getArgs(), $methodReflection->getVariants() ); $methodThrowPoint = $this->getStaticMethodThrowPoint($methodReflection, $expr, $scope); @@ -2070,8 +2070,8 @@ static function () use ($scope, $expr): MutatingScope { && strtolower($methodName) === 'bind' ) { $thisType = null; - if (isset($expr->args[1])) { - $argType = $scope->getType($expr->args[1]->value); + if (isset($expr->getArgs()[1])) { + $argType = $scope->getType($expr->getArgs()[1]->value); if ($argType instanceof NullType) { $thisType = null; } else { @@ -2079,8 +2079,8 @@ static function () use ($scope, $expr): MutatingScope { } } $scopeClass = 'static'; - if (isset($expr->args[2])) { - $argValue = $expr->args[2]->value; + if (isset($expr->getArgs()[2])) { + $argValue = $expr->getArgs()[2]->value; $argValueType = $scope->getType($argValue); $directClassNames = TypeUtils::getDirectClassNames($argValueType); @@ -2109,7 +2109,7 @@ static function () use ($scope, $expr): MutatingScope { $throwPoints[] = ThrowPoint::createImplicit($scope, $expr); } } - $result = $this->processArgs($methodReflection, $parametersAcceptor, $expr->args, $scope, $nodeCallback, $context, $closureBindScope ?? null); + $result = $this->processArgs($methodReflection, $parametersAcceptor, $expr->getArgs(), $scope, $nodeCallback, $context, $closureBindScope ?? null); $scope = $result->getScope(); $scopeFunction = $scope->getFunction(); if ( @@ -2129,7 +2129,7 @@ static function () use ($scope, $expr): MutatingScope { if ($methodReflection !== null) { if ($methodReflection->hasSideEffects()->yes()) { - foreach ($expr->args as $arg) { + foreach ($expr->getArgs() as $arg) { $scope = $scope->invalidateExpression($arg->value, true); } } @@ -2449,10 +2449,10 @@ static function () use ($expr, $rightResult): MutatingScope { $constructorReflection = $classReflection->getConstructor(); $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( $scope, - $expr->args, + $expr->getArgs(), $constructorReflection->getVariants() ); - $constructorThrowPoint = $this->getConstructorThrowPoint($constructorReflection, $classReflection, $expr, $expr->class, $expr->args, $scope); + $constructorThrowPoint = $this->getConstructorThrowPoint($constructorReflection, $classReflection, $expr, $expr->class, $expr->getArgs(), $scope); if ($constructorThrowPoint !== null) { $throwPoints[] = $constructorThrowPoint; } @@ -2461,7 +2461,7 @@ static function () use ($expr, $rightResult): MutatingScope { $throwPoints[] = ThrowPoint::createImplicit($scope, $expr); } } - $result = $this->processArgs($constructorReflection, $parametersAcceptor, $expr->args, $scope, $nodeCallback, $context); + $result = $this->processArgs($constructorReflection, $parametersAcceptor, $expr->getArgs(), $scope, $nodeCallback, $context); $scope = $result->getScope(); $hasYield = $hasYield || $result->hasYield(); $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); @@ -2678,7 +2678,7 @@ private function getFunctionThrowPoint( !$functionReflection->isBuiltin() || $requiredParameters === null || $requiredParameters > 0 - || count($funcCall->args) > 0 + || count($funcCall->getArgs()) > 0 ) { $functionReturnedType = $scope->getType($funcCall); if (!(new ObjectType(\Throwable::class))->isSuperTypeOf($functionReturnedType)->yes()) { diff --git a/src/Analyser/NullsafeOperatorHelper.php b/src/Analyser/NullsafeOperatorHelper.php index 5c4ce696f9..a74dd6e426 100644 --- a/src/Analyser/NullsafeOperatorHelper.php +++ b/src/Analyser/NullsafeOperatorHelper.php @@ -19,7 +19,7 @@ public static function getNullsafeShortcircuitedExpr(Expr $expr): Expr return $expr; } - return new Expr\MethodCall($var, $expr->name, $expr->args); + return new Expr\MethodCall($var, $expr->name, $expr->getArgs()); } if ($expr instanceof Expr\StaticCall && $expr->class instanceof Expr) { @@ -28,7 +28,7 @@ public static function getNullsafeShortcircuitedExpr(Expr $expr): Expr return $expr; } - return new Expr\StaticCall($class, $expr->name, $expr->args); + return new Expr\StaticCall($class, $expr->name, $expr->getArgs()); } if ($expr instanceof Expr\ArrayDimFetch) { diff --git a/src/Analyser/Scope.php b/src/Analyser/Scope.php index fe9c726a1c..bcd205a9a7 100644 --- a/src/Analyser/Scope.php +++ b/src/Analyser/Scope.php @@ -94,7 +94,7 @@ public function isInClosureBind(): bool; public function isParameterValueNullable(Param $parameter): bool; /** - * @param \PhpParser\Node\Name|\PhpParser\Node\Identifier|\PhpParser\Node\NullableType|\PhpParser\Node\UnionType|null $type + * @param \PhpParser\Node\Name|\PhpParser\Node\Identifier|\PhpParser\Node\ComplexType|null $type * @param bool $isNullable * @param bool $isVariadic * @return Type diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 014746073d..8ef6a2c6d8 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -205,7 +205,7 @@ public function specifyTypesInCondition( if ( !$context->null() && $exprNode instanceof FuncCall - && count($exprNode->args) === 1 + && count($exprNode->getArgs()) === 1 && $exprNode->name instanceof Name && in_array(strtolower((string) $exprNode->name), ['count', 'sizeof'], true) && $constantType instanceof ConstantIntegerType @@ -215,9 +215,9 @@ public function specifyTypesInCondition( if ($constantType->getValue() === 0) { $newContext = $newContext->negate(); } - $argType = $scope->getType($exprNode->args[0]->value); + $argType = $scope->getType($exprNode->getArgs()[0]->value); if ($argType->isArray()->yes()) { - return $this->create($exprNode->args[0]->value, new NonEmptyArrayType(), $newContext, false, $scope); + return $this->create($exprNode->getArgs()[0]->value, new NonEmptyArrayType(), $newContext, false, $scope); } } } @@ -225,7 +225,7 @@ public function specifyTypesInCondition( if ( !$context->null() && $exprNode instanceof FuncCall - && count($exprNode->args) === 1 + && count($exprNode->getArgs()) === 1 && $exprNode->name instanceof Name && strtolower((string) $exprNode->name) === 'strlen' && $constantType instanceof ConstantIntegerType @@ -235,9 +235,9 @@ public function specifyTypesInCondition( if ($constantType->getValue() === 0) { $newContext = $newContext->negate(); } - $argType = $scope->getType($exprNode->args[0]->value); + $argType = $scope->getType($exprNode->getArgs()[0]->value); if ($argType instanceof StringType) { - return $this->create($exprNode->args[0]->value, new AccessoryNonEmptyStringType(), $newContext, false, $scope); + return $this->create($exprNode->getArgs()[0]->value, new AccessoryNonEmptyStringType(), $newContext, false, $scope); } } } @@ -331,7 +331,7 @@ public function specifyTypesInCondition( if ( !$context->null() && $exprNode instanceof FuncCall - && count($exprNode->args) === 1 + && count($exprNode->getArgs()) === 1 && $exprNode->name instanceof Name && in_array(strtolower((string) $exprNode->name), ['count', 'sizeof'], true) && $constantType instanceof ConstantIntegerType @@ -341,9 +341,9 @@ public function specifyTypesInCondition( if ($constantType->getValue() === 0) { $newContext = $newContext->negate(); } - $argType = $scope->getType($exprNode->args[0]->value); + $argType = $scope->getType($exprNode->getArgs()[0]->value); if ($argType->isArray()->yes()) { - return $this->create($exprNode->args[0]->value, new NonEmptyArrayType(), $newContext, false, $scope); + return $this->create($exprNode->getArgs()[0]->value, new NonEmptyArrayType(), $newContext, false, $scope); } } } @@ -395,13 +395,13 @@ public function specifyTypesInCondition( $expr->left instanceof FuncCall && $expr->left->name instanceof Name && strtolower($expr->left->name->toString()) === 'get_class' - && isset($expr->left->args[0]) + && isset($expr->left->getArgs()[0]) && $rightType instanceof ConstantStringType ) { return $this->specifyTypesInCondition( $scope, new Instanceof_( - $expr->left->args[0]->value, + $expr->left->getArgs()[0]->value, new Name($rightType->getValue()) ), $context @@ -412,13 +412,13 @@ public function specifyTypesInCondition( $expr->right instanceof FuncCall && $expr->right->name instanceof Name && strtolower($expr->right->name->toString()) === 'get_class' - && isset($expr->right->args[0]) + && isset($expr->right->getArgs()[0]) && $leftType instanceof ConstantStringType ) { return $this->specifyTypesInCondition( $scope, new Instanceof_( - $expr->right->args[0]->value, + $expr->right->getArgs()[0]->value, new Name($leftType->getValue()) ), $context @@ -439,7 +439,7 @@ public function specifyTypesInCondition( if ( $expr->left instanceof FuncCall - && count($expr->left->args) === 1 + && count($expr->left->getArgs()) === 1 && $expr->left->name instanceof Name && in_array(strtolower((string) $expr->left->name), ['count', 'sizeof', 'strlen'], true) && ( @@ -464,7 +464,7 @@ public function specifyTypesInCondition( if ( !$context->null() && $expr->right instanceof FuncCall - && count($expr->right->args) === 1 + && count($expr->right->getArgs()) === 1 && $expr->right->name instanceof Name && in_array(strtolower((string) $expr->right->name), ['count', 'sizeof'], true) && (new IntegerType())->isSuperTypeOf($leftType)->yes() @@ -473,9 +473,9 @@ public function specifyTypesInCondition( $context->truthy() && (IntegerRangeType::createAllGreaterThanOrEqualTo(1 - $offset)->isSuperTypeOf($leftType)->yes()) || ($context->falsey() && (new ConstantIntegerType(1 - $offset))->isSuperTypeOf($leftType)->yes()) ) { - $argType = $scope->getType($expr->right->args[0]->value); + $argType = $scope->getType($expr->right->getArgs()[0]->value); if ($argType->isArray()->yes()) { - $result = $result->unionWith($this->create($expr->right->args[0]->value, new NonEmptyArrayType(), $context, false, $scope)); + $result = $result->unionWith($this->create($expr->right->getArgs()[0]->value, new NonEmptyArrayType(), $context, false, $scope)); } } } @@ -483,7 +483,7 @@ public function specifyTypesInCondition( if ( !$context->null() && $expr->right instanceof FuncCall - && count($expr->right->args) === 1 + && count($expr->right->getArgs()) === 1 && $expr->right->name instanceof Name && strtolower((string) $expr->right->name) === 'strlen' && (new IntegerType())->isSuperTypeOf($leftType)->yes() @@ -492,9 +492,9 @@ public function specifyTypesInCondition( $context->truthy() && (IntegerRangeType::createAllGreaterThanOrEqualTo(1 - $offset)->isSuperTypeOf($leftType)->yes()) || ($context->falsey() && (new ConstantIntegerType(1 - $offset))->isSuperTypeOf($leftType)->yes()) ) { - $argType = $scope->getType($expr->right->args[0]->value); + $argType = $scope->getType($expr->right->getArgs()[0]->value); if ($argType instanceof StringType) { - $result = $result->unionWith($this->create($expr->right->args[0]->value, new AccessoryNonEmptyStringType(), $context, false, $scope)); + $result = $result->unionWith($this->create($expr->right->getArgs()[0]->value, new AccessoryNonEmptyStringType(), $context, false, $scope)); } } } diff --git a/src/Dependency/ExportedNodeResolver.php b/src/Dependency/ExportedNodeResolver.php index 855da7f885..0353437cac 100644 --- a/src/Dependency/ExportedNodeResolver.php +++ b/src/Dependency/ExportedNodeResolver.php @@ -243,7 +243,7 @@ public function resolve(string $fileName, \PhpParser\Node $node): ?ExportedNode } /** - * @param Node\Identifier|Node\Name|Node\NullableType|Node\UnionType|null $type + * @param Node\Identifier|Node\Name|Node\ComplexType|null $type * @return string|null */ private function printType($type): ?string @@ -267,7 +267,22 @@ private function printType($type): ?string }, $type->types)); } - return $type->toString(); + if ($type instanceof Node\IntersectionType) { + return implode('&', array_map(function ($innerType): string { + $printedType = $this->printType($innerType); + if ($printedType === null) { + throw new \PHPStan\ShouldNotHappenException(); + } + + return $printedType; + }, $type->types)); + } + + if ($type instanceof Node\Identifier || $type instanceof Name) { + return $type->toString(); + } + + throw new \PHPStan\ShouldNotHappenException(); } /** @@ -291,7 +306,7 @@ private function exportParameterNodes(array $params): array $innerTypes = $type->types; $innerTypes[] = new Name('null'); $type = new Node\UnionType($innerTypes); - } elseif (!$type instanceof Node\NullableType) { + } elseif ($type instanceof Node\Identifier || $type instanceof Name) { $type = new Node\NullableType($type); } } diff --git a/src/Internal/ContainerDynamicReturnTypeExtension.php b/src/Internal/ContainerDynamicReturnTypeExtension.php index 58703f78c0..6b99ec8a4d 100644 --- a/src/Internal/ContainerDynamicReturnTypeExtension.php +++ b/src/Internal/ContainerDynamicReturnTypeExtension.php @@ -30,17 +30,17 @@ public function isMethodSupported(MethodReflection $methodReflection): bool public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type { - if (count($methodCall->args) === 0) { + if (count($methodCall->getArgs()) === 0) { return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); } - $argType = $scope->getType($methodCall->args[0]->value); + $argType = $scope->getType($methodCall->getArgs()[0]->value); if (!$argType instanceof ConstantStringType) { return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); } $type = new ObjectType($argType->getValue()); - if ($methodReflection->getName() === 'getByType' && count($methodCall->args) >= 2) { - $argType = $scope->getType($methodCall->args[1]->value); + if ($methodReflection->getName() === 'getByType' && count($methodCall->getArgs()) >= 2) { + $argType = $scope->getType($methodCall->getArgs()[1]->value); if ($argType instanceof ConstantBooleanType && $argType->getValue()) { $type = TypeCombinator::addNull($type); } diff --git a/src/Internal/UnionTypeGetInternalDynamicReturnTypeExtension.php b/src/Internal/UnionTypeGetInternalDynamicReturnTypeExtension.php index fa06f30e68..32ff7b2771 100644 --- a/src/Internal/UnionTypeGetInternalDynamicReturnTypeExtension.php +++ b/src/Internal/UnionTypeGetInternalDynamicReturnTypeExtension.php @@ -29,11 +29,11 @@ public function getTypeFromMethodCall( Scope $scope ): Type { - if (count($methodCall->args) < 2) { + if (count($methodCall->getArgs()) < 2) { return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); } - $getterClosureType = $scope->getType($methodCall->args[1]->value); + $getterClosureType = $scope->getType($methodCall->getArgs()[1]->value); return ParametersAcceptorSelector::selectSingle($getterClosureType->getCallableParametersAcceptors($scope))->getReturnType(); } diff --git a/src/Node/ClassPropertyNode.php b/src/Node/ClassPropertyNode.php index d04f196489..b1d7598e3d 100644 --- a/src/Node/ClassPropertyNode.php +++ b/src/Node/ClassPropertyNode.php @@ -6,9 +6,7 @@ use PhpParser\Node\Expr; use PhpParser\Node\Identifier; use PhpParser\Node\Name; -use PhpParser\Node\NullableType; use PhpParser\Node\Stmt\Class_; -use PhpParser\Node\UnionType; use PhpParser\NodeAbstract; /** @api */ @@ -19,7 +17,7 @@ class ClassPropertyNode extends NodeAbstract implements VirtualNode private int $flags; - /** @var Identifier|Name|NullableType|UnionType|null */ + /** @var Identifier|Name|Node\ComplexType|null */ private $type; private ?Expr $default; @@ -30,7 +28,7 @@ class ClassPropertyNode extends NodeAbstract implements VirtualNode /** * @param int $flags - * @param Identifier|Name|NullableType|UnionType|null $type + * @param Identifier|Name|Node\ComplexType|null $type * @param string $name * @param Expr|null $default */ @@ -105,7 +103,7 @@ public function isReadOnly(): bool } /** - * @return Identifier|Name|NullableType|UnionType|null + * @return Identifier|Name|Node\ComplexType|null */ public function getNativeType() { diff --git a/src/Reflection/BetterReflection/SourceLocator/CachingVisitor.php b/src/Reflection/BetterReflection/SourceLocator/CachingVisitor.php index 30ef0852e4..3f56c2ab95 100644 --- a/src/Reflection/BetterReflection/SourceLocator/CachingVisitor.php +++ b/src/Reflection/BetterReflection/SourceLocator/CachingVisitor.php @@ -74,12 +74,12 @@ public function enterNode(\PhpParser\Node $node): ?int } /** @var \PhpParser\Node\Scalar\String_ $nameNode */ - $nameNode = $node->args[0]->value; + $nameNode = $node->getArgs()[0]->value; $constantName = $nameNode->value; if (defined($constantName)) { $constantValue = constant($constantName); - $node->args[1]->value = BuilderHelpers::normalizeValue($constantValue); + $node->getArgs()[1]->value = BuilderHelpers::normalizeValue($constantValue); } $constantNode = new FetchedNode( diff --git a/src/Rules/Classes/InstantiationRule.php b/src/Rules/Classes/InstantiationRule.php index 9aa1612b21..b1fb7c1402 100644 --- a/src/Rules/Classes/InstantiationRule.php +++ b/src/Rules/Classes/InstantiationRule.php @@ -152,7 +152,7 @@ private function checkClassName(string $class, bool $isName, Node $node, Scope $ } if (!$classReflection->hasConstructor()) { - if (count($node->args) > 0) { + if (count($node->getArgs()) > 0) { return array_merge($messages, [ RuleErrorBuilder::message(sprintf( 'Class %s does not have a constructor and must be instantiated without any parameters.', @@ -178,7 +178,7 @@ private function checkClassName(string $class, bool $isName, Node $node, Scope $ return array_merge($messages, $this->check->check( ParametersAcceptorSelector::selectFromArgs( $scope, - $node->args, + $node->getArgs(), $constructorReflection->getVariants() ), $scope, diff --git a/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php b/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php index f873a37332..04a697c237 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php @@ -67,7 +67,7 @@ public function processNode(Node $node, Scope $scope): array $addTip(RuleErrorBuilder::message(sprintf( 'Call to function %s()%s will always evaluate to false.', $functionName, - $this->impossibleCheckTypeHelper->getArgumentsDescription($scope, $node->args) + $this->impossibleCheckTypeHelper->getArgumentsDescription($scope, $node->getArgs()) )))->build(), ]; } elseif ($this->checkAlwaysTrueCheckTypeFunctionCall) { @@ -75,7 +75,7 @@ public function processNode(Node $node, Scope $scope): array $addTip(RuleErrorBuilder::message(sprintf( 'Call to function %s()%s will always evaluate to true.', $functionName, - $this->impossibleCheckTypeHelper->getArgumentsDescription($scope, $node->args) + $this->impossibleCheckTypeHelper->getArgumentsDescription($scope, $node->getArgs()) )))->build(), ]; } diff --git a/src/Rules/Comparison/ImpossibleCheckTypeHelper.php b/src/Rules/Comparison/ImpossibleCheckTypeHelper.php index 141d2c278b..dd36fee1d5 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeHelper.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeHelper.php @@ -59,12 +59,12 @@ public function findSpecifiedType( { if ( $node instanceof FuncCall - && count($node->args) > 0 + && count($node->getArgs()) > 0 ) { if ($node->name instanceof \PhpParser\Node\Name) { $functionName = strtolower((string) $node->name); if ($functionName === 'assert') { - $assertValue = $scope->getType($node->args[0]->value)->toBoolean(); + $assertValue = $scope->getType($node->getArgs()[0]->value)->toBoolean(); if (!$assertValue instanceof ConstantBooleanType) { return null; } @@ -84,9 +84,9 @@ public function findSpecifiedType( return null; } elseif ( $functionName === 'in_array' - && count($node->args) >= 3 + && count($node->getArgs()) >= 3 ) { - $haystackType = $scope->getType($node->args[1]->value); + $haystackType = $scope->getType($node->getArgs()[1]->value); if ($haystackType instanceof MixedType) { return null; } @@ -96,7 +96,7 @@ public function findSpecifiedType( } if (!$haystackType instanceof ConstantArrayType || count($haystackType->getValueTypes()) > 0) { - $needleType = $scope->getType($node->args[0]->value); + $needleType = $scope->getType($node->getArgs()[0]->value); $haystackArrayTypes = TypeUtils::getArrays($haystackType); if (count($haystackArrayTypes) === 1 && $haystackArrayTypes[0]->getIterableValueType() instanceof NeverType) { @@ -132,9 +132,9 @@ public function findSpecifiedType( } } } - } elseif ($functionName === 'method_exists' && count($node->args) >= 2) { - $objectType = $scope->getType($node->args[0]->value); - $methodType = $scope->getType($node->args[1]->value); + } elseif ($functionName === 'method_exists' && count($node->getArgs()) >= 2) { + $objectType = $scope->getType($node->getArgs()[0]->value); + $methodType = $scope->getType($node->getArgs()[1]->value); if ($objectType instanceof ConstantStringType && !$this->reflectionProvider->hasClass($objectType->getValue()) diff --git a/src/Rules/Comparison/ImpossibleCheckTypeMethodCallRule.php b/src/Rules/Comparison/ImpossibleCheckTypeMethodCallRule.php index c7df381e21..cce1721459 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeMethodCallRule.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeMethodCallRule.php @@ -67,7 +67,7 @@ public function processNode(Node $node, Scope $scope): array 'Call to method %s::%s()%s will always evaluate to false.', $method->getDeclaringClass()->getDisplayName(), $method->getName(), - $this->impossibleCheckTypeHelper->getArgumentsDescription($scope, $node->args) + $this->impossibleCheckTypeHelper->getArgumentsDescription($scope, $node->getArgs()) )))->build(), ]; } elseif ($this->checkAlwaysTrueCheckTypeFunctionCall) { @@ -77,7 +77,7 @@ public function processNode(Node $node, Scope $scope): array 'Call to method %s::%s()%s will always evaluate to true.', $method->getDeclaringClass()->getDisplayName(), $method->getName(), - $this->impossibleCheckTypeHelper->getArgumentsDescription($scope, $node->args) + $this->impossibleCheckTypeHelper->getArgumentsDescription($scope, $node->getArgs()) )))->build(), ]; } diff --git a/src/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRule.php b/src/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRule.php index 7284114a6a..39b91a3b78 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRule.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRule.php @@ -68,7 +68,7 @@ public function processNode(Node $node, Scope $scope): array 'Call to static method %s::%s()%s will always evaluate to false.', $method->getDeclaringClass()->getDisplayName(), $method->getName(), - $this->impossibleCheckTypeHelper->getArgumentsDescription($scope, $node->args) + $this->impossibleCheckTypeHelper->getArgumentsDescription($scope, $node->getArgs()) )))->build(), ]; } elseif ($this->checkAlwaysTrueCheckTypeFunctionCall) { @@ -79,7 +79,7 @@ public function processNode(Node $node, Scope $scope): array 'Call to static method %s::%s()%s will always evaluate to true.', $method->getDeclaringClass()->getDisplayName(), $method->getName(), - $this->impossibleCheckTypeHelper->getArgumentsDescription($scope, $node->args) + $this->impossibleCheckTypeHelper->getArgumentsDescription($scope, $node->getArgs()) )))->build(), ]; } diff --git a/src/Rules/DateTimeInstantiationRule.php b/src/Rules/DateTimeInstantiationRule.php index 0e8ead4972..be5572d975 100644 --- a/src/Rules/DateTimeInstantiationRule.php +++ b/src/Rules/DateTimeInstantiationRule.php @@ -26,13 +26,13 @@ public function processNode(Node $node, Scope $scope): array { if ( !($node->class instanceof \PhpParser\Node\Name) - || \count($node->args) === 0 + || \count($node->getArgs()) === 0 || !\in_array(strtolower((string) $node->class), ['datetime', 'datetimeimmutable'], true) ) { return []; } - $arg = $scope->getType($node->args[0]->value); + $arg = $scope->getType($node->getArgs()[0]->value); if (!($arg instanceof ConstantStringType)) { return []; } diff --git a/src/Rules/Debug/DumpTypeRule.php b/src/Rules/Debug/DumpTypeRule.php index 6c3c4be802..56ae137d61 100644 --- a/src/Rules/Debug/DumpTypeRule.php +++ b/src/Rules/Debug/DumpTypeRule.php @@ -42,7 +42,7 @@ public function processNode(Node $node, Scope $scope): array return []; } - if (count($node->args) === 0) { + if (count($node->getArgs()) === 0) { return [ RuleErrorBuilder::message(sprintf('Missing argument for %s() function call.', $functionName)) ->nonIgnorable() @@ -54,7 +54,7 @@ public function processNode(Node $node, Scope $scope): array RuleErrorBuilder::message( sprintf( 'Dumped type: %s', - $scope->getType($node->args[0]->value)->describe(VerbosityLevel::precise()) + $scope->getType($node->getArgs()[0]->value)->describe(VerbosityLevel::precise()) ) )->nonIgnorable()->build(), ]; diff --git a/src/Rules/Debug/FileAssertRule.php b/src/Rules/Debug/FileAssertRule.php index dc0831ae39..7ca3dc3712 100644 --- a/src/Rules/Debug/FileAssertRule.php +++ b/src/Rules/Debug/FileAssertRule.php @@ -43,15 +43,15 @@ public function processNode(Node $node, Scope $scope): array $function = $this->reflectionProvider->getFunction($node->name, $scope); if ($function->getName() === 'PHPStan\\Testing\\assertType') { - return $this->processAssertType($node->args, $scope); + return $this->processAssertType($node->getArgs(), $scope); } if ($function->getName() === 'PHPStan\\Testing\\assertNativeType') { - return $this->processAssertNativeType($node->args, $scope); + return $this->processAssertNativeType($node->getArgs(), $scope); } if ($function->getName() === 'PHPStan\\Testing\\assertVariableCertainty') { - return $this->processAssertVariableCertainty($node->args, $scope); + return $this->processAssertVariableCertainty($node->getArgs(), $scope); } return []; diff --git a/src/Rules/FunctionCallParametersCheck.php b/src/Rules/FunctionCallParametersCheck.php index 26d951a98e..0faba58866 100644 --- a/src/Rules/FunctionCallParametersCheck.php +++ b/src/Rules/FunctionCallParametersCheck.php @@ -94,7 +94,7 @@ public function check( /** @var array $arguments */ $arguments = []; /** @var array $args */ - $args = $funcCall->args; + $args = $funcCall->getArgs(); $hasNamedArguments = false; $hasUnpackedArgument = false; $errors = []; diff --git a/src/Rules/FunctionDefinitionCheck.php b/src/Rules/FunctionDefinitionCheck.php index cd920e636b..ea08541362 100644 --- a/src/Rules/FunctionDefinitionCheck.php +++ b/src/Rules/FunctionDefinitionCheck.php @@ -90,7 +90,7 @@ public function checkFunction( /** * @param \PHPStan\Analyser\Scope $scope * @param \PhpParser\Node\Param[] $parameters - * @param \PhpParser\Node\Identifier|\PhpParser\Node\Name|\PhpParser\Node\NullableType|\PhpParser\Node\UnionType|null $returnTypeNode + * @param \PhpParser\Node\Identifier|\PhpParser\Node\Name|\PhpParser\Node\ComplexType|null $returnTypeNode * @param string $parameterMessage * @param string $returnMessage * @param string $unionTypesMessage diff --git a/src/Rules/Functions/CallCallablesRule.php b/src/Rules/Functions/CallCallablesRule.php index 5793b79219..b0efc815d6 100644 --- a/src/Rules/Functions/CallCallablesRule.php +++ b/src/Rules/Functions/CallCallablesRule.php @@ -97,7 +97,7 @@ static function (Type $type): bool { $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( $scope, - $node->args, + $node->getArgs(), $parametersAcceptors ); diff --git a/src/Rules/Functions/CallToFunctionParametersRule.php b/src/Rules/Functions/CallToFunctionParametersRule.php index 927f9f3d88..f7fd2b7d27 100644 --- a/src/Rules/Functions/CallToFunctionParametersRule.php +++ b/src/Rules/Functions/CallToFunctionParametersRule.php @@ -45,7 +45,7 @@ public function processNode(Node $node, Scope $scope): array return $this->check->check( ParametersAcceptorSelector::selectFromArgs( $scope, - $node->args, + $node->getArgs(), $function->getVariants() ), $scope, diff --git a/src/Rules/Functions/PrintfParametersRule.php b/src/Rules/Functions/PrintfParametersRule.php index 83f3878b7a..96d38ed36c 100644 --- a/src/Rules/Functions/PrintfParametersRule.php +++ b/src/Rules/Functions/PrintfParametersRule.php @@ -53,7 +53,7 @@ public function processNode(Node $node, Scope $scope): array $formatArgumentPosition = $functionsArgumentPositions[$name]; - $args = $node->args; + $args = $node->getArgs(); foreach ($args as $arg) { if ($arg->unpack) { return []; diff --git a/src/Rules/Functions/RandomIntParametersRule.php b/src/Rules/Functions/RandomIntParametersRule.php index 9d7a09576c..576116cd93 100644 --- a/src/Rules/Functions/RandomIntParametersRule.php +++ b/src/Rules/Functions/RandomIntParametersRule.php @@ -42,8 +42,8 @@ public function processNode(Node $node, Scope $scope): array return []; } - $minType = $scope->getType($node->args[0]->value)->toInteger(); - $maxType = $scope->getType($node->args[1]->value)->toInteger(); + $minType = $scope->getType($node->getArgs()[0]->value)->toInteger(); + $maxType = $scope->getType($node->getArgs()[1]->value)->toInteger(); if ( !$minType instanceof ConstantIntegerType && !$minType instanceof IntegerRangeType diff --git a/src/Rules/Methods/CallMethodsRule.php b/src/Rules/Methods/CallMethodsRule.php index 1e901ce981..c64ea1db91 100644 --- a/src/Rules/Methods/CallMethodsRule.php +++ b/src/Rules/Methods/CallMethodsRule.php @@ -138,7 +138,7 @@ static function (Type $type) use ($name): bool { $errors = array_merge($errors, $this->check->check( ParametersAcceptorSelector::selectFromArgs( $scope, - $node->args, + $node->getArgs(), $methodReflection->getVariants() ), $scope, diff --git a/src/Rules/Methods/CallStaticMethodsRule.php b/src/Rules/Methods/CallStaticMethodsRule.php index 12d0d16271..68fcacc52f 100644 --- a/src/Rules/Methods/CallStaticMethodsRule.php +++ b/src/Rules/Methods/CallStaticMethodsRule.php @@ -270,7 +270,7 @@ static function (Type $type) use ($methodName): bool { $errors = array_merge($errors, $this->check->check( ParametersAcceptorSelector::selectFromArgs( $scope, - $node->args, + $node->getArgs(), $method->getVariants() ), $scope, diff --git a/src/Rules/Regexp/RegularExpressionPatternRule.php b/src/Rules/Regexp/RegularExpressionPatternRule.php index b1548f40b7..a797073786 100644 --- a/src/Rules/Regexp/RegularExpressionPatternRule.php +++ b/src/Rules/Regexp/RegularExpressionPatternRule.php @@ -52,10 +52,10 @@ private function extractPatterns(FuncCall $functionCall, Scope $scope): array return []; } - if (!isset($functionCall->args[0])) { + if (!isset($functionCall->getArgs()[0])) { return []; } - $patternNode = $functionCall->args[0]->value; + $patternNode = $functionCall->getArgs()[0]->value; $patternType = $scope->getType($patternNode); $patternStrings = []; diff --git a/src/Rules/UnusedFunctionParametersCheck.php b/src/Rules/UnusedFunctionParametersCheck.php index 40233b2c47..856a8c39de 100644 --- a/src/Rules/UnusedFunctionParametersCheck.php +++ b/src/Rules/UnusedFunctionParametersCheck.php @@ -79,7 +79,7 @@ private function getUsedVariables(Scope $scope, $node): array && $node->name instanceof Node\Name && (string) $node->name === 'compact' ) { - foreach ($node->args as $arg) { + foreach ($node->getArgs() as $arg) { $argType = $scope->getType($arg->value); if (!($argType instanceof ConstantStringType)) { continue; diff --git a/src/Rules/Variables/CompactVariablesRule.php b/src/Rules/Variables/CompactVariablesRule.php index 6292893e6a..00dea2da1c 100644 --- a/src/Rules/Variables/CompactVariablesRule.php +++ b/src/Rules/Variables/CompactVariablesRule.php @@ -40,7 +40,7 @@ public function processNode(Node $node, Scope $scope): array return []; } - $functionArguments = $node->args; + $functionArguments = $node->getArgs(); $messages = []; foreach ($functionArguments as $argument) { diff --git a/src/Testing/TypeInferenceTestCase.php b/src/Testing/TypeInferenceTestCase.php index 9b59fd06b0..31f1367eb9 100644 --- a/src/Testing/TypeInferenceTestCase.php +++ b/src/Testing/TypeInferenceTestCase.php @@ -126,16 +126,16 @@ public function gatherAssertTypes(string $file): array $functionName = $nameNode->toString(); if ($functionName === 'PHPStan\\Testing\\assertType') { - $expectedType = $scope->getType($node->args[0]->value); - $actualType = $scope->getType($node->args[1]->value); + $expectedType = $scope->getType($node->getArgs()[0]->value); + $actualType = $scope->getType($node->getArgs()[1]->value); $assert = ['type', $file, $expectedType, $actualType, $node->getLine()]; } elseif ($functionName === 'PHPStan\\Testing\\assertNativeType') { $nativeScope = $scope->doNotTreatPhpDocTypesAsCertain(); - $expectedType = $nativeScope->getNativeType($node->args[0]->value); - $actualType = $nativeScope->getNativeType($node->args[1]->value); + $expectedType = $nativeScope->getNativeType($node->getArgs()[0]->value); + $actualType = $nativeScope->getNativeType($node->getArgs()[1]->value); $assert = ['type', $file, $expectedType, $actualType, $node->getLine()]; } elseif ($functionName === 'PHPStan\\Testing\\assertVariableCertainty') { - $certainty = $node->args[0]->value; + $certainty = $node->getArgs()[0]->value; if (!$certainty instanceof StaticCall) { $this->fail(sprintf('First argument of %s() must be TrinaryLogic call', $functionName)); } @@ -153,7 +153,7 @@ public function gatherAssertTypes(string $file): array // @phpstan-ignore-next-line $expectedertaintyValue = TrinaryLogic::{$certainty->name->toString()}(); - $variable = $node->args[1]->value; + $variable = $node->getArgs()[1]->value; if (!$variable instanceof Node\Expr\Variable) { $this->fail(sprintf('ERROR: Invalid assertVariableCertainty call.')); } @@ -167,7 +167,7 @@ public function gatherAssertTypes(string $file): array return; } - if (count($node->args) !== 2) { + if (count($node->getArgs()) !== 2) { $this->fail(sprintf( 'ERROR: Wrong %s() call on line %d.', $functionName, diff --git a/src/Type/ParserNodeTypeToPHPStanType.php b/src/Type/ParserNodeTypeToPHPStanType.php index 2bc1d2978f..c337f65221 100644 --- a/src/Type/ParserNodeTypeToPHPStanType.php +++ b/src/Type/ParserNodeTypeToPHPStanType.php @@ -2,6 +2,7 @@ namespace PHPStan\Type; +use PhpParser\Node\Identifier; use PhpParser\Node\Name; use PhpParser\Node\NullableType; use PHPStan\Reflection\ClassReflection; @@ -11,7 +12,7 @@ class ParserNodeTypeToPHPStanType { /** - * @param \PhpParser\Node\Name|\PhpParser\Node\Identifier|\PhpParser\Node\NullableType|\PhpParser\Node\UnionType|null $type + * @param \PhpParser\Node\Name|\PhpParser\Node\Identifier|\PhpParser\Node\ComplexType|null $type * @param ClassReflection|null $classReflection * @return Type */ @@ -45,6 +46,15 @@ public static function resolve($type, ?ClassReflection $classReflection): Type } return TypeCombinator::union(...$types); + } elseif ($type instanceof \PhpParser\Node\IntersectionType) { + $types = []; + foreach ($type->types as $intersectionTypeType) { + $types[] = self::resolve($intersectionTypeType, $classReflection); + } + + return TypeCombinator::intersect(...$types); + } elseif (!$type instanceof Identifier) { + throw new \PHPStan\ShouldNotHappenException(get_class($type)); } $type = $type->name; diff --git a/src/Type/Php/ArgumentBasedFunctionReturnTypeExtension.php b/src/Type/Php/ArgumentBasedFunctionReturnTypeExtension.php index 8525cca74f..b4e4942f8a 100644 --- a/src/Type/Php/ArgumentBasedFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArgumentBasedFunctionReturnTypeExtension.php @@ -46,11 +46,11 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, { $argumentPosition = $this->functionNames[$functionReflection->getName()]; - if (!isset($functionCall->args[$argumentPosition])) { + if (!isset($functionCall->getArgs()[$argumentPosition])) { return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); } - $argument = $functionCall->args[$argumentPosition]; + $argument = $functionCall->getArgs()[$argumentPosition]; $argumentType = $scope->getType($argument->value); $argumentKeyType = $argumentType->getIterableKeyType(); $argumentValueType = $argumentType->getIterableValueType(); diff --git a/src/Type/Php/ArrayCombineFunctionReturnTypeExtension.php b/src/Type/Php/ArrayCombineFunctionReturnTypeExtension.php index 34d2fda545..2a6a6854f3 100644 --- a/src/Type/Php/ArrayCombineFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayCombineFunctionReturnTypeExtension.php @@ -36,12 +36,12 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type { - if (count($functionCall->args) < 2) { + if (count($functionCall->getArgs()) < 2) { return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); } - $firstArg = $functionCall->args[0]->value; - $secondArg = $functionCall->args[1]->value; + $firstArg = $functionCall->getArgs()[0]->value; + $secondArg = $functionCall->getArgs()[1]->value; $keysParamType = $scope->getType($firstArg); $valuesParamType = $scope->getType($secondArg); diff --git a/src/Type/Php/ArrayCurrentDynamicReturnTypeExtension.php b/src/Type/Php/ArrayCurrentDynamicReturnTypeExtension.php index a322e4de05..dab9cdf5c8 100644 --- a/src/Type/Php/ArrayCurrentDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayCurrentDynamicReturnTypeExtension.php @@ -20,11 +20,11 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type { - if (!isset($functionCall->args[0])) { + if (!isset($functionCall->getArgs()[0])) { return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); } - $argType = $scope->getType($functionCall->args[0]->value); + $argType = $scope->getType($functionCall->getArgs()[0]->value); $iterableAtLeastOnce = $argType->isIterableAtLeastOnce(); if ($iterableAtLeastOnce->no()) { return new ConstantBooleanType(false); diff --git a/src/Type/Php/ArrayFillFunctionReturnTypeExtension.php b/src/Type/Php/ArrayFillFunctionReturnTypeExtension.php index ab42e9a5d9..6faf9b4279 100644 --- a/src/Type/Php/ArrayFillFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayFillFunctionReturnTypeExtension.php @@ -38,13 +38,13 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type { - if (count($functionCall->args) < 3) { + if (count($functionCall->getArgs()) < 3) { return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); } - $startIndexType = $scope->getType($functionCall->args[0]->value); - $numberType = $scope->getType($functionCall->args[1]->value); - $valueType = $scope->getType($functionCall->args[2]->value); + $startIndexType = $scope->getType($functionCall->getArgs()[0]->value); + $numberType = $scope->getType($functionCall->getArgs()[1]->value); + $valueType = $scope->getType($functionCall->getArgs()[2]->value); if ($numberType instanceof IntegerRangeType) { if ($numberType->getMin() < 0) { diff --git a/src/Type/Php/ArrayFillKeysFunctionReturnTypeExtension.php b/src/Type/Php/ArrayFillKeysFunctionReturnTypeExtension.php index 28ffedb197..74c025957b 100644 --- a/src/Type/Php/ArrayFillKeysFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayFillKeysFunctionReturnTypeExtension.php @@ -22,12 +22,12 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type { - if (count($functionCall->args) < 2) { + if (count($functionCall->getArgs()) < 2) { return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); } - $valueType = $scope->getType($functionCall->args[1]->value); - $keysType = $scope->getType($functionCall->args[0]->value); + $valueType = $scope->getType($functionCall->getArgs()[1]->value); + $keysType = $scope->getType($functionCall->getArgs()[0]->value); $constantArrays = TypeUtils::getConstantArrays($keysType); if (count($constantArrays) === 0) { return new ArrayType($keysType->getIterableValueType(), $valueType); diff --git a/src/Type/Php/ArrayFilterFunctionReturnTypeReturnTypeExtension.php b/src/Type/Php/ArrayFilterFunctionReturnTypeReturnTypeExtension.php index 0876a38dc9..7c074ac65b 100644 --- a/src/Type/Php/ArrayFilterFunctionReturnTypeReturnTypeExtension.php +++ b/src/Type/Php/ArrayFilterFunctionReturnTypeReturnTypeExtension.php @@ -32,9 +32,9 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type { - $arrayArg = $functionCall->args[0]->value ?? null; - $callbackArg = $functionCall->args[1]->value ?? null; - $flagArg = $functionCall->args[2]->value ?? null; + $arrayArg = $functionCall->getArgs()[0]->value ?? null; + $callbackArg = $functionCall->getArgs()[1]->value ?? null; + $flagArg = $functionCall->getArgs()[2]->value ?? null; if ($arrayArg !== null) { $arrayArgType = $scope->getType($arrayArg); diff --git a/src/Type/Php/ArrayFlipFunctionReturnTypeExtension.php b/src/Type/Php/ArrayFlipFunctionReturnTypeExtension.php index 71a499216e..98795c93cb 100644 --- a/src/Type/Php/ArrayFlipFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayFlipFunctionReturnTypeExtension.php @@ -21,11 +21,11 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type { - if (count($functionCall->args) !== 1) { + if (count($functionCall->getArgs()) !== 1) { return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); } - $array = $functionCall->args[0]->value; + $array = $functionCall->getArgs()[0]->value; $argType = $scope->getType($array); if ($argType->isArray()->yes()) { diff --git a/src/Type/Php/ArrayKeyDynamicReturnTypeExtension.php b/src/Type/Php/ArrayKeyDynamicReturnTypeExtension.php index 08cb3013dc..e0f47e6584 100644 --- a/src/Type/Php/ArrayKeyDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayKeyDynamicReturnTypeExtension.php @@ -20,11 +20,11 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type { - if (!isset($functionCall->args[0])) { + if (!isset($functionCall->getArgs()[0])) { return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); } - $argType = $scope->getType($functionCall->args[0]->value); + $argType = $scope->getType($functionCall->getArgs()[0]->value); $iterableAtLeastOnce = $argType->isIterableAtLeastOnce(); if ($iterableAtLeastOnce->no()) { return new NullType(); diff --git a/src/Type/Php/ArrayKeyExistsFunctionTypeSpecifyingExtension.php b/src/Type/Php/ArrayKeyExistsFunctionTypeSpecifyingExtension.php index 837f5e88a9..d6871e09c7 100644 --- a/src/Type/Php/ArrayKeyExistsFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/ArrayKeyExistsFunctionTypeSpecifyingExtension.php @@ -42,10 +42,10 @@ public function specifyTypes( TypeSpecifierContext $context ): SpecifiedTypes { - if (count($node->args) < 2) { + if (count($node->getArgs()) < 2) { return new SpecifiedTypes(); } - $keyType = $scope->getType($node->args[0]->value); + $keyType = $scope->getType($node->getArgs()[0]->value); if ($context->truthy()) { $type = TypeCombinator::intersect( @@ -57,7 +57,7 @@ public function specifyTypes( } return $this->typeSpecifier->create( - $node->args[1]->value, + $node->getArgs()[1]->value, $type, $context, false, diff --git a/src/Type/Php/ArrayKeyFirstDynamicReturnTypeExtension.php b/src/Type/Php/ArrayKeyFirstDynamicReturnTypeExtension.php index c868e7a39e..4e6b2dc47a 100644 --- a/src/Type/Php/ArrayKeyFirstDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayKeyFirstDynamicReturnTypeExtension.php @@ -21,11 +21,11 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type { - if (!isset($functionCall->args[0])) { + if (!isset($functionCall->getArgs()[0])) { return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); } - $argType = $scope->getType($functionCall->args[0]->value); + $argType = $scope->getType($functionCall->getArgs()[0]->value); $iterableAtLeastOnce = $argType->isIterableAtLeastOnce(); if ($iterableAtLeastOnce->no()) { return new NullType(); diff --git a/src/Type/Php/ArrayKeyLastDynamicReturnTypeExtension.php b/src/Type/Php/ArrayKeyLastDynamicReturnTypeExtension.php index 25bec78100..3104c661b6 100644 --- a/src/Type/Php/ArrayKeyLastDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayKeyLastDynamicReturnTypeExtension.php @@ -21,11 +21,11 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type { - if (!isset($functionCall->args[0])) { + if (!isset($functionCall->getArgs()[0])) { return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); } - $argType = $scope->getType($functionCall->args[0]->value); + $argType = $scope->getType($functionCall->getArgs()[0]->value); $iterableAtLeastOnce = $argType->isIterableAtLeastOnce(); if ($iterableAtLeastOnce->no()) { return new NullType(); diff --git a/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php index 056294ffc9..2c624d00c5 100644 --- a/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayKeysFunctionDynamicReturnTypeExtension.php @@ -24,7 +24,7 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type { - $arrayArg = $functionCall->args[0]->value ?? null; + $arrayArg = $functionCall->getArgs()[0]->value ?? null; if ($arrayArg !== null) { $valueType = $scope->getType($arrayArg); if ($valueType->isArray()->yes()) { diff --git a/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php b/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php index 7e2634ba50..543f3ac725 100644 --- a/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php @@ -26,12 +26,12 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type { - if (count($functionCall->args) < 2) { + if (count($functionCall->getArgs()) < 2) { return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); } $valueType = new MixedType(); - $callableType = $scope->getType($functionCall->args[0]->value); + $callableType = $scope->getType($functionCall->getArgs()[0]->value); if ($callableType->isCallable()->yes()) { $valueType = new NeverType(); foreach ($callableType->getCallableParametersAcceptors($scope) as $parametersAcceptor) { @@ -43,10 +43,10 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, new MixedType(), $valueType ); - $arrayType = $scope->getType($functionCall->args[1]->value); + $arrayType = $scope->getType($functionCall->getArgs()[1]->value); $constantArrays = TypeUtils::getConstantArrays($arrayType); - if (!isset($functionCall->args[2])) { + if (!isset($functionCall->getArgs()[2])) { if (count($constantArrays) > 0) { $arrayTypes = []; foreach ($constantArrays as $constantArray) { diff --git a/src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php index ef48894937..5d31918a11 100644 --- a/src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayMergeFunctionDynamicReturnTypeExtension.php @@ -24,14 +24,14 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type { - if (!isset($functionCall->args[0])) { + if (!isset($functionCall->getArgs()[0])) { return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); } $keyTypes = []; $valueTypes = []; $nonEmpty = false; - foreach ($functionCall->args as $arg) { + foreach ($functionCall->getArgs() as $arg) { $argType = $scope->getType($arg->value); if ($arg->unpack) { $argType = $argType->getIterableValueType(); diff --git a/src/Type/Php/ArrayNextDynamicReturnTypeExtension.php b/src/Type/Php/ArrayNextDynamicReturnTypeExtension.php index 5a1a19a0d6..8f46a6d47c 100644 --- a/src/Type/Php/ArrayNextDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayNextDynamicReturnTypeExtension.php @@ -20,11 +20,11 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type { - if (!isset($functionCall->args[0])) { + if (!isset($functionCall->getArgs()[0])) { return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); } - $argType = $scope->getType($functionCall->args[0]->value); + $argType = $scope->getType($functionCall->getArgs()[0]->value); $iterableAtLeastOnce = $argType->isIterableAtLeastOnce(); if ($iterableAtLeastOnce->no()) { return new ConstantBooleanType(false); diff --git a/src/Type/Php/ArrayPointerFunctionsDynamicReturnTypeExtension.php b/src/Type/Php/ArrayPointerFunctionsDynamicReturnTypeExtension.php index b7e9557ded..1e7ed9d642 100644 --- a/src/Type/Php/ArrayPointerFunctionsDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayPointerFunctionsDynamicReturnTypeExtension.php @@ -31,11 +31,11 @@ public function getTypeFromFunctionCall( Scope $scope ): Type { - if (count($functionCall->args) === 0) { + if (count($functionCall->getArgs()) === 0) { return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); } - $argType = $scope->getType($functionCall->args[0]->value); + $argType = $scope->getType($functionCall->getArgs()[0]->value); $iterableAtLeastOnce = $argType->isIterableAtLeastOnce(); if ($iterableAtLeastOnce->no()) { return new ConstantBooleanType(false); diff --git a/src/Type/Php/ArrayPopFunctionReturnTypeExtension.php b/src/Type/Php/ArrayPopFunctionReturnTypeExtension.php index 2e26f43a2b..1e4c2e97b6 100644 --- a/src/Type/Php/ArrayPopFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayPopFunctionReturnTypeExtension.php @@ -21,11 +21,11 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type { - if (!isset($functionCall->args[0])) { + if (!isset($functionCall->getArgs()[0])) { return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); } - $argType = $scope->getType($functionCall->args[0]->value); + $argType = $scope->getType($functionCall->getArgs()[0]->value); $iterableAtLeastOnce = $argType->isIterableAtLeastOnce(); if ($iterableAtLeastOnce->no()) { return new NullType(); diff --git a/src/Type/Php/ArrayRandFunctionReturnTypeExtension.php b/src/Type/Php/ArrayRandFunctionReturnTypeExtension.php index 00444e498f..7172838fb1 100644 --- a/src/Type/Php/ArrayRandFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayRandFunctionReturnTypeExtension.php @@ -24,12 +24,12 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type { - $argsCount = count($functionCall->args); - if (count($functionCall->args) < 1) { + $argsCount = count($functionCall->getArgs()); + if (count($functionCall->getArgs()) < 1) { return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); } - $firstArgType = $scope->getType($functionCall->args[0]->value); + $firstArgType = $scope->getType($functionCall->getArgs()[0]->value); $isInteger = (new IntegerType())->isSuperTypeOf($firstArgType->getIterableKeyType()); $isString = (new StringType())->isSuperTypeOf($firstArgType->getIterableKeyType()); @@ -45,7 +45,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, return $valueType; } - $secondArgType = $scope->getType($functionCall->args[1]->value); + $secondArgType = $scope->getType($functionCall->getArgs()[1]->value); if ($secondArgType instanceof ConstantIntegerType) { if ($secondArgType->getValue() === 1) { diff --git a/src/Type/Php/ArrayReduceFunctionReturnTypeExtension.php b/src/Type/Php/ArrayReduceFunctionReturnTypeExtension.php index ec876b45a7..0d7462c6e3 100644 --- a/src/Type/Php/ArrayReduceFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayReduceFunctionReturnTypeExtension.php @@ -21,28 +21,28 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type { - if (!isset($functionCall->args[1])) { + if (!isset($functionCall->getArgs()[1])) { return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); } - $callbackType = $scope->getType($functionCall->args[1]->value); + $callbackType = $scope->getType($functionCall->getArgs()[1]->value); if ($callbackType->isCallable()->no()) { return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); } $callbackReturnType = ParametersAcceptorSelector::selectFromArgs( $scope, - $functionCall->args, + $functionCall->getArgs(), $callbackType->getCallableParametersAcceptors($scope) )->getReturnType(); - if (isset($functionCall->args[2])) { - $initialType = $scope->getType($functionCall->args[2]->value); + if (isset($functionCall->getArgs()[2])) { + $initialType = $scope->getType($functionCall->getArgs()[2]->value); } else { $initialType = new NullType(); } - $arraysType = $scope->getType($functionCall->args[0]->value); + $arraysType = $scope->getType($functionCall->getArgs()[0]->value); $constantArrays = TypeUtils::getConstantArrays($arraysType); if (count($constantArrays) > 0) { $onlyEmpty = true; diff --git a/src/Type/Php/ArrayReverseFunctionReturnTypeExtension.php b/src/Type/Php/ArrayReverseFunctionReturnTypeExtension.php index 4f893d81ca..37421aa683 100644 --- a/src/Type/Php/ArrayReverseFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayReverseFunctionReturnTypeExtension.php @@ -19,11 +19,11 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type { - if (!isset($functionCall->args[0])) { + if (!isset($functionCall->getArgs()[0])) { return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); } - return $scope->getType($functionCall->args[0]->value); + return $scope->getType($functionCall->getArgs()[0]->value); } } diff --git a/src/Type/Php/ArraySearchFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ArraySearchFunctionDynamicReturnTypeExtension.php index 2c56f6c47c..db63ff4d31 100644 --- a/src/Type/Php/ArraySearchFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArraySearchFunctionDynamicReturnTypeExtension.php @@ -29,12 +29,12 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type { - $argsCount = count($functionCall->args); + $argsCount = count($functionCall->getArgs()); if ($argsCount < 2) { return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); } - $haystackArgType = $scope->getType($functionCall->args[1]->value); + $haystackArgType = $scope->getType($functionCall->getArgs()[1]->value); $haystackIsArray = (new ArrayType(new MixedType(), new MixedType()))->isSuperTypeOf($haystackArgType); if ($haystackIsArray->no()) { return new NullType(); @@ -44,14 +44,14 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, return TypeCombinator::union($haystackArgType->getIterableKeyType(), new ConstantBooleanType(false)); } - $strictArgType = $scope->getType($functionCall->args[2]->value); + $strictArgType = $scope->getType($functionCall->getArgs()[2]->value); if (!($strictArgType instanceof ConstantBooleanType)) { return TypeCombinator::union($haystackArgType->getIterableKeyType(), new ConstantBooleanType(false), new NullType()); } elseif ($strictArgType->getValue() === false) { return TypeCombinator::union($haystackArgType->getIterableKeyType(), new ConstantBooleanType(false)); } - $needleArgType = $scope->getType($functionCall->args[0]->value); + $needleArgType = $scope->getType($functionCall->getArgs()[0]->value); if ($haystackArgType->getIterableValueType()->isSuperTypeOf($needleArgType)->no()) { return new ConstantBooleanType(false); } diff --git a/src/Type/Php/ArrayShiftFunctionReturnTypeExtension.php b/src/Type/Php/ArrayShiftFunctionReturnTypeExtension.php index b7feab7eca..6adf0db00d 100644 --- a/src/Type/Php/ArrayShiftFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayShiftFunctionReturnTypeExtension.php @@ -21,11 +21,11 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type { - if (!isset($functionCall->args[0])) { + if (!isset($functionCall->getArgs()[0])) { return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); } - $argType = $scope->getType($functionCall->args[0]->value); + $argType = $scope->getType($functionCall->getArgs()[0]->value); $iterableAtLeastOnce = $argType->isIterableAtLeastOnce(); if ($iterableAtLeastOnce->no()) { return new NullType(); diff --git a/src/Type/Php/ArraySliceFunctionReturnTypeExtension.php b/src/Type/Php/ArraySliceFunctionReturnTypeExtension.php index ac42cf83f9..316e5f06bb 100644 --- a/src/Type/Php/ArraySliceFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArraySliceFunctionReturnTypeExtension.php @@ -30,7 +30,7 @@ public function getTypeFromFunctionCall( Scope $scope ): Type { - $arrayArg = $functionCall->args[0]->value ?? null; + $arrayArg = $functionCall->getArgs()[0]->value ?? null; if ($arrayArg === null) { return new ArrayType( @@ -41,8 +41,8 @@ public function getTypeFromFunctionCall( $valueType = $scope->getType($arrayArg); - if (isset($functionCall->args[1])) { - $offset = $scope->getType($functionCall->args[1]->value); + if (isset($functionCall->getArgs()[1])) { + $offset = $scope->getType($functionCall->getArgs()[1]->value); if (!$offset instanceof ConstantIntegerType) { $offset = new ConstantIntegerType(0); } @@ -50,8 +50,8 @@ public function getTypeFromFunctionCall( $offset = new ConstantIntegerType(0); } - if (isset($functionCall->args[2])) { - $limit = $scope->getType($functionCall->args[2]->value); + if (isset($functionCall->getArgs()[2])) { + $limit = $scope->getType($functionCall->getArgs()[2]->value); if (!$limit instanceof ConstantIntegerType) { $limit = new NullType(); } @@ -71,8 +71,8 @@ public function getTypeFromFunctionCall( ); } - if (isset($functionCall->args[3])) { - $preserveKeys = $scope->getType($functionCall->args[3]->value); + if (isset($functionCall->getArgs()[3])) { + $preserveKeys = $scope->getType($functionCall->getArgs()[3]->value); $preserveKeys = (new ConstantBooleanType(true))->isSuperTypeOf($preserveKeys)->yes(); } else { $preserveKeys = false; diff --git a/src/Type/Php/ArraySumFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ArraySumFunctionDynamicReturnTypeExtension.php index aa3cca722f..03a5d15910 100644 --- a/src/Type/Php/ArraySumFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArraySumFunctionDynamicReturnTypeExtension.php @@ -24,11 +24,11 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type { - if (!isset($functionCall->args[0])) { + if (!isset($functionCall->getArgs()[0])) { return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); } - $arrayType = $scope->getType($functionCall->args[0]->value); + $arrayType = $scope->getType($functionCall->getArgs()[0]->value); $itemType = $arrayType->getIterableValueType(); if ($arrayType->isIterableAtLeastOnce()->no()) { diff --git a/src/Type/Php/ArrayValuesFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ArrayValuesFunctionDynamicReturnTypeExtension.php index 0c43caa801..82c037f45c 100644 --- a/src/Type/Php/ArrayValuesFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ArrayValuesFunctionDynamicReturnTypeExtension.php @@ -23,7 +23,7 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type { - $arrayArg = $functionCall->args[0]->value ?? null; + $arrayArg = $functionCall->getArgs()[0]->value ?? null; if ($arrayArg !== null) { $valueType = $scope->getType($arrayArg); if ($valueType->isArray()->yes()) { diff --git a/src/Type/Php/AssertFunctionTypeSpecifyingExtension.php b/src/Type/Php/AssertFunctionTypeSpecifyingExtension.php index c6412e9339..b47af84fd5 100644 --- a/src/Type/Php/AssertFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/AssertFunctionTypeSpecifyingExtension.php @@ -19,12 +19,12 @@ class AssertFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExt public function isFunctionSupported(FunctionReflection $functionReflection, FuncCall $node, TypeSpecifierContext $context): bool { return $functionReflection->getName() === 'assert' - && isset($node->args[0]); + && isset($node->getArgs()[0]); } public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes { - return $this->typeSpecifier->specifyTypesInCondition($scope, $node->args[0]->value, TypeSpecifierContext::createTruthy()); + return $this->typeSpecifier->specifyTypesInCondition($scope, $node->getArgs()[0]->value, TypeSpecifierContext::createTruthy()); } public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void diff --git a/src/Type/Php/Base64DecodeDynamicFunctionReturnTypeExtension.php b/src/Type/Php/Base64DecodeDynamicFunctionReturnTypeExtension.php index dc36db80d4..bf204f5e01 100644 --- a/src/Type/Php/Base64DecodeDynamicFunctionReturnTypeExtension.php +++ b/src/Type/Php/Base64DecodeDynamicFunctionReturnTypeExtension.php @@ -26,11 +26,11 @@ public function getTypeFromFunctionCall( Scope $scope ): Type { - if (!isset($functionCall->args[1])) { + if (!isset($functionCall->getArgs()[1])) { return new StringType(); } - $argType = $scope->getType($functionCall->args[1]->value); + $argType = $scope->getType($functionCall->getArgs()[1]->value); if ($argType instanceof MixedType) { return new BenevolentUnionType([new StringType(), new ConstantBooleanType(false)]); diff --git a/src/Type/Php/BcMathStringOrNullReturnTypeExtension.php b/src/Type/Php/BcMathStringOrNullReturnTypeExtension.php index a3bc589d4f..0c1bf2e22b 100644 --- a/src/Type/Php/BcMathStringOrNullReturnTypeExtension.php +++ b/src/Type/Php/BcMathStringOrNullReturnTypeExtension.php @@ -41,18 +41,18 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $defaultReturnType = new UnionType([$stringAndNumericStringType, new NullType()]); - if (isset($functionCall->args[1]) === false) { + if (isset($functionCall->getArgs()[1]) === false) { return $stringAndNumericStringType; } - $secondArgument = $scope->getType($functionCall->args[1]->value); + $secondArgument = $scope->getType($functionCall->getArgs()[1]->value); $secondArgumentIsNumeric = ($secondArgument instanceof ConstantScalarType && is_numeric($secondArgument->getValue())) || $secondArgument instanceof IntegerType; if ($secondArgument instanceof ConstantScalarType && ($this->isZero($secondArgument->getValue()) || !$secondArgumentIsNumeric)) { return new NullType(); } - if (isset($functionCall->args[2]) === false) { + if (isset($functionCall->getArgs()[2]) === false) { if ($secondArgument instanceof ConstantScalarType || $secondArgumentIsNumeric) { return $stringAndNumericStringType; } @@ -60,7 +60,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, return $defaultReturnType; } - $thirdArgument = $scope->getType($functionCall->args[2]->value); + $thirdArgument = $scope->getType($functionCall->getArgs()[2]->value); $thirdArgumentIsNumeric = ($thirdArgument instanceof ConstantScalarType && is_numeric($thirdArgument->getValue())) || $thirdArgument instanceof IntegerType; if ($thirdArgument instanceof ConstantScalarType && !is_numeric($thirdArgument->getValue())) { @@ -88,11 +88,11 @@ private function getTypeForBcSqrt(FuncCall $functionCall, Scope $scope): Type $stringAndNumericStringType = TypeCombinator::intersect(new StringType(), new AccessoryNumericStringType()); $defaultReturnType = new UnionType([$stringAndNumericStringType, new NullType()]); - if (isset($functionCall->args[0]) === false) { + if (isset($functionCall->getArgs()[0]) === false) { return $defaultReturnType; } - $firstArgument = $scope->getType($functionCall->args[0]->value); + $firstArgument = $scope->getType($functionCall->getArgs()[0]->value); $firstArgumentIsPositive = $firstArgument instanceof ConstantScalarType && is_numeric($firstArgument->getValue()) && $firstArgument->getValue() >= 0; $firstArgumentIsNegative = $firstArgument instanceof ConstantScalarType && is_numeric($firstArgument->getValue()) && $firstArgument->getValue() < 0; @@ -102,7 +102,7 @@ private function getTypeForBcSqrt(FuncCall $functionCall, Scope $scope): Type return new NullType(); } - if (isset($functionCall->args[1]) === false) { + if (isset($functionCall->getArgs()[1]) === false) { if ($firstArgumentIsPositive) { return $stringAndNumericStringType; } @@ -110,7 +110,7 @@ private function getTypeForBcSqrt(FuncCall $functionCall, Scope $scope): Type return $defaultReturnType; } - $secondArgument = $scope->getType($functionCall->args[1]->value); + $secondArgument = $scope->getType($functionCall->getArgs()[1]->value); $secondArgumentIsValid = $secondArgument instanceof ConstantScalarType && is_numeric($secondArgument->getValue()) && !$this->isZero($secondArgument->getValue()); $secondArgumentIsNonNumeric = $secondArgument instanceof ConstantScalarType && !is_numeric($secondArgument->getValue()); @@ -137,11 +137,11 @@ private function getTypeForBcPowMod(FuncCall $functionCall, Scope $scope): Type { $stringAndNumericStringType = TypeCombinator::intersect(new StringType(), new AccessoryNumericStringType()); - if (isset($functionCall->args[1]) === false) { + if (isset($functionCall->getArgs()[1]) === false) { return new UnionType([$stringAndNumericStringType, new ConstantBooleanType(false)]); } - $exponent = $scope->getType($functionCall->args[1]->value); + $exponent = $scope->getType($functionCall->getArgs()[1]->value); $exponentIsNegative = IntegerRangeType::fromInterval(null, 0)->isSuperTypeOf($exponent)->yes(); if ($exponent instanceof ConstantScalarType) { @@ -152,8 +152,8 @@ private function getTypeForBcPowMod(FuncCall $functionCall, Scope $scope): Type return new ConstantBooleanType(false); } - if (isset($functionCall->args[2])) { - $modulus = $scope->getType($functionCall->args[2]->value); + if (isset($functionCall->getArgs()[2])) { + $modulus = $scope->getType($functionCall->getArgs()[2]->value); $modulusIsZero = $modulus instanceof ConstantScalarType && $this->isZero($modulus->getValue()); $modulusIsNonNumeric = $modulus instanceof ConstantScalarType && !is_numeric($modulus->getValue()); diff --git a/src/Type/Php/ClassExistsFunctionTypeSpecifyingExtension.php b/src/Type/Php/ClassExistsFunctionTypeSpecifyingExtension.php index 1c78cb457b..771c5f924f 100644 --- a/src/Type/Php/ClassExistsFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/ClassExistsFunctionTypeSpecifyingExtension.php @@ -34,12 +34,12 @@ public function isFunctionSupported( 'class_exists', 'interface_exists', 'trait_exists', - ], true) && isset($node->args[0]) && $context->truthy(); + ], true) && isset($node->getArgs()[0]) && $context->truthy(); } public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes { - $argType = $scope->getType($node->args[0]->value); + $argType = $scope->getType($node->getArgs()[0]->value); $classStringType = new ClassStringType(); if (TypeCombinator::intersect($argType, $classStringType) instanceof NeverType) { if ($argType instanceof ConstantStringType) { @@ -58,7 +58,7 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n } return $this->typeSpecifier->create( - $node->args[0]->value, + $node->getArgs()[0]->value, $classStringType, $context, false, diff --git a/src/Type/Php/ClosureBindDynamicReturnTypeExtension.php b/src/Type/Php/ClosureBindDynamicReturnTypeExtension.php index 9f64a13b7a..e8d12490e3 100644 --- a/src/Type/Php/ClosureBindDynamicReturnTypeExtension.php +++ b/src/Type/Php/ClosureBindDynamicReturnTypeExtension.php @@ -24,7 +24,7 @@ public function isStaticMethodSupported(MethodReflection $methodReflection): boo public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): Type { - $closureType = $scope->getType($methodCall->args[0]->value); + $closureType = $scope->getType($methodCall->getArgs()[0]->value); if (!($closureType instanceof ClosureType)) { return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); } diff --git a/src/Type/Php/ClosureFromCallableDynamicReturnTypeExtension.php b/src/Type/Php/ClosureFromCallableDynamicReturnTypeExtension.php index 39e048a883..925b272ddd 100644 --- a/src/Type/Php/ClosureFromCallableDynamicReturnTypeExtension.php +++ b/src/Type/Php/ClosureFromCallableDynamicReturnTypeExtension.php @@ -26,15 +26,15 @@ public function isStaticMethodSupported(MethodReflection $methodReflection): boo public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): Type { - if (!isset($methodCall->args[0])) { + if (!isset($methodCall->getArgs()[0])) { return ParametersAcceptorSelector::selectFromArgs( $scope, - $methodCall->args, + $methodCall->getArgs(), $methodReflection->getVariants() )->getReturnType(); } - $callableType = $scope->getType($methodCall->args[0]->value); + $callableType = $scope->getType($methodCall->getArgs()[0]->value); if ($callableType->isCallable()->no()) { return new ErrorType(); } diff --git a/src/Type/Php/CompactFunctionReturnTypeExtension.php b/src/Type/Php/CompactFunctionReturnTypeExtension.php index ccd1cb8e47..08a11322e7 100644 --- a/src/Type/Php/CompactFunctionReturnTypeExtension.php +++ b/src/Type/Php/CompactFunctionReturnTypeExtension.php @@ -34,7 +34,7 @@ public function getTypeFromFunctionCall( ): Type { $defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - if (count($functionCall->args) === 0) { + if (count($functionCall->getArgs()) === 0) { return $defaultReturnType; } @@ -43,7 +43,7 @@ public function getTypeFromFunctionCall( } $array = ConstantArrayTypeBuilder::createEmpty(); - foreach ($functionCall->args as $arg) { + foreach ($functionCall->getArgs() as $arg) { $type = $scope->getType($arg->value); $constantStrings = $this->findConstantStrings($type); if ($constantStrings === null) { diff --git a/src/Type/Php/CountFunctionReturnTypeExtension.php b/src/Type/Php/CountFunctionReturnTypeExtension.php index 542c011026..a506d1945a 100644 --- a/src/Type/Php/CountFunctionReturnTypeExtension.php +++ b/src/Type/Php/CountFunctionReturnTypeExtension.php @@ -26,19 +26,19 @@ public function getTypeFromFunctionCall( Scope $scope ): Type { - if (count($functionCall->args) < 1) { + if (count($functionCall->getArgs()) < 1) { return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); } - if (count($functionCall->args) > 1) { - $mode = $scope->getType($functionCall->args[1]->value); + if (count($functionCall->getArgs()) > 1) { + $mode = $scope->getType($functionCall->getArgs()[1]->value); if ($mode->isSuperTypeOf(new ConstantIntegerType(\COUNT_RECURSIVE))->yes()) { return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); } } - $argType = $scope->getType($functionCall->args[0]->value); - $constantArrays = TypeUtils::getConstantArrays($scope->getType($functionCall->args[0]->value)); + $argType = $scope->getType($functionCall->getArgs()[0]->value); + $constantArrays = TypeUtils::getConstantArrays($scope->getType($functionCall->getArgs()[0]->value)); if (count($constantArrays) === 0) { if ($argType->isIterableAtLeastOnce()->yes()) { return IntegerRangeType::fromInterval(1, null); diff --git a/src/Type/Php/CountFunctionTypeSpecifyingExtension.php b/src/Type/Php/CountFunctionTypeSpecifyingExtension.php index df5b19df30..be461f08d8 100644 --- a/src/Type/Php/CountFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/CountFunctionTypeSpecifyingExtension.php @@ -26,7 +26,7 @@ public function isFunctionSupported( ): bool { return !$context->null() - && count($node->args) >= 1 + && count($node->getArgs()) >= 1 && in_array($functionReflection->getName(), ['sizeof', 'count'], true); } @@ -37,11 +37,11 @@ public function specifyTypes( TypeSpecifierContext $context ): SpecifiedTypes { - if (!(new ArrayType(new MixedType(), new MixedType()))->isSuperTypeOf($scope->getType($node->args[0]->value))->yes()) { + if (!(new ArrayType(new MixedType(), new MixedType()))->isSuperTypeOf($scope->getType($node->getArgs()[0]->value))->yes()) { return new SpecifiedTypes([], []); } - return $this->typeSpecifier->create($node->args[0]->value, new NonEmptyArrayType(), $context, false, $scope); + return $this->typeSpecifier->create($node->getArgs()[0]->value, new NonEmptyArrayType(), $context, false, $scope); } public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void diff --git a/src/Type/Php/CurlInitReturnTypeExtension.php b/src/Type/Php/CurlInitReturnTypeExtension.php index 6777c14666..f837c7cb7b 100644 --- a/src/Type/Php/CurlInitReturnTypeExtension.php +++ b/src/Type/Php/CurlInitReturnTypeExtension.php @@ -23,7 +23,7 @@ public function getTypeFromFunctionCall( Scope $scope ): Type { - $argsCount = count($functionCall->args); + $argsCount = count($functionCall->getArgs()); $returnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); if ($argsCount === 0) { return TypeCombinator::remove($returnType, new ConstantBooleanType(false)); diff --git a/src/Type/Php/DateFunctionReturnTypeExtension.php b/src/Type/Php/DateFunctionReturnTypeExtension.php index 7d4792c03f..5821cd1cea 100644 --- a/src/Type/Php/DateFunctionReturnTypeExtension.php +++ b/src/Type/Php/DateFunctionReturnTypeExtension.php @@ -26,10 +26,10 @@ public function getTypeFromFunctionCall( Scope $scope ): Type { - if (count($functionCall->args) === 0) { + if (count($functionCall->getArgs()) === 0) { return new StringType(); } - $argType = $scope->getType($functionCall->args[0]->value); + $argType = $scope->getType($functionCall->getArgs()[0]->value); $constantStrings = TypeUtils::getConstantStrings($argType); if (count($constantStrings) === 0) { return new StringType(); diff --git a/src/Type/Php/DateIntervalConstructorThrowTypeExtension.php b/src/Type/Php/DateIntervalConstructorThrowTypeExtension.php index 047d196f61..4a31169303 100644 --- a/src/Type/Php/DateIntervalConstructorThrowTypeExtension.php +++ b/src/Type/Php/DateIntervalConstructorThrowTypeExtension.php @@ -22,11 +22,11 @@ public function isStaticMethodSupported(MethodReflection $methodReflection): boo public function getThrowTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): ?Type { - if (count($methodCall->args) === 0) { + if (count($methodCall->getArgs()) === 0) { return $methodReflection->getThrowType(); } - $valueType = $scope->getType($methodCall->args[0]->value); + $valueType = $scope->getType($methodCall->getArgs()[0]->value); $constantStrings = TypeUtils::getConstantStrings($valueType); foreach ($constantStrings as $constantString) { diff --git a/src/Type/Php/DateTimeConstructorThrowTypeExtension.php b/src/Type/Php/DateTimeConstructorThrowTypeExtension.php index 102996ff9d..a07800298d 100644 --- a/src/Type/Php/DateTimeConstructorThrowTypeExtension.php +++ b/src/Type/Php/DateTimeConstructorThrowTypeExtension.php @@ -23,11 +23,11 @@ public function isStaticMethodSupported(MethodReflection $methodReflection): boo public function getThrowTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): ?Type { - if (count($methodCall->args) === 0) { + if (count($methodCall->getArgs()) === 0) { return null; } - $valueType = $scope->getType($methodCall->args[0]->value); + $valueType = $scope->getType($methodCall->getArgs()[0]->value); $constantStrings = TypeUtils::getConstantStrings($valueType); foreach ($constantStrings as $constantString) { diff --git a/src/Type/Php/DateTimeDynamicReturnTypeExtension.php b/src/Type/Php/DateTimeDynamicReturnTypeExtension.php index 48e3820856..988a91ff82 100644 --- a/src/Type/Php/DateTimeDynamicReturnTypeExtension.php +++ b/src/Type/Php/DateTimeDynamicReturnTypeExtension.php @@ -26,12 +26,12 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, { $defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - if (count($functionCall->args) < 2) { + if (count($functionCall->getArgs()) < 2) { return $defaultReturnType; } - $format = $scope->getType($functionCall->args[0]->value); - $datetime = $scope->getType($functionCall->args[1]->value); + $format = $scope->getType($functionCall->getArgs()[0]->value); + $datetime = $scope->getType($functionCall->getArgs()[1]->value); if (!$format instanceof ConstantStringType || !$datetime instanceof ConstantStringType) { return $defaultReturnType; diff --git a/src/Type/Php/DefineConstantTypeSpecifyingExtension.php b/src/Type/Php/DefineConstantTypeSpecifyingExtension.php index 0ac5590dca..f1fbcc3e27 100644 --- a/src/Type/Php/DefineConstantTypeSpecifyingExtension.php +++ b/src/Type/Php/DefineConstantTypeSpecifyingExtension.php @@ -30,7 +30,7 @@ public function isFunctionSupported( { return $functionReflection->getName() === 'define' && $context->null() - && count($node->args) >= 2; + && count($node->getArgs()) >= 2; } public function specifyTypes( @@ -40,7 +40,7 @@ public function specifyTypes( TypeSpecifierContext $context ): SpecifiedTypes { - $constantName = $scope->getType($node->args[0]->value); + $constantName = $scope->getType($node->getArgs()[0]->value); if ( !$constantName instanceof ConstantStringType || $constantName->getValue() === '' @@ -52,7 +52,7 @@ public function specifyTypes( new \PhpParser\Node\Expr\ConstFetch( new \PhpParser\Node\Name\FullyQualified($constantName->getValue()) ), - $scope->getType($node->args[1]->value), + $scope->getType($node->getArgs()[1]->value), TypeSpecifierContext::createTruthy(), false, $scope diff --git a/src/Type/Php/DefinedConstantTypeSpecifyingExtension.php b/src/Type/Php/DefinedConstantTypeSpecifyingExtension.php index 9ce72c1e1b..12458f0093 100644 --- a/src/Type/Php/DefinedConstantTypeSpecifyingExtension.php +++ b/src/Type/Php/DefinedConstantTypeSpecifyingExtension.php @@ -30,7 +30,7 @@ public function isFunctionSupported( ): bool { return $functionReflection->getName() === 'defined' - && count($node->args) >= 1 + && count($node->getArgs()) >= 1 && !$context->null(); } @@ -41,7 +41,7 @@ public function specifyTypes( TypeSpecifierContext $context ): SpecifiedTypes { - $constantName = $scope->getType($node->args[0]->value); + $constantName = $scope->getType($node->getArgs()[0]->value); if ( !$constantName instanceof ConstantStringType || $constantName->getValue() === '' diff --git a/src/Type/Php/DsMapDynamicReturnTypeExtension.php b/src/Type/Php/DsMapDynamicReturnTypeExtension.php index faf1f5bbeb..d4a5719487 100644 --- a/src/Type/Php/DsMapDynamicReturnTypeExtension.php +++ b/src/Type/Php/DsMapDynamicReturnTypeExtension.php @@ -30,11 +30,11 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method { $returnType = ParametersAcceptorSelector::selectFromArgs( $scope, - $methodCall->args, + $methodCall->getArgs(), $methodReflection->getVariants() )->getReturnType(); - if (count($methodCall->args) > 1) { + if (count($methodCall->getArgs()) > 1) { return $returnType; } diff --git a/src/Type/Php/ExplodeFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ExplodeFunctionDynamicReturnTypeExtension.php index 723ab81877..f7307652cb 100644 --- a/src/Type/Php/ExplodeFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ExplodeFunctionDynamicReturnTypeExtension.php @@ -41,11 +41,11 @@ public function getTypeFromFunctionCall( Scope $scope ): Type { - if (count($functionCall->args) < 2) { + if (count($functionCall->getArgs()) < 2) { return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); } - $delimiterType = $scope->getType($functionCall->args[0]->value); + $delimiterType = $scope->getType($functionCall->getArgs()[0]->value); $isSuperset = (new ConstantStringType(''))->isSuperTypeOf($delimiterType); if ($isSuperset->yes()) { if ($this->phpVersion->getVersionId() >= 80000) { @@ -55,8 +55,8 @@ public function getTypeFromFunctionCall( } elseif ($isSuperset->no()) { $arrayType = new ArrayType(new IntegerType(), new StringType()); if ( - !isset($functionCall->args[2]) - || IntegerRangeType::fromInterval(0, null)->isSuperTypeOf($scope->getType($functionCall->args[2]->value))->yes() + !isset($functionCall->getArgs()[2]) + || IntegerRangeType::fromInterval(0, null)->isSuperTypeOf($scope->getType($functionCall->getArgs()[2]->value))->yes() ) { return TypeCombinator::intersect($arrayType, new NonEmptyArrayType()); } diff --git a/src/Type/Php/FilterVarDynamicReturnTypeExtension.php b/src/Type/Php/FilterVarDynamicReturnTypeExtension.php index 0de45a325f..e768567ab1 100644 --- a/src/Type/Php/FilterVarDynamicReturnTypeExtension.php +++ b/src/Type/Php/FilterVarDynamicReturnTypeExtension.php @@ -116,7 +116,7 @@ public function getTypeFromFunctionCall( { $mixedType = new MixedType(); - $filterArg = $functionCall->args[1] ?? null; + $filterArg = $functionCall->getArgs()[1] ?? null; if ($filterArg === null) { $filterValue = $this->getConstant('FILTER_DEFAULT'); } else { @@ -127,8 +127,8 @@ public function getTypeFromFunctionCall( $filterValue = $filterType->getValue(); } - $flagsArg = $functionCall->args[2] ?? null; - $inputType = $scope->getType($functionCall->args[0]->value); + $flagsArg = $functionCall->getArgs()[2] ?? null; + $inputType = $scope->getType($functionCall->getArgs()[0]->value); $exactType = $this->determineExactType($inputType, $filterValue); if ($exactType !== null) { $type = $exactType; diff --git a/src/Type/Php/GetClassDynamicReturnTypeExtension.php b/src/Type/Php/GetClassDynamicReturnTypeExtension.php index b2a62501f9..ac2b38eb8e 100644 --- a/src/Type/Php/GetClassDynamicReturnTypeExtension.php +++ b/src/Type/Php/GetClassDynamicReturnTypeExtension.php @@ -30,7 +30,7 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type { - $args = $functionCall->args; + $args = $functionCall->getArgs(); if (count($args) === 0) { if ($scope->isInClass()) { return new ConstantStringType($scope->getClassReflection()->getName(), true); diff --git a/src/Type/Php/GetParentClassDynamicFunctionReturnTypeExtension.php b/src/Type/Php/GetParentClassDynamicFunctionReturnTypeExtension.php index b834633536..41caad5470 100644 --- a/src/Type/Php/GetParentClassDynamicFunctionReturnTypeExtension.php +++ b/src/Type/Php/GetParentClassDynamicFunctionReturnTypeExtension.php @@ -40,7 +40,7 @@ public function getTypeFromFunctionCall( $defaultReturnType = ParametersAcceptorSelector::selectSingle( $functionReflection->getVariants() )->getReturnType(); - if (count($functionCall->args) === 0) { + if (count($functionCall->getArgs()) === 0) { if ($scope->isInTrait()) { return $defaultReturnType; } @@ -53,7 +53,7 @@ public function getTypeFromFunctionCall( return new ConstantBooleanType(false); } - $argType = $scope->getType($functionCall->args[0]->value); + $argType = $scope->getType($functionCall->getArgs()[0]->value); if ($scope->isInTrait() && TypeUtils::findThisType($argType) !== null) { return $defaultReturnType; } diff --git a/src/Type/Php/GettimeofdayDynamicFunctionReturnTypeExtension.php b/src/Type/Php/GettimeofdayDynamicFunctionReturnTypeExtension.php index 8fdb910688..ac25fd63cd 100644 --- a/src/Type/Php/GettimeofdayDynamicFunctionReturnTypeExtension.php +++ b/src/Type/Php/GettimeofdayDynamicFunctionReturnTypeExtension.php @@ -38,11 +38,11 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, ]); $floatType = new FloatType(); - if (!isset($functionCall->args[0])) { + if (!isset($functionCall->getArgs()[0])) { return $arrayType; } - $argType = $scope->getType($functionCall->args[0]->value); + $argType = $scope->getType($functionCall->getArgs()[0]->value); $isTrueType = (new ConstantBooleanType(true))->isSuperTypeOf($argType); $isFalseType = (new ConstantBooleanType(false))->isSuperTypeOf($argType); $compareTypes = $isTrueType->compareTo($isFalseType); diff --git a/src/Type/Php/HashFunctionsReturnTypeExtension.php b/src/Type/Php/HashFunctionsReturnTypeExtension.php index 402bc24196..f977e240ad 100644 --- a/src/Type/Php/HashFunctionsReturnTypeExtension.php +++ b/src/Type/Php/HashFunctionsReturnTypeExtension.php @@ -25,11 +25,11 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, { $defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - if (!isset($functionCall->args[0])) { + if (!isset($functionCall->getArgs()[0])) { return $defaultReturnType; } - $argType = $scope->getType($functionCall->args[0]->value); + $argType = $scope->getType($functionCall->getArgs()[0]->value); if ($argType instanceof MixedType) { return TypeUtils::toBenevolentUnion($defaultReturnType); } diff --git a/src/Type/Php/HashHmacFunctionsReturnTypeExtension.php b/src/Type/Php/HashHmacFunctionsReturnTypeExtension.php index 208b55ed24..afc607f6e1 100644 --- a/src/Type/Php/HashHmacFunctionsReturnTypeExtension.php +++ b/src/Type/Php/HashHmacFunctionsReturnTypeExtension.php @@ -76,11 +76,11 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); } - if (!isset($functionCall->args[0])) { + if (!isset($functionCall->getArgs()[0])) { return $defaultReturnType; } - $argType = $scope->getType($functionCall->args[0]->value); + $argType = $scope->getType($functionCall->getArgs()[0]->value); if ($argType instanceof MixedType) { return TypeUtils::toBenevolentUnion($defaultReturnType); } diff --git a/src/Type/Php/HrtimeFunctionReturnTypeExtension.php b/src/Type/Php/HrtimeFunctionReturnTypeExtension.php index 44e8581750..0d390f7978 100644 --- a/src/Type/Php/HrtimeFunctionReturnTypeExtension.php +++ b/src/Type/Php/HrtimeFunctionReturnTypeExtension.php @@ -27,11 +27,11 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $arrayType = new ConstantArrayType([new ConstantIntegerType(0), new ConstantIntegerType(1)], [new IntegerType(), new IntegerType()], 2); $numberType = TypeUtils::toBenevolentUnion(TypeCombinator::union(new IntegerType(), new FloatType())); - if (count($functionCall->args) < 1) { + if (count($functionCall->getArgs()) < 1) { return $arrayType; } - $argType = $scope->getType($functionCall->args[0]->value); + $argType = $scope->getType($functionCall->getArgs()[0]->value); $isTrueType = (new ConstantBooleanType(true))->isSuperTypeOf($argType); $isFalseType = (new ConstantBooleanType(false))->isSuperTypeOf($argType); $compareTypes = $isTrueType->compareTo($isFalseType); diff --git a/src/Type/Php/ImplodeFunctionReturnTypeExtension.php b/src/Type/Php/ImplodeFunctionReturnTypeExtension.php index 2321c44b68..ef18dc3272 100644 --- a/src/Type/Php/ImplodeFunctionReturnTypeExtension.php +++ b/src/Type/Php/ImplodeFunctionReturnTypeExtension.php @@ -28,7 +28,7 @@ public function getTypeFromFunctionCall( Scope $scope ): \PHPStan\Type\Type { - $args = $functionCall->args; + $args = $functionCall->getArgs(); if (count($args) === 1) { $argType = $scope->getType($args[0]->value); if ($argType->isArray()->yes()) { diff --git a/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php b/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php index 8310a0207f..8e7fc5adfc 100644 --- a/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php @@ -31,22 +31,22 @@ public function isFunctionSupported(FunctionReflection $functionReflection, Func public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes { - if (count($node->args) < 3) { + if (count($node->getArgs()) < 3) { return new SpecifiedTypes(); } - $strictNodeType = $scope->getType($node->args[2]->value); + $strictNodeType = $scope->getType($node->getArgs()[2]->value); if (!(new ConstantBooleanType(true))->isSuperTypeOf($strictNodeType)->yes()) { return new SpecifiedTypes([], []); } - $arrayValueType = $scope->getType($node->args[1]->value)->getIterableValueType(); + $arrayValueType = $scope->getType($node->getArgs()[1]->value)->getIterableValueType(); if ( $context->truthy() || count(TypeUtils::getConstantScalars($arrayValueType)) > 0 ) { return $this->typeSpecifier->create( - $node->args[0]->value, + $node->getArgs()[0]->value, $arrayValueType, $context, false, diff --git a/src/Type/Php/IntdivThrowTypeExtension.php b/src/Type/Php/IntdivThrowTypeExtension.php index 5145a78920..0da850864a 100644 --- a/src/Type/Php/IntdivThrowTypeExtension.php +++ b/src/Type/Php/IntdivThrowTypeExtension.php @@ -22,12 +22,12 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getThrowTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $funcCall, Scope $scope): ?Type { - if (count($funcCall->args) < 2) { + if (count($funcCall->getArgs()) < 2) { return $functionReflection->getThrowType(); } $containsMin = false; - $valueType = $scope->getType($funcCall->args[0]->value); + $valueType = $scope->getType($funcCall->getArgs()[0]->value); foreach (TypeUtils::getConstantScalars($valueType) as $constantScalarType) { if ($constantScalarType->getValue() === PHP_INT_MIN) { $containsMin = true; @@ -41,7 +41,7 @@ public function getThrowTypeFromFunctionCall(FunctionReflection $functionReflect } $divisionByZero = false; - $divisorType = $scope->getType($funcCall->args[1]->value); + $divisorType = $scope->getType($funcCall->getArgs()[1]->value); foreach (TypeUtils::getConstantScalars($divisorType) as $constantScalarType) { if ($containsMin && $constantScalarType->getValue() === -1) { return new ObjectType(\ArithmeticError::class); diff --git a/src/Type/Php/IsAFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsAFunctionTypeSpecifyingExtension.php index 4ee1670161..92234ef118 100644 --- a/src/Type/Php/IsAFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsAFunctionTypeSpecifyingExtension.php @@ -27,8 +27,8 @@ class IsAFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtens public function isFunctionSupported(FunctionReflection $functionReflection, FuncCall $node, TypeSpecifierContext $context): bool { return strtolower($functionReflection->getName()) === 'is_a' - && isset($node->args[0]) - && isset($node->args[1]) + && isset($node->getArgs()[0]) + && isset($node->getArgs()[1]) && !$context->null(); } @@ -38,7 +38,7 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n throw new \PHPStan\ShouldNotHappenException(); } - $classNameArgExpr = $node->args[1]->value; + $classNameArgExpr = $node->getArgs()[1]->value; $classNameArgExprType = $scope->getType($classNameArgExpr); if ( $classNameArgExpr instanceof ClassConstFetch @@ -47,24 +47,24 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n && strtolower($classNameArgExpr->name->name) === 'class' ) { $objectType = $scope->resolveTypeByName($classNameArgExpr->class); - $types = $this->typeSpecifier->create($node->args[0]->value, $objectType, $context, false, $scope); + $types = $this->typeSpecifier->create($node->getArgs()[0]->value, $objectType, $context, false, $scope); } elseif ($classNameArgExprType instanceof ConstantStringType) { $objectType = new ObjectType($classNameArgExprType->getValue()); - $types = $this->typeSpecifier->create($node->args[0]->value, $objectType, $context, false, $scope); + $types = $this->typeSpecifier->create($node->getArgs()[0]->value, $objectType, $context, false, $scope); } elseif ($classNameArgExprType instanceof GenericClassStringType) { $objectType = $classNameArgExprType->getGenericType(); - $types = $this->typeSpecifier->create($node->args[0]->value, $objectType, $context, false, $scope); + $types = $this->typeSpecifier->create($node->getArgs()[0]->value, $objectType, $context, false, $scope); } elseif ($context->true()) { $objectType = new ObjectWithoutClassType(); - $types = $this->typeSpecifier->create($node->args[0]->value, $objectType, $context, false, $scope); + $types = $this->typeSpecifier->create($node->getArgs()[0]->value, $objectType, $context, false, $scope); } else { $types = new SpecifiedTypes(); } - if (isset($node->args[2]) && $context->true()) { - if (!$scope->getType($node->args[2]->value)->isSuperTypeOf(new ConstantBooleanType(true))->no()) { + if (isset($node->getArgs()[2]) && $context->true()) { + if (!$scope->getType($node->getArgs()[2]->value)->isSuperTypeOf(new ConstantBooleanType(true))->no()) { $types = $types->intersectWith($this->typeSpecifier->create( - $node->args[0]->value, + $node->getArgs()[0]->value, isset($objectType) ? new GenericClassStringType($objectType) : new ClassStringType(), $context, false, diff --git a/src/Type/Php/IsArrayFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsArrayFunctionTypeSpecifyingExtension.php index a0b82542a0..c324649f40 100644 --- a/src/Type/Php/IsArrayFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsArrayFunctionTypeSpecifyingExtension.php @@ -26,14 +26,14 @@ public function isFunctionSupported(FunctionReflection $functionReflection, Func public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes { - if (!isset($node->args[0])) { + if (!isset($node->getArgs()[0])) { return new SpecifiedTypes(); } if ($context->null()) { throw new \PHPStan\ShouldNotHappenException(); } - return $this->typeSpecifier->create($node->args[0]->value, new ArrayType(new MixedType(), new MixedType()), $context, false, $scope); + return $this->typeSpecifier->create($node->getArgs()[0]->value, new ArrayType(new MixedType(), new MixedType()), $context, false, $scope); } public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void diff --git a/src/Type/Php/IsBoolFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsBoolFunctionTypeSpecifyingExtension.php index 3753d2304a..50b77703e9 100644 --- a/src/Type/Php/IsBoolFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsBoolFunctionTypeSpecifyingExtension.php @@ -25,14 +25,14 @@ public function isFunctionSupported(FunctionReflection $functionReflection, Func public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes { - if (!isset($node->args[0])) { + if (!isset($node->getArgs()[0])) { return new SpecifiedTypes(); } if ($context->null()) { throw new \PHPStan\ShouldNotHappenException(); } - return $this->typeSpecifier->create($node->args[0]->value, new BooleanType(), $context, false, $scope); + return $this->typeSpecifier->create($node->getArgs()[0]->value, new BooleanType(), $context, false, $scope); } public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void diff --git a/src/Type/Php/IsCallableFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsCallableFunctionTypeSpecifyingExtension.php index eb051391d4..16f3d55f8b 100644 --- a/src/Type/Php/IsCallableFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsCallableFunctionTypeSpecifyingExtension.php @@ -40,11 +40,11 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n throw new \PHPStan\ShouldNotHappenException(); } - if (!isset($node->args[0])) { + if (!isset($node->getArgs()[0])) { return new SpecifiedTypes(); } - $value = $node->args[0]->value; + $value = $node->getArgs()[0]->value; $valueType = $scope->getType($value); if ( $value instanceof Array_ diff --git a/src/Type/Php/IsCountableFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsCountableFunctionTypeSpecifyingExtension.php index 50814695d3..864ef327f4 100644 --- a/src/Type/Php/IsCountableFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsCountableFunctionTypeSpecifyingExtension.php @@ -32,12 +32,12 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n throw new \PHPStan\ShouldNotHappenException(); } - if (!isset($node->args[0])) { + if (!isset($node->getArgs()[0])) { return new SpecifiedTypes(); } return $this->typeSpecifier->create( - $node->args[0]->value, + $node->getArgs()[0]->value, new UnionType([ new ArrayType(new MixedType(), new MixedType()), new ObjectType(\Countable::class), diff --git a/src/Type/Php/IsFloatFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsFloatFunctionTypeSpecifyingExtension.php index 33b021251e..77ded103d8 100644 --- a/src/Type/Php/IsFloatFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsFloatFunctionTypeSpecifyingExtension.php @@ -29,14 +29,14 @@ public function isFunctionSupported(FunctionReflection $functionReflection, Func public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes { - if (!isset($node->args[0])) { + if (!isset($node->getArgs()[0])) { return new SpecifiedTypes(); } if ($context->null()) { throw new \PHPStan\ShouldNotHappenException(); } - return $this->typeSpecifier->create($node->args[0]->value, new FloatType(), $context, false, $scope); + return $this->typeSpecifier->create($node->getArgs()[0]->value, new FloatType(), $context, false, $scope); } public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void diff --git a/src/Type/Php/IsIntFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsIntFunctionTypeSpecifyingExtension.php index b8d70648ae..27e6ea99cb 100644 --- a/src/Type/Php/IsIntFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsIntFunctionTypeSpecifyingExtension.php @@ -29,14 +29,14 @@ public function isFunctionSupported(FunctionReflection $functionReflection, Func public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes { - if (!isset($node->args[0])) { + if (!isset($node->getArgs()[0])) { return new SpecifiedTypes(); } if ($context->null()) { throw new \PHPStan\ShouldNotHappenException(); } - return $this->typeSpecifier->create($node->args[0]->value, new IntegerType(), $context, false, $scope); + return $this->typeSpecifier->create($node->getArgs()[0]->value, new IntegerType(), $context, false, $scope); } public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void diff --git a/src/Type/Php/IsIterableFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsIterableFunctionTypeSpecifyingExtension.php index 78d5337610..178ebf3bad 100644 --- a/src/Type/Php/IsIterableFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsIterableFunctionTypeSpecifyingExtension.php @@ -30,11 +30,11 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n throw new \PHPStan\ShouldNotHappenException(); } - if (!isset($node->args[0])) { + if (!isset($node->getArgs()[0])) { return new SpecifiedTypes(); } - return $this->typeSpecifier->create($node->args[0]->value, new IterableType(new MixedType(), new MixedType()), $context, false, $scope); + return $this->typeSpecifier->create($node->getArgs()[0]->value, new IterableType(new MixedType(), new MixedType()), $context, false, $scope); } public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void diff --git a/src/Type/Php/IsNullFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsNullFunctionTypeSpecifyingExtension.php index 85d26bb99b..7d1faec933 100644 --- a/src/Type/Php/IsNullFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsNullFunctionTypeSpecifyingExtension.php @@ -29,11 +29,11 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n throw new \PHPStan\ShouldNotHappenException(); } - if (!isset($node->args[0])) { + if (!isset($node->getArgs()[0])) { return new SpecifiedTypes(); } - return $this->typeSpecifier->create($node->args[0]->value, new NullType(), $context, false, $scope); + return $this->typeSpecifier->create($node->getArgs()[0]->value, new NullType(), $context, false, $scope); } public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void diff --git a/src/Type/Php/IsNumericFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsNumericFunctionTypeSpecifyingExtension.php index 9cf67e8359..5c4aaf6195 100644 --- a/src/Type/Php/IsNumericFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsNumericFunctionTypeSpecifyingExtension.php @@ -30,7 +30,7 @@ public function isFunctionSupported(FunctionReflection $functionReflection, Func public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes { - if (!isset($node->args[0])) { + if (!isset($node->getArgs()[0])) { return new SpecifiedTypes(); } if ($context->null()) { @@ -49,7 +49,7 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n ]); } - return $this->typeSpecifier->create($node->args[0]->value, new UnionType($numericTypes), $context, false, $scope); + return $this->typeSpecifier->create($node->getArgs()[0]->value, new UnionType($numericTypes), $context, false, $scope); } public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void diff --git a/src/Type/Php/IsObjectFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsObjectFunctionTypeSpecifyingExtension.php index 9d60ab9b3b..e48e70341d 100644 --- a/src/Type/Php/IsObjectFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsObjectFunctionTypeSpecifyingExtension.php @@ -29,11 +29,11 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n throw new \PHPStan\ShouldNotHappenException(); } - if (!isset($node->args[0])) { + if (!isset($node->getArgs()[0])) { return new SpecifiedTypes(); } - return $this->typeSpecifier->create($node->args[0]->value, new ObjectWithoutClassType(), $context, false, $scope); + return $this->typeSpecifier->create($node->getArgs()[0]->value, new ObjectWithoutClassType(), $context, false, $scope); } public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void diff --git a/src/Type/Php/IsResourceFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsResourceFunctionTypeSpecifyingExtension.php index efaec426d8..1dd6bc0354 100644 --- a/src/Type/Php/IsResourceFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsResourceFunctionTypeSpecifyingExtension.php @@ -29,11 +29,11 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n throw new \PHPStan\ShouldNotHappenException(); } - if (!isset($node->args[0])) { + if (!isset($node->getArgs()[0])) { return new SpecifiedTypes(); } - return $this->typeSpecifier->create($node->args[0]->value, new ResourceType(), $context, false, $scope); + return $this->typeSpecifier->create($node->getArgs()[0]->value, new ResourceType(), $context, false, $scope); } public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void diff --git a/src/Type/Php/IsScalarFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsScalarFunctionTypeSpecifyingExtension.php index 4bcdd7c962..277262cd35 100644 --- a/src/Type/Php/IsScalarFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsScalarFunctionTypeSpecifyingExtension.php @@ -33,11 +33,11 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n throw new \PHPStan\ShouldNotHappenException(); } - if (!isset($node->args[0])) { + if (!isset($node->getArgs()[0])) { return new SpecifiedTypes(); } - return $this->typeSpecifier->create($node->args[0]->value, new UnionType([ + return $this->typeSpecifier->create($node->getArgs()[0]->value, new UnionType([ new StringType(), new IntegerType(), new FloatType(), diff --git a/src/Type/Php/IsStringFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsStringFunctionTypeSpecifyingExtension.php index f589ec523c..bb5832d5db 100644 --- a/src/Type/Php/IsStringFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsStringFunctionTypeSpecifyingExtension.php @@ -29,11 +29,11 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n throw new \PHPStan\ShouldNotHappenException(); } - if (!isset($node->args[0])) { + if (!isset($node->getArgs()[0])) { return new SpecifiedTypes(); } - return $this->typeSpecifier->create($node->args[0]->value, new StringType(), $context, false, $scope); + return $this->typeSpecifier->create($node->getArgs()[0]->value, new StringType(), $context, false, $scope); } public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void diff --git a/src/Type/Php/IsSubclassOfFunctionTypeSpecifyingExtension.php b/src/Type/Php/IsSubclassOfFunctionTypeSpecifyingExtension.php index e692d24ed1..88416246c0 100644 --- a/src/Type/Php/IsSubclassOfFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/IsSubclassOfFunctionTypeSpecifyingExtension.php @@ -39,12 +39,12 @@ public function isFunctionSupported(FunctionReflection $functionReflection, Func public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes { - if (count($node->args) < 2) { + if (count($node->getArgs()) < 2) { return new SpecifiedTypes(); } - $objectType = $scope->getType($node->args[0]->value); - $classType = $scope->getType($node->args[1]->value); - $allowStringType = isset($node->args[2]) ? $scope->getType($node->args[2]->value) : new ConstantBooleanType(true); + $objectType = $scope->getType($node->getArgs()[0]->value); + $classType = $scope->getType($node->getArgs()[1]->value); + $allowStringType = isset($node->getArgs()[2]) ? $scope->getType($node->getArgs()[2]->value) : new ConstantBooleanType(true); $allowString = !$allowStringType->equals(new ConstantBooleanType(false)); if (!$classType instanceof ConstantStringType) { @@ -59,7 +59,7 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n } return $this->typeSpecifier->create( - $node->args[0]->value, + $node->getArgs()[0]->value, $type, $context, false, @@ -100,7 +100,7 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n }); return $this->typeSpecifier->create( - $node->args[0]->value, + $node->getArgs()[0]->value, $type, $context, false, diff --git a/src/Type/Php/JsonThrowOnErrorDynamicReturnTypeExtension.php b/src/Type/Php/JsonThrowOnErrorDynamicReturnTypeExtension.php index e2697da0a0..c3cff87d86 100644 --- a/src/Type/Php/JsonThrowOnErrorDynamicReturnTypeExtension.php +++ b/src/Type/Php/JsonThrowOnErrorDynamicReturnTypeExtension.php @@ -54,11 +54,11 @@ public function getTypeFromFunctionCall( { $argumentPosition = $this->argumentPositions[$functionReflection->getName()]; $defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - if (!isset($functionCall->args[$argumentPosition])) { + if (!isset($functionCall->getArgs()[$argumentPosition])) { return $defaultReturnType; } - $optionsExpr = $functionCall->args[$argumentPosition]->value; + $optionsExpr = $functionCall->getArgs()[$argumentPosition]->value; $constrictedReturnType = TypeCombinator::remove($defaultReturnType, new ConstantBooleanType(false)); if ($this->isBitwiseOrWithJsonThrowOnError($optionsExpr, $scope)) { return $constrictedReturnType; diff --git a/src/Type/Php/JsonThrowTypeExtension.php b/src/Type/Php/JsonThrowTypeExtension.php index bcb7bd4ee3..a348c3b5bb 100644 --- a/src/Type/Php/JsonThrowTypeExtension.php +++ b/src/Type/Php/JsonThrowTypeExtension.php @@ -52,11 +52,11 @@ public function getThrowTypeFromFunctionCall( ): ?Type { $argumentPosition = $this->argumentPositions[$functionReflection->getName()]; - if (!isset($functionCall->args[$argumentPosition])) { + if (!isset($functionCall->getArgs()[$argumentPosition])) { return null; } - $optionsExpr = $functionCall->args[$argumentPosition]->value; + $optionsExpr = $functionCall->getArgs()[$argumentPosition]->value; if ($this->isBitwiseOrWithJsonThrowOnError($optionsExpr, $scope)) { return new ObjectType('JsonException'); } diff --git a/src/Type/Php/MbConvertEncodingFunctionReturnTypeExtension.php b/src/Type/Php/MbConvertEncodingFunctionReturnTypeExtension.php index 1369e545d6..7fa2af76d0 100644 --- a/src/Type/Php/MbConvertEncodingFunctionReturnTypeExtension.php +++ b/src/Type/Php/MbConvertEncodingFunctionReturnTypeExtension.php @@ -28,11 +28,11 @@ public function getTypeFromFunctionCall( ): Type { $defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - if (!isset($functionCall->args[0])) { + if (!isset($functionCall->getArgs()[0])) { return $defaultReturnType; } - $argType = $scope->getType($functionCall->args[0]->value); + $argType = $scope->getType($functionCall->getArgs()[0]->value); $isString = (new StringType())->isSuperTypeOf($argType); $isArray = (new ArrayType(new MixedType(), new MixedType()))->isSuperTypeOf($argType); $compare = $isString->compareTo($isArray); diff --git a/src/Type/Php/MbFunctionsReturnTypeExtension.php b/src/Type/Php/MbFunctionsReturnTypeExtension.php index 0b98c19e43..f2ce073b64 100644 --- a/src/Type/Php/MbFunctionsReturnTypeExtension.php +++ b/src/Type/Php/MbFunctionsReturnTypeExtension.php @@ -62,11 +62,11 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $returnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); $positionEncodingParam = $this->encodingPositionMap[$functionReflection->getName()]; - if (count($functionCall->args) < $positionEncodingParam) { + if (count($functionCall->getArgs()) < $positionEncodingParam) { return TypeCombinator::remove($returnType, new BooleanType()); } - $strings = TypeUtils::getConstantStrings($scope->getType($functionCall->args[$positionEncodingParam - 1]->value)); + $strings = TypeUtils::getConstantStrings($scope->getType($functionCall->getArgs()[$positionEncodingParam - 1]->value)); $results = array_unique(array_map(function (ConstantStringType $encoding): bool { return $this->isSupportedEncoding($encoding->getValue()); }, $strings)); diff --git a/src/Type/Php/MbSubstituteCharacterDynamicReturnTypeExtension.php b/src/Type/Php/MbSubstituteCharacterDynamicReturnTypeExtension.php index ae3352a29a..f1aeb5139f 100644 --- a/src/Type/Php/MbSubstituteCharacterDynamicReturnTypeExtension.php +++ b/src/Type/Php/MbSubstituteCharacterDynamicReturnTypeExtension.php @@ -48,7 +48,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $ranges[] = IntegerRangeType::fromInterval($minCodePoint, $maxCodePoint); } - if (!isset($functionCall->args[0])) { + if (!isset($functionCall->getArgs()[0])) { return TypeCombinator::union( new ConstantStringType('none'), new ConstantStringType('long'), @@ -57,7 +57,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, ); } - $argType = $scope->getType($functionCall->args[0]->value); + $argType = $scope->getType($functionCall->getArgs()[0]->value); $isString = (new StringType())->isSuperTypeOf($argType); $isNull = (new NullType())->isSuperTypeOf($argType); $isInteger = (new IntegerType())->isSuperTypeOf($argType); diff --git a/src/Type/Php/MethodExistsTypeSpecifyingExtension.php b/src/Type/Php/MethodExistsTypeSpecifyingExtension.php index 2a5228d7d9..16535fcbb6 100644 --- a/src/Type/Php/MethodExistsTypeSpecifyingExtension.php +++ b/src/Type/Php/MethodExistsTypeSpecifyingExtension.php @@ -37,7 +37,7 @@ public function isFunctionSupported( { return $functionReflection->getName() === 'method_exists' && $context->truthy() - && count($node->args) >= 2; + && count($node->getArgs()) >= 2; } public function specifyTypes( @@ -47,20 +47,20 @@ public function specifyTypes( TypeSpecifierContext $context ): SpecifiedTypes { - $objectType = $scope->getType($node->args[0]->value); + $objectType = $scope->getType($node->getArgs()[0]->value); if (!$objectType instanceof ObjectType) { if ((new StringType())->isSuperTypeOf($objectType)->yes()) { return new SpecifiedTypes([], []); } } - $methodNameType = $scope->getType($node->args[1]->value); + $methodNameType = $scope->getType($node->getArgs()[1]->value); if (!$methodNameType instanceof ConstantStringType) { return new SpecifiedTypes([], []); } return $this->typeSpecifier->create( - $node->args[0]->value, + $node->getArgs()[0]->value, new UnionType([ new IntersectionType([ new ObjectWithoutClassType(), diff --git a/src/Type/Php/MicrotimeFunctionReturnTypeExtension.php b/src/Type/Php/MicrotimeFunctionReturnTypeExtension.php index 15ef4772dd..f96fb94c68 100644 --- a/src/Type/Php/MicrotimeFunctionReturnTypeExtension.php +++ b/src/Type/Php/MicrotimeFunctionReturnTypeExtension.php @@ -23,11 +23,11 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type { - if (count($functionCall->args) < 1) { + if (count($functionCall->getArgs()) < 1) { return new StringType(); } - $argType = $scope->getType($functionCall->args[0]->value); + $argType = $scope->getType($functionCall->getArgs()[0]->value); $isTrueType = (new ConstantBooleanType(true))->isSuperTypeOf($argType); $isFalseType = (new ConstantBooleanType(false))->isSuperTypeOf($argType); $compareTypes = $isTrueType->compareTo($isFalseType); diff --git a/src/Type/Php/MinMaxFunctionReturnTypeExtension.php b/src/Type/Php/MinMaxFunctionReturnTypeExtension.php index 355acdbc89..41603eb52a 100644 --- a/src/Type/Php/MinMaxFunctionReturnTypeExtension.php +++ b/src/Type/Php/MinMaxFunctionReturnTypeExtension.php @@ -33,12 +33,12 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type { - if (!isset($functionCall->args[0])) { + if (!isset($functionCall->getArgs()[0])) { return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); } - if (count($functionCall->args) === 1) { - $argType = $scope->getType($functionCall->args[0]->value); + if (count($functionCall->getArgs()) === 1) { + $argType = $scope->getType($functionCall->getArgs()[0]->value); if ($argType->isArray()->yes()) { $isIterable = $argType->isIterableAtLeastOnce(); if ($isIterable->no()) { @@ -69,8 +69,8 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, // rewrite min($x, $y) as $x < $y ? $x : $y // we don't handle arrays, which have different semantics $functionName = $functionReflection->getName(); - $args = $functionCall->args; - if (count($functionCall->args) === 2) { + $args = $functionCall->getArgs(); + if (count($functionCall->getArgs()) === 2) { $argType0 = $scope->getType($args[0]->value); $argType1 = $scope->getType($args[1]->value); @@ -92,7 +92,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, } $argumentTypes = []; - foreach ($functionCall->args as $arg) { + foreach ($functionCall->getArgs() as $arg) { $argType = $scope->getType($arg->value); if ($arg->unpack) { $iterableValueType = $argType->getIterableValueType(); diff --git a/src/Type/Php/NonEmptyStringFunctionsReturnTypeExtension.php b/src/Type/Php/NonEmptyStringFunctionsReturnTypeExtension.php index f4afe77ea8..4a484c150d 100644 --- a/src/Type/Php/NonEmptyStringFunctionsReturnTypeExtension.php +++ b/src/Type/Php/NonEmptyStringFunctionsReturnTypeExtension.php @@ -44,7 +44,7 @@ public function getTypeFromFunctionCall( Scope $scope ): \PHPStan\Type\Type { - $args = $functionCall->args; + $args = $functionCall->getArgs(); if (count($args) === 0) { return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); } diff --git a/src/Type/Php/NumberFormatFunctionDynamicReturnTypeExtension.php b/src/Type/Php/NumberFormatFunctionDynamicReturnTypeExtension.php index 0dc7fb0cc9..5dd2c9a723 100644 --- a/src/Type/Php/NumberFormatFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/NumberFormatFunctionDynamicReturnTypeExtension.php @@ -23,12 +23,12 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type { $stringType = new StringType(); - if (!isset($functionCall->args[3])) { + if (!isset($functionCall->getArgs()[3])) { return $stringType; } - $thousandsType = $scope->getType($functionCall->args[3]->value); - $decimalType = $scope->getType($functionCall->args[2]->value); + $thousandsType = $scope->getType($functionCall->getArgs()[3]->value); + $decimalType = $scope->getType($functionCall->getArgs()[2]->value); if (!$thousandsType instanceof ConstantStringType || $thousandsType->getValue() !== '') { return $stringType; diff --git a/src/Type/Php/ParseUrlFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ParseUrlFunctionDynamicReturnTypeExtension.php index 0c0053371f..47d276e872 100644 --- a/src/Type/Php/ParseUrlFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ParseUrlFunctionDynamicReturnTypeExtension.php @@ -36,7 +36,7 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type { - if (count($functionCall->args) < 1) { + if (count($functionCall->getArgs()) < 1) { return ParametersAcceptorSelector::selectSingle( $functionReflection->getVariants() )->getReturnType(); @@ -44,9 +44,9 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $this->cacheReturnTypes(); - $urlType = $scope->getType($functionCall->args[0]->value); - if (count($functionCall->args) > 1) { - $componentType = $scope->getType($functionCall->args[1]->value); + $urlType = $scope->getType($functionCall->getArgs()[0]->value); + if (count($functionCall->getArgs()) > 1) { + $componentType = $scope->getType($functionCall->getArgs()[1]->value); if (!$componentType instanceof ConstantType) { return $this->createAllComponentsReturnType(); diff --git a/src/Type/Php/PathinfoFunctionDynamicReturnTypeExtension.php b/src/Type/Php/PathinfoFunctionDynamicReturnTypeExtension.php index dbb09f1a8a..b382c4ce7c 100644 --- a/src/Type/Php/PathinfoFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/PathinfoFunctionDynamicReturnTypeExtension.php @@ -25,7 +25,7 @@ public function getTypeFromFunctionCall( Scope $scope ): Type { - $argsCount = count($functionCall->args); + $argsCount = count($functionCall->getArgs()); if ($argsCount === 0) { return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); } elseif ($argsCount === 1) { diff --git a/src/Type/Php/PowFunctionReturnTypeExtension.php b/src/Type/Php/PowFunctionReturnTypeExtension.php index 9e84218270..e8dc55f83f 100644 --- a/src/Type/Php/PowFunctionReturnTypeExtension.php +++ b/src/Type/Php/PowFunctionReturnTypeExtension.php @@ -28,12 +28,12 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, new FloatType(), new IntegerType(), ]); - if (count($functionCall->args) < 2) { + if (count($functionCall->getArgs()) < 2) { return $defaultReturnType; } - $firstArgType = $scope->getType($functionCall->args[0]->value); - $secondArgType = $scope->getType($functionCall->args[1]->value); + $firstArgType = $scope->getType($functionCall->getArgs()[0]->value); + $secondArgType = $scope->getType($functionCall->getArgs()[1]->value); if ($firstArgType instanceof MixedType || $secondArgType instanceof MixedType) { return $defaultReturnType; } diff --git a/src/Type/Php/PregSplitDynamicReturnTypeExtension.php b/src/Type/Php/PregSplitDynamicReturnTypeExtension.php index 912363f117..83e1ca6aa9 100644 --- a/src/Type/Php/PregSplitDynamicReturnTypeExtension.php +++ b/src/Type/Php/PregSplitDynamicReturnTypeExtension.php @@ -38,7 +38,7 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type { - $flagsArg = $functionCall->args[3] ?? null; + $flagsArg = $functionCall->getArgs()[3] ?? null; if ($this->hasFlag($this->getConstant('PREG_SPLIT_OFFSET_CAPTURE'), $flagsArg, $scope)) { $type = new ArrayType( diff --git a/src/Type/Php/PropertyExistsTypeSpecifyingExtension.php b/src/Type/Php/PropertyExistsTypeSpecifyingExtension.php index 7ff323cfa2..bc970ee7c7 100644 --- a/src/Type/Php/PropertyExistsTypeSpecifyingExtension.php +++ b/src/Type/Php/PropertyExistsTypeSpecifyingExtension.php @@ -43,7 +43,7 @@ public function isFunctionSupported( { return $functionReflection->getName() === 'property_exists' && $context->truthy() - && count($node->args) >= 2; + && count($node->getArgs()) >= 2; } public function specifyTypes( @@ -53,17 +53,17 @@ public function specifyTypes( TypeSpecifierContext $context ): SpecifiedTypes { - $propertyNameType = $scope->getType($node->args[1]->value); + $propertyNameType = $scope->getType($node->getArgs()[1]->value); if (!$propertyNameType instanceof ConstantStringType) { return new SpecifiedTypes([], []); } - $objectType = $scope->getType($node->args[0]->value); + $objectType = $scope->getType($node->getArgs()[0]->value); if ($objectType instanceof ConstantStringType) { return new SpecifiedTypes([], []); } elseif ((new ObjectWithoutClassType())->isSuperTypeOf($objectType)->yes()) { $propertyNode = new PropertyFetch( - $node->args[0]->value, + $node->getArgs()[0]->value, new Identifier($propertyNameType->getValue()) ); } else { @@ -78,7 +78,7 @@ public function specifyTypes( } return $this->typeSpecifier->create( - $node->args[0]->value, + $node->getArgs()[0]->value, new IntersectionType([ new ObjectWithoutClassType(), new HasPropertyType($propertyNameType->getValue()), diff --git a/src/Type/Php/RandomIntFunctionReturnTypeExtension.php b/src/Type/Php/RandomIntFunctionReturnTypeExtension.php index 6f5b9762bc..53243678ad 100644 --- a/src/Type/Php/RandomIntFunctionReturnTypeExtension.php +++ b/src/Type/Php/RandomIntFunctionReturnTypeExtension.php @@ -21,16 +21,16 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type { - if ($functionReflection->getName() === 'rand' && count($functionCall->args) === 0) { + if ($functionReflection->getName() === 'rand' && count($functionCall->getArgs()) === 0) { return IntegerRangeType::fromInterval(0, null); } - if (count($functionCall->args) < 2) { - return ParametersAcceptorSelector::selectFromArgs($scope, $functionCall->args, $functionReflection->getVariants())->getReturnType(); + if (count($functionCall->getArgs()) < 2) { + return ParametersAcceptorSelector::selectFromArgs($scope, $functionCall->getArgs(), $functionReflection->getVariants())->getReturnType(); } - $minType = $scope->getType($functionCall->args[0]->value)->toInteger(); - $maxType = $scope->getType($functionCall->args[1]->value)->toInteger(); + $minType = $scope->getType($functionCall->getArgs()[0]->value)->toInteger(); + $maxType = $scope->getType($functionCall->getArgs()[1]->value)->toInteger(); return $this->createRange($minType, $maxType); } diff --git a/src/Type/Php/RangeFunctionReturnTypeExtension.php b/src/Type/Php/RangeFunctionReturnTypeExtension.php index bc6bcee9fd..12a72bce8a 100644 --- a/src/Type/Php/RangeFunctionReturnTypeExtension.php +++ b/src/Type/Php/RangeFunctionReturnTypeExtension.php @@ -35,13 +35,13 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type { - if (count($functionCall->args) < 2) { + if (count($functionCall->getArgs()) < 2) { return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); } - $startType = $scope->getType($functionCall->args[0]->value); - $endType = $scope->getType($functionCall->args[1]->value); - $stepType = count($functionCall->args) >= 3 ? $scope->getType($functionCall->args[2]->value) : new ConstantIntegerType(1); + $startType = $scope->getType($functionCall->getArgs()[0]->value); + $endType = $scope->getType($functionCall->getArgs()[1]->value); + $stepType = count($functionCall->getArgs()) >= 3 ? $scope->getType($functionCall->getArgs()[2]->value) : new ConstantIntegerType(1); $constantReturnTypes = []; diff --git a/src/Type/Php/ReflectionClassConstructorThrowTypeExtension.php b/src/Type/Php/ReflectionClassConstructorThrowTypeExtension.php index 46a1be0979..3a3fbeb13b 100644 --- a/src/Type/Php/ReflectionClassConstructorThrowTypeExtension.php +++ b/src/Type/Php/ReflectionClassConstructorThrowTypeExtension.php @@ -32,11 +32,11 @@ public function isStaticMethodSupported(MethodReflection $methodReflection): boo public function getThrowTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): ?Type { - if (count($methodCall->args) < 1) { + if (count($methodCall->getArgs()) < 1) { return $methodReflection->getThrowType(); } - $valueType = $scope->getType($methodCall->args[0]->value); + $valueType = $scope->getType($methodCall->getArgs()[0]->value); foreach (TypeUtils::flattenTypes($valueType) as $type) { if ($type instanceof ClassStringType || $type instanceof ObjectWithoutClassType || $type instanceof ObjectType) { continue; diff --git a/src/Type/Php/ReflectionClassIsSubclassOfTypeSpecifyingExtension.php b/src/Type/Php/ReflectionClassIsSubclassOfTypeSpecifyingExtension.php index 54f9eacab4..601366a9f8 100644 --- a/src/Type/Php/ReflectionClassIsSubclassOfTypeSpecifyingExtension.php +++ b/src/Type/Php/ReflectionClassIsSubclassOfTypeSpecifyingExtension.php @@ -32,13 +32,13 @@ public function getClass(): string public function isMethodSupported(MethodReflection $methodReflection, MethodCall $node, TypeSpecifierContext $context): bool { return $methodReflection->getName() === 'isSubclassOf' - && isset($node->args[0]) + && isset($node->getArgs()[0]) && $context->true(); } public function specifyTypes(MethodReflection $methodReflection, MethodCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes { - $valueType = $scope->getType($node->args[0]->value); + $valueType = $scope->getType($node->getArgs()[0]->value); if (!$valueType instanceof ConstantStringType) { return new SpecifiedTypes([], []); } diff --git a/src/Type/Php/ReflectionFunctionConstructorThrowTypeExtension.php b/src/Type/Php/ReflectionFunctionConstructorThrowTypeExtension.php index d1232b5043..b25efa7a86 100644 --- a/src/Type/Php/ReflectionFunctionConstructorThrowTypeExtension.php +++ b/src/Type/Php/ReflectionFunctionConstructorThrowTypeExtension.php @@ -31,11 +31,11 @@ public function isStaticMethodSupported(MethodReflection $methodReflection): boo public function getThrowTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): ?Type { - if (count($methodCall->args) < 1) { + if (count($methodCall->getArgs()) < 1) { return $methodReflection->getThrowType(); } - $valueType = $scope->getType($methodCall->args[0]->value); + $valueType = $scope->getType($methodCall->getArgs()[0]->value); foreach (TypeUtils::getConstantStrings($valueType) as $constantString) { if (!$this->reflectionProvider->hasFunction(new Name($constantString->getValue()), $scope)) { return $methodReflection->getThrowType(); diff --git a/src/Type/Php/ReflectionGetAttributesMethodReturnTypeExtension.php b/src/Type/Php/ReflectionGetAttributesMethodReturnTypeExtension.php index 330fbddcea..7b0bcf7c17 100644 --- a/src/Type/Php/ReflectionGetAttributesMethodReturnTypeExtension.php +++ b/src/Type/Php/ReflectionGetAttributesMethodReturnTypeExtension.php @@ -41,10 +41,10 @@ public function isMethodSupported(MethodReflection $methodReflection): bool public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type { - if (count($methodCall->args) === 0) { + if (count($methodCall->getArgs()) === 0) { return $this->getDefaultReturnType($scope, $methodCall, $methodReflection); } - $argType = $scope->getType($methodCall->args[0]->value); + $argType = $scope->getType($methodCall->getArgs()[0]->value); if ($argType instanceof ConstantStringType) { $classType = new ObjectType($argType->getValue()); @@ -61,7 +61,7 @@ private function getDefaultReturnType(Scope $scope, MethodCall $methodCall, Meth { return ParametersAcceptorSelector::selectFromArgs( $scope, - $methodCall->args, + $methodCall->getArgs(), $methodReflection->getVariants() )->getReturnType(); } diff --git a/src/Type/Php/ReflectionMethodConstructorThrowTypeExtension.php b/src/Type/Php/ReflectionMethodConstructorThrowTypeExtension.php index 4d5d3e13f1..4042fbec7a 100644 --- a/src/Type/Php/ReflectionMethodConstructorThrowTypeExtension.php +++ b/src/Type/Php/ReflectionMethodConstructorThrowTypeExtension.php @@ -32,12 +32,12 @@ public function isStaticMethodSupported(MethodReflection $methodReflection): boo public function getThrowTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): ?Type { - if (count($methodCall->args) < 2) { + if (count($methodCall->getArgs()) < 2) { return $methodReflection->getThrowType(); } - $valueType = $scope->getType($methodCall->args[0]->value); - $propertyType = $scope->getType($methodCall->args[1]->value); + $valueType = $scope->getType($methodCall->getArgs()[0]->value); + $propertyType = $scope->getType($methodCall->getArgs()[1]->value); foreach (TypeUtils::flattenTypes($valueType) as $type) { if ($type instanceof GenericClassStringType) { $classes = $type->getReferencedClasses(); diff --git a/src/Type/Php/ReflectionPropertyConstructorThrowTypeExtension.php b/src/Type/Php/ReflectionPropertyConstructorThrowTypeExtension.php index 4faa7ec8f7..6d6d53c6de 100644 --- a/src/Type/Php/ReflectionPropertyConstructorThrowTypeExtension.php +++ b/src/Type/Php/ReflectionPropertyConstructorThrowTypeExtension.php @@ -30,12 +30,12 @@ public function isStaticMethodSupported(MethodReflection $methodReflection): boo public function getThrowTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): ?Type { - if (count($methodCall->args) < 2) { + if (count($methodCall->getArgs()) < 2) { return $methodReflection->getThrowType(); } - $valueType = $scope->getType($methodCall->args[0]->value); - $propertyType = $scope->getType($methodCall->args[1]->value); + $valueType = $scope->getType($methodCall->getArgs()[0]->value); + $propertyType = $scope->getType($methodCall->getArgs()[1]->value); foreach (TypeUtils::getConstantStrings($valueType) as $constantString) { if (!$this->reflectionProvider->hasClass($constantString->getValue())) { return $methodReflection->getThrowType(); diff --git a/src/Type/Php/ReplaceFunctionsDynamicReturnTypeExtension.php b/src/Type/Php/ReplaceFunctionsDynamicReturnTypeExtension.php index 752aa8560f..a9c6acb7d0 100644 --- a/src/Type/Php/ReplaceFunctionsDynamicReturnTypeExtension.php +++ b/src/Type/Php/ReplaceFunctionsDynamicReturnTypeExtension.php @@ -56,11 +56,11 @@ private function getPreliminarilyResolvedTypeFromFunctionCall( ): Type { $argumentPosition = $this->functions[$functionReflection->getName()]; - if (count($functionCall->args) <= $argumentPosition) { + if (count($functionCall->getArgs()) <= $argumentPosition) { return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); } - $subjectArgumentType = $scope->getType($functionCall->args[$argumentPosition]->value); + $subjectArgumentType = $scope->getType($functionCall->getArgs()[$argumentPosition]->value); $defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); if ($subjectArgumentType instanceof MixedType) { return TypeUtils::toBenevolentUnion($defaultReturnType); diff --git a/src/Type/Php/SimpleXMLElementAsXMLMethodReturnTypeExtension.php b/src/Type/Php/SimpleXMLElementAsXMLMethodReturnTypeExtension.php index 562ec373be..0cce9f5365 100644 --- a/src/Type/Php/SimpleXMLElementAsXMLMethodReturnTypeExtension.php +++ b/src/Type/Php/SimpleXMLElementAsXMLMethodReturnTypeExtension.php @@ -28,7 +28,7 @@ public function isMethodSupported(MethodReflection $methodReflection): bool public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type { - if (count($methodCall->args) === 1) { + if (count($methodCall->getArgs()) === 1) { return new BooleanType(); } return new UnionType([new StringType(), new ConstantBooleanType(false)]); diff --git a/src/Type/Php/SimpleXMLElementConstructorThrowTypeExtension.php b/src/Type/Php/SimpleXMLElementConstructorThrowTypeExtension.php index ec572c38fe..9b02e64f3c 100644 --- a/src/Type/Php/SimpleXMLElementConstructorThrowTypeExtension.php +++ b/src/Type/Php/SimpleXMLElementConstructorThrowTypeExtension.php @@ -22,11 +22,11 @@ public function isStaticMethodSupported(MethodReflection $methodReflection): boo public function getThrowTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): ?Type { - if (count($methodCall->args) === 0) { + if (count($methodCall->getArgs()) === 0) { return $methodReflection->getThrowType(); } - $valueType = $scope->getType($methodCall->args[0]->value); + $valueType = $scope->getType($methodCall->getArgs()[0]->value); $constantStrings = TypeUtils::getConstantStrings($valueType); foreach ($constantStrings as $constantString) { diff --git a/src/Type/Php/SimpleXMLElementXpathMethodReturnTypeExtension.php b/src/Type/Php/SimpleXMLElementXpathMethodReturnTypeExtension.php index 06da90da2a..404514b184 100644 --- a/src/Type/Php/SimpleXMLElementXpathMethodReturnTypeExtension.php +++ b/src/Type/Php/SimpleXMLElementXpathMethodReturnTypeExtension.php @@ -28,11 +28,11 @@ public function isMethodSupported(MethodReflection $methodReflection): bool public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type { - if (!isset($methodCall->args[0])) { + if (!isset($methodCall->getArgs()[0])) { return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); } - $argType = $scope->getType($methodCall->args[0]->value); + $argType = $scope->getType($methodCall->getArgs()[0]->value); $xmlElement = new \SimpleXMLElement(''); diff --git a/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php b/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php index f885585d2c..2ca19905c8 100644 --- a/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/SprintfFunctionDynamicReturnTypeExtension.php @@ -27,7 +27,7 @@ public function getTypeFromFunctionCall( Scope $scope ): Type { - $args = $functionCall->args; + $args = $functionCall->getArgs(); if (count($args) === 0) { return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); } diff --git a/src/Type/Php/StrPadFunctionReturnTypeExtension.php b/src/Type/Php/StrPadFunctionReturnTypeExtension.php index 3aeac0890e..cd239cc4a6 100644 --- a/src/Type/Php/StrPadFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrPadFunctionReturnTypeExtension.php @@ -26,7 +26,7 @@ public function getTypeFromFunctionCall( Scope $scope ): \PHPStan\Type\Type { - $args = $functionCall->args; + $args = $functionCall->getArgs(); if (count($args) < 2) { return new StringType(); } diff --git a/src/Type/Php/StrRepeatFunctionReturnTypeExtension.php b/src/Type/Php/StrRepeatFunctionReturnTypeExtension.php index f0ad8bb0e5..a66753a1a4 100644 --- a/src/Type/Php/StrRepeatFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrRepeatFunctionReturnTypeExtension.php @@ -28,7 +28,7 @@ public function getTypeFromFunctionCall( Scope $scope ): \PHPStan\Type\Type { - $args = $functionCall->args; + $args = $functionCall->getArgs(); if (count($args) < 2) { return new StringType(); } diff --git a/src/Type/Php/StrSplitFunctionReturnTypeExtension.php b/src/Type/Php/StrSplitFunctionReturnTypeExtension.php index bd78e3993e..78292f54f5 100644 --- a/src/Type/Php/StrSplitFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrSplitFunctionReturnTypeExtension.php @@ -49,12 +49,12 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, { $defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - if (count($functionCall->args) < 1) { + if (count($functionCall->getArgs()) < 1) { return $defaultReturnType; } - if (count($functionCall->args) >= 2) { - $splitLengthType = $scope->getType($functionCall->args[1]->value); + if (count($functionCall->getArgs()) >= 2) { + $splitLengthType = $scope->getType($functionCall->getArgs()[1]->value); if ($splitLengthType instanceof ConstantIntegerType) { $splitLength = $splitLengthType->getValue(); if ($splitLength < 1) { @@ -66,8 +66,8 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, } if ($functionReflection->getName() === 'mb_str_split') { - if (count($functionCall->args) >= 3) { - $strings = TypeUtils::getConstantStrings($scope->getType($functionCall->args[2]->value)); + if (count($functionCall->getArgs()) >= 3) { + $strings = TypeUtils::getConstantStrings($scope->getType($functionCall->getArgs()[2]->value)); $values = array_unique(array_map(static function (ConstantStringType $encoding): string { return $encoding->getValue(); }, $strings)); @@ -89,7 +89,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, return $defaultReturnType; } - $stringType = $scope->getType($functionCall->args[0]->value); + $stringType = $scope->getType($functionCall->getArgs()[0]->value); if (!$stringType instanceof ConstantStringType) { return TypeCombinator::intersect( new ArrayType(new IntegerType(), new StringType()), diff --git a/src/Type/Php/StrTokFunctionReturnTypeExtension.php b/src/Type/Php/StrTokFunctionReturnTypeExtension.php index 5f7f343528..5a3bee5146 100644 --- a/src/Type/Php/StrTokFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrTokFunctionReturnTypeExtension.php @@ -21,12 +21,12 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): \PHPStan\Type\Type { - $args = $functionCall->args; + $args = $functionCall->getArgs(); if (count($args) !== 2) { return ParametersAcceptorSelector::selectFromArgs($scope, $args, $functionReflection->getVariants())->getReturnType(); } - $delimiterType = $scope->getType($functionCall->args[0]->value); + $delimiterType = $scope->getType($functionCall->getArgs()[0]->value); $isEmptyString = (new ConstantStringType(''))->isSuperTypeOf($delimiterType); if ($isEmptyString->yes()) { return new ConstantBooleanType(false); diff --git a/src/Type/Php/StrWordCountFunctionDynamicReturnTypeExtension.php b/src/Type/Php/StrWordCountFunctionDynamicReturnTypeExtension.php index 6e53059859..c35dac7758 100644 --- a/src/Type/Php/StrWordCountFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/StrWordCountFunctionDynamicReturnTypeExtension.php @@ -27,11 +27,11 @@ public function getTypeFromFunctionCall( Scope $scope ): Type { - $argsCount = count($functionCall->args); + $argsCount = count($functionCall->getArgs()); if ($argsCount === 1) { return new IntegerType(); } elseif ($argsCount === 2 || $argsCount === 3) { - $formatType = $scope->getType($functionCall->args[1]->value); + $formatType = $scope->getType($functionCall->getArgs()[1]->value); if ($formatType instanceof ConstantIntegerType) { $val = $formatType->getValue(); if ($val === 0) { diff --git a/src/Type/Php/StrlenFunctionReturnTypeExtension.php b/src/Type/Php/StrlenFunctionReturnTypeExtension.php index 0f432c8c62..f92aa6abe6 100644 --- a/src/Type/Php/StrlenFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrlenFunctionReturnTypeExtension.php @@ -27,7 +27,7 @@ public function getTypeFromFunctionCall( Scope $scope ): \PHPStan\Type\Type { - $args = $functionCall->args; + $args = $functionCall->getArgs(); if (count($args) === 0) { return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); } diff --git a/src/Type/Php/StrtotimeFunctionReturnTypeExtension.php b/src/Type/Php/StrtotimeFunctionReturnTypeExtension.php index 767c141307..791392e78c 100644 --- a/src/Type/Php/StrtotimeFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrtotimeFunctionReturnTypeExtension.php @@ -24,10 +24,10 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type { $defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - if (count($functionCall->args) === 0) { + if (count($functionCall->getArgs()) === 0) { return $defaultReturnType; } - $argType = $scope->getType($functionCall->args[0]->value); + $argType = $scope->getType($functionCall->getArgs()[0]->value); if ($argType instanceof MixedType) { return TypeUtils::toBenevolentUnion($defaultReturnType); } diff --git a/src/Type/Php/StrvalFamilyFunctionReturnTypeExtension.php b/src/Type/Php/StrvalFamilyFunctionReturnTypeExtension.php index ffb7b8f5d5..7f2b431a94 100644 --- a/src/Type/Php/StrvalFamilyFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrvalFamilyFunctionReturnTypeExtension.php @@ -31,11 +31,11 @@ public function getTypeFromFunctionCall( Scope $scope ): Type { - if (count($functionCall->args) === 0) { + if (count($functionCall->getArgs()) === 0) { return new NullType(); } - $argType = $scope->getType($functionCall->args[0]->value); + $argType = $scope->getType($functionCall->getArgs()[0]->value); switch ($functionReflection->getName()) { case 'strval': diff --git a/src/Type/Php/SubstrDynamicReturnTypeExtension.php b/src/Type/Php/SubstrDynamicReturnTypeExtension.php index f86d919eab..20c95ec2b1 100644 --- a/src/Type/Php/SubstrDynamicReturnTypeExtension.php +++ b/src/Type/Php/SubstrDynamicReturnTypeExtension.php @@ -27,7 +27,7 @@ public function getTypeFromFunctionCall( Scope $scope ): \PHPStan\Type\Type { - $args = $functionCall->args; + $args = $functionCall->getArgs(); if (count($args) === 0) { return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); } diff --git a/src/Type/Php/TypeSpecifyingFunctionsDynamicReturnTypeExtension.php b/src/Type/Php/TypeSpecifyingFunctionsDynamicReturnTypeExtension.php index df691ad698..08fe95d210 100644 --- a/src/Type/Php/TypeSpecifyingFunctionsDynamicReturnTypeExtension.php +++ b/src/Type/Php/TypeSpecifyingFunctionsDynamicReturnTypeExtension.php @@ -75,7 +75,7 @@ public function getTypeFromFunctionCall( Scope $scope ): Type { - if (count($functionCall->args) === 0) { + if (count($functionCall->getArgs()) === 0) { return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); } diff --git a/src/Type/Php/VarExportFunctionDynamicReturnTypeExtension.php b/src/Type/Php/VarExportFunctionDynamicReturnTypeExtension.php index b90055cd8d..de6b3cbc12 100644 --- a/src/Type/Php/VarExportFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/VarExportFunctionDynamicReturnTypeExtension.php @@ -36,18 +36,18 @@ public function getTypeFromFunctionCall(\PHPStan\Reflection\FunctionReflection $ $fallbackReturnType = new BooleanType(); } - if (count($functionCall->args) < 1) { + if (count($functionCall->getArgs()) < 1) { return TypeCombinator::union( new StringType(), $fallbackReturnType ); } - if (count($functionCall->args) < 2) { + if (count($functionCall->getArgs()) < 2) { return $fallbackReturnType; } - $returnArgumentType = $scope->getType($functionCall->args[1]->value); + $returnArgumentType = $scope->getType($functionCall->getArgs()[1]->value); if ((new ConstantBooleanType(true))->isSuperTypeOf($returnArgumentType)->yes()) { return new StringType(); } diff --git a/src/Type/Php/VersionCompareFunctionDynamicReturnTypeExtension.php b/src/Type/Php/VersionCompareFunctionDynamicReturnTypeExtension.php index d0a6c37763..62347e0f75 100644 --- a/src/Type/Php/VersionCompareFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/VersionCompareFunctionDynamicReturnTypeExtension.php @@ -27,19 +27,19 @@ public function getTypeFromFunctionCall( Scope $scope ): Type { - if (count($functionCall->args) < 2) { - return ParametersAcceptorSelector::selectFromArgs($scope, $functionCall->args, $functionReflection->getVariants())->getReturnType(); + if (count($functionCall->getArgs()) < 2) { + return ParametersAcceptorSelector::selectFromArgs($scope, $functionCall->getArgs(), $functionReflection->getVariants())->getReturnType(); } - $version1Strings = TypeUtils::getConstantStrings($scope->getType($functionCall->args[0]->value)); - $version2Strings = TypeUtils::getConstantStrings($scope->getType($functionCall->args[1]->value)); + $version1Strings = TypeUtils::getConstantStrings($scope->getType($functionCall->getArgs()[0]->value)); + $version2Strings = TypeUtils::getConstantStrings($scope->getType($functionCall->getArgs()[1]->value)); $counts = [ count($version1Strings), count($version2Strings), ]; - if (isset($functionCall->args[2])) { - $operatorStrings = TypeUtils::getConstantStrings($scope->getType($functionCall->args[2]->value)); + if (isset($functionCall->getArgs()[2])) { + $operatorStrings = TypeUtils::getConstantStrings($scope->getType($functionCall->getArgs()[2]->value)); $counts[] = count($operatorStrings); $returnType = new BooleanType(); } else { diff --git a/tests/PHPStan/Tests/AssertionClassMethodTypeSpecifyingExtension.php b/tests/PHPStan/Tests/AssertionClassMethodTypeSpecifyingExtension.php index 203ce65db6..1e1afae3e2 100644 --- a/tests/PHPStan/Tests/AssertionClassMethodTypeSpecifyingExtension.php +++ b/tests/PHPStan/Tests/AssertionClassMethodTypeSpecifyingExtension.php @@ -50,7 +50,7 @@ public function specifyTypes( TypeSpecifierContext $context ): SpecifiedTypes { - return new SpecifiedTypes(['$foo' => [$node->args[0]->value, new StringType()]]); + return new SpecifiedTypes(['$foo' => [$node->getArgs()[0]->value, new StringType()]]); } } diff --git a/tests/PHPStan/Tests/AssertionClassStaticMethodTypeSpecifyingExtension.php b/tests/PHPStan/Tests/AssertionClassStaticMethodTypeSpecifyingExtension.php index 0961cd6f8f..8cc1e7b25a 100644 --- a/tests/PHPStan/Tests/AssertionClassStaticMethodTypeSpecifyingExtension.php +++ b/tests/PHPStan/Tests/AssertionClassStaticMethodTypeSpecifyingExtension.php @@ -50,7 +50,7 @@ public function specifyTypes( TypeSpecifierContext $context ): SpecifiedTypes { - return new SpecifiedTypes(['$bar' => [$node->args[0]->value, new IntegerType()]]); + return new SpecifiedTypes(['$bar' => [$node->getArgs()[0]->value, new IntegerType()]]); } } From f11480b4a2cabe9c5f19f53e9d7fa2dc1e226700 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 21 Sep 2021 15:26:05 +0200 Subject: [PATCH 0352/1284] Fix calling implode() with array on level 9 --- src/PhpDoc/TypeNodeResolver.php | 4 ++-- .../CallToFunctionParametersRuleTest.php | 6 +++++ .../PHPStan/Rules/Functions/data/bug-5661.php | 24 +++++++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Rules/Functions/data/bug-5661.php diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index 78e0ad68fa..10c701a90a 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -429,7 +429,7 @@ private function resolveIntersectionTypeNode(IntersectionTypeNode $typeNode, Nam private function resolveArrayTypeNode(ArrayTypeNode $typeNode, NameScope $nameScope): Type { $itemType = $this->resolve($typeNode->type, $nameScope); - return new ArrayType(new MixedType(), $itemType); + return new ArrayType(new BenevolentUnionType([new IntegerType(), new StringType()]), $itemType); } private function resolveGenericTypeNode(GenericTypeNode $typeNode, NameScope $nameScope): Type @@ -439,7 +439,7 @@ private function resolveGenericTypeNode(GenericTypeNode $typeNode, NameScope $na if ($mainTypeName === 'array' || $mainTypeName === 'non-empty-array') { if (count($genericTypes) === 1) { // array - $arrayType = new ArrayType(new MixedType(true), $genericTypes[0]); + $arrayType = new ArrayType(new BenevolentUnionType([new IntegerType(), new StringType()]), $genericTypes[0]); } elseif (count($genericTypes) === 2) { // array $keyType = TypeCombinator::intersect($genericTypes[0], new UnionType([ new IntegerType(), diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 04b005e767..ad590cb524 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -870,4 +870,10 @@ public function testBug2782(): void ]); } + public function testBug5661(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/bug-5661.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/bug-5661.php b/tests/PHPStan/Rules/Functions/data/bug-5661.php new file mode 100644 index 0000000000..471594e367 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-5661.php @@ -0,0 +1,24 @@ + $array + */ + function sayHello(array $array): void + { + echo join(', ', $array) . PHP_EOL; + } + + /** + * @param string[] $array + */ + function sayHello2(array $array): void + { + echo join(', ', $array) . PHP_EOL; + } + +} From 217fac309862f82485f8b756f09665192235aa13 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 21 Sep 2021 15:37:51 +0200 Subject: [PATCH 0353/1284] While loop - condition always false --- conf/config.level4.neon | 7 +++ .../WhileLoopAlwaysFalseConditionRule.php | 62 +++++++++++++++++++ .../WhileLoopAlwaysFalseConditionRuleTest.php | 50 +++++++++++++++ .../Comparison/data/while-loop-false.php | 35 +++++++++++ 4 files changed, 154 insertions(+) create mode 100644 src/Rules/Comparison/WhileLoopAlwaysFalseConditionRule.php create mode 100644 tests/PHPStan/Rules/Comparison/WhileLoopAlwaysFalseConditionRuleTest.php create mode 100644 tests/PHPStan/Rules/Comparison/data/while-loop-false.php diff --git a/conf/config.level4.neon b/conf/config.level4.neon index 60b726545d..31b3cf0d0d 100644 --- a/conf/config.level4.neon +++ b/conf/config.level4.neon @@ -137,6 +137,13 @@ services: tags: - phpstan.rules.rule + - + class: PHPStan\Rules\Comparison\WhileLoopAlwaysFalseConditionRule + arguments: + treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% + tags: + - phpstan.rules.rule + - class: PHPStan\Rules\TooWideTypehints\TooWideMethodReturnTypehintRule arguments: diff --git a/src/Rules/Comparison/WhileLoopAlwaysFalseConditionRule.php b/src/Rules/Comparison/WhileLoopAlwaysFalseConditionRule.php new file mode 100644 index 0000000000..f9288e90f5 --- /dev/null +++ b/src/Rules/Comparison/WhileLoopAlwaysFalseConditionRule.php @@ -0,0 +1,62 @@ + + */ +class WhileLoopAlwaysFalseConditionRule implements \PHPStan\Rules\Rule +{ + + private ConstantConditionRuleHelper $helper; + + private bool $treatPhpDocTypesAsCertain; + + public function __construct( + ConstantConditionRuleHelper $helper, + bool $treatPhpDocTypesAsCertain + ) + { + $this->helper = $helper; + $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; + } + + public function getNodeType(): string + { + return While_::class; + } + + public function processNode( + \PhpParser\Node $node, + \PHPStan\Analyser\Scope $scope + ): array + { + $exprType = $this->helper->getBooleanType($scope, $node->cond); + if ($exprType instanceof ConstantBooleanType && !$exprType->getValue()) { + $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node): RuleErrorBuilder { + if (!$this->treatPhpDocTypesAsCertain) { + return $ruleErrorBuilder; + } + + $booleanNativeType = $this->helper->getNativeBooleanType($scope, $node->cond); + if ($booleanNativeType instanceof ConstantBooleanType) { + return $ruleErrorBuilder; + } + + return $ruleErrorBuilder->tip('Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'); + }; + + return [ + $addTip(RuleErrorBuilder::message('While loop condition is always false.'))->line($node->cond->getLine()) + ->build(), + ]; + } + + return []; + } + +} diff --git a/tests/PHPStan/Rules/Comparison/WhileLoopAlwaysFalseConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/WhileLoopAlwaysFalseConditionRuleTest.php new file mode 100644 index 0000000000..7914aa6a03 --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/WhileLoopAlwaysFalseConditionRuleTest.php @@ -0,0 +1,50 @@ + + */ +class WhileLoopAlwaysFalseConditionRuleTest extends \PHPStan\Testing\RuleTestCase +{ + + /** @var bool */ + private $treatPhpDocTypesAsCertain = true; + + protected function getRule(): \PHPStan\Rules\Rule + { + return new WhileLoopAlwaysFalseConditionRule( + new ConstantConditionRuleHelper( + new ImpossibleCheckTypeHelper( + $this->createReflectionProvider(), + $this->getTypeSpecifier(), + [], + $this->treatPhpDocTypesAsCertain + ), + $this->treatPhpDocTypesAsCertain + ), + $this->treatPhpDocTypesAsCertain + ); + } + + protected function shouldTreatPhpDocTypesAsCertain(): bool + { + return $this->treatPhpDocTypesAsCertain; + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/while-loop-false.php'], [ + [ + 'While loop condition is always false.', + 10, + ], + [ + 'While loop condition is always false.', + 20, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Comparison/data/while-loop-false.php b/tests/PHPStan/Rules/Comparison/data/while-loop-false.php new file mode 100644 index 0000000000..667156e7c5 --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/while-loop-false.php @@ -0,0 +1,35 @@ + Date: Tue, 21 Sep 2021 15:43:56 +0200 Subject: [PATCH 0354/1284] While loop - condition always true --- conf/config.level4.neon | 7 +++ src/Analyser/NodeScopeResolver.php | 7 ++- src/Node/BreaklessWhileLoopNode.php | 38 +++++++++++ .../WhileLoopAlwaysTrueConditionRule.php | 63 +++++++++++++++++++ .../WhileLoopAlwaysTrueConditionRuleTest.php | 50 +++++++++++++++ .../Rules/Comparison/data/while-loop-true.php | 54 ++++++++++++++++ 6 files changed, 218 insertions(+), 1 deletion(-) create mode 100644 src/Node/BreaklessWhileLoopNode.php create mode 100644 src/Rules/Comparison/WhileLoopAlwaysTrueConditionRule.php create mode 100644 tests/PHPStan/Rules/Comparison/WhileLoopAlwaysTrueConditionRuleTest.php create mode 100644 tests/PHPStan/Rules/Comparison/data/while-loop-true.php diff --git a/conf/config.level4.neon b/conf/config.level4.neon index 31b3cf0d0d..43fe1bddf6 100644 --- a/conf/config.level4.neon +++ b/conf/config.level4.neon @@ -144,6 +144,13 @@ services: tags: - phpstan.rules.rule + - + class: PHPStan\Rules\Comparison\WhileLoopAlwaysTrueConditionRule + arguments: + treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% + tags: + - phpstan.rules.rule + - class: PHPStan\Rules\TooWideTypehints\TooWideMethodReturnTypehintRule arguments: diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index ad1ca635e3..8c6dc0db93 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -56,6 +56,7 @@ use PHPStan\File\FileReader; use PHPStan\Node\BooleanAndNode; use PHPStan\Node\BooleanOrNode; +use PHPStan\Node\BreaklessWhileLoopNode; use PHPStan\Node\CatchWithUnthrownExceptionNode; use PHPStan\Node\ClassConstantsNode; use PHPStan\Node\ClassMethodsNode; @@ -899,9 +900,13 @@ private function processStmtNode( $isIterableAtLeastOnce = $beforeCondBooleanType instanceof ConstantBooleanType && $beforeCondBooleanType->getValue(); $alwaysIterates = $condBooleanType instanceof ConstantBooleanType && $condBooleanType->getValue(); $neverIterates = $condBooleanType instanceof ConstantBooleanType && !$condBooleanType->getValue(); + $breakCount = count($finalScopeResult->getExitPointsByType(Break_::class)); + if ($breakCount === 0) { + $nodeCallback(new BreaklessWhileLoopNode($stmt), $bodyScopeMaybeRan); + } if ($alwaysIterates) { - $isAlwaysTerminating = count($finalScopeResult->getExitPointsByType(Break_::class)) === 0; + $isAlwaysTerminating = $breakCount === 0; } elseif ($isIterableAtLeastOnce) { $isAlwaysTerminating = $finalScopeResult->isAlwaysTerminating(); } else { diff --git a/src/Node/BreaklessWhileLoopNode.php b/src/Node/BreaklessWhileLoopNode.php new file mode 100644 index 0000000000..f662a139fe --- /dev/null +++ b/src/Node/BreaklessWhileLoopNode.php @@ -0,0 +1,38 @@ +getAttributes()); + $this->originalNode = $originalNode; + } + + public function getOriginalNode(): While_ + { + return $this->originalNode; + } + + public function getType(): string + { + return 'PHPStan_Node_BreaklessWhileLoop'; + } + + /** + * @return string[] + */ + public function getSubNodeNames(): array + { + return []; + } + +} diff --git a/src/Rules/Comparison/WhileLoopAlwaysTrueConditionRule.php b/src/Rules/Comparison/WhileLoopAlwaysTrueConditionRule.php new file mode 100644 index 0000000000..15d696408b --- /dev/null +++ b/src/Rules/Comparison/WhileLoopAlwaysTrueConditionRule.php @@ -0,0 +1,63 @@ + + */ +class WhileLoopAlwaysTrueConditionRule implements \PHPStan\Rules\Rule +{ + + private ConstantConditionRuleHelper $helper; + + private bool $treatPhpDocTypesAsCertain; + + public function __construct( + ConstantConditionRuleHelper $helper, + bool $treatPhpDocTypesAsCertain + ) + { + $this->helper = $helper; + $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; + } + + public function getNodeType(): string + { + return BreaklessWhileLoopNode::class; + } + + public function processNode( + \PhpParser\Node $node, + \PHPStan\Analyser\Scope $scope + ): array + { + $originalNode = $node->getOriginalNode(); + $exprType = $this->helper->getBooleanType($scope, $originalNode->cond); + if ($exprType instanceof ConstantBooleanType && $exprType->getValue()) { + $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $originalNode): RuleErrorBuilder { + if (!$this->treatPhpDocTypesAsCertain) { + return $ruleErrorBuilder; + } + + $booleanNativeType = $this->helper->getNativeBooleanType($scope, $originalNode->cond); + if ($booleanNativeType instanceof ConstantBooleanType) { + return $ruleErrorBuilder; + } + + return $ruleErrorBuilder->tip('Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'); + }; + + return [ + $addTip(RuleErrorBuilder::message('While loop condition is always true.'))->line($originalNode->cond->getLine()) + ->build(), + ]; + } + + return []; + } + +} diff --git a/tests/PHPStan/Rules/Comparison/WhileLoopAlwaysTrueConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/WhileLoopAlwaysTrueConditionRuleTest.php new file mode 100644 index 0000000000..f827c11f93 --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/WhileLoopAlwaysTrueConditionRuleTest.php @@ -0,0 +1,50 @@ + + */ +class WhileLoopAlwaysTrueConditionRuleTest extends \PHPStan\Testing\RuleTestCase +{ + + /** @var bool */ + private $treatPhpDocTypesAsCertain = true; + + protected function getRule(): \PHPStan\Rules\Rule + { + return new WhileLoopAlwaysTrueConditionRule( + new ConstantConditionRuleHelper( + new ImpossibleCheckTypeHelper( + $this->createReflectionProvider(), + $this->getTypeSpecifier(), + [], + $this->treatPhpDocTypesAsCertain + ), + $this->treatPhpDocTypesAsCertain + ), + $this->treatPhpDocTypesAsCertain + ); + } + + protected function shouldTreatPhpDocTypesAsCertain(): bool + { + return $this->treatPhpDocTypesAsCertain; + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/while-loop-true.php'], [ + [ + 'While loop condition is always true.', + 10, + ], + [ + 'While loop condition is always true.', + 20, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Comparison/data/while-loop-true.php b/tests/PHPStan/Rules/Comparison/data/while-loop-true.php new file mode 100644 index 0000000000..717e753e9d --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/while-loop-true.php @@ -0,0 +1,54 @@ + Date: Wed, 22 Sep 2021 09:49:06 +0200 Subject: [PATCH 0355/1284] mb_ereg_replace and mb_ereg_replace_callback can return null --- resources/functionMap.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index b4a7943555..72d41613df 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -6322,8 +6322,8 @@ 'mb_encoding_aliases' => ['array|false', 'encoding'=>'string'], 'mb_ereg' => ['int|false', 'pattern'=>'string', 'string'=>'string', '&w_registers='=>'array'], 'mb_ereg_match' => ['bool', 'pattern'=>'string', 'string'=>'string', 'option='=>'string'], -'mb_ereg_replace' => ['string|false', 'pattern'=>'string', 'replacement'=>'string', 'string'=>'string', 'option='=>'string'], -'mb_ereg_replace_callback' => ['string|false', 'pattern'=>'string', 'callback'=>'callable', 'string'=>'string', 'option='=>'string'], +'mb_ereg_replace' => ['string|false|null', 'pattern'=>'string', 'replacement'=>'string', 'string'=>'string', 'option='=>'string'], +'mb_ereg_replace_callback' => ['string|false|null', 'pattern'=>'string', 'callback'=>'callable', 'string'=>'string', 'option='=>'string'], 'mb_ereg_search' => ['bool', 'pattern='=>'string', 'option='=>'string'], 'mb_ereg_search_getpos' => ['int'], 'mb_ereg_search_getregs' => ['array|false'], From 8e583e91e7739364a7e8df312630668b68f960e8 Mon Sep 17 00:00:00 2001 From: hbrecht Date: Wed, 22 Sep 2021 11:11:38 +0200 Subject: [PATCH 0356/1284] Update functionMap for SoapClient methods (#683) as per https://www.php.net/manual/en/class.soapclient.php --- resources/functionMap.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 72d41613df..a0169e3562 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -10283,20 +10283,20 @@ 'snmpset' => ['bool', 'host'=>'string', 'community'=>'string', 'object_id'=>'string', 'type'=>'string', 'value'=>'mixed', 'timeout='=>'int', 'retries='=>'int'], 'snmpwalk' => ['array|false', 'host'=>'string', 'community'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'], 'snmpwalkoid' => ['array|false', 'hostname'=>'string', 'community'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'], -'SoapClient::__call' => ['', 'function_name'=>'string', 'arguments'=>'array'], +'SoapClient::__call' => ['mixed', 'function_name'=>'string', 'arguments'=>'array'], 'SoapClient::__construct' => ['void', 'wsdl'=>'mixed', 'options='=>'array|null'], -'SoapClient::__doRequest' => ['string', 'request'=>'string', 'location'=>'string', 'action'=>'string', 'version'=>'int', 'one_way='=>'int'], +'SoapClient::__doRequest' => ['string|null', 'request'=>'string', 'location'=>'string', 'action'=>'string', 'version'=>'int', 'one_way='=>'int'], 'SoapClient::__getCookies' => ['array'], -'SoapClient::__getFunctions' => ['array'], -'SoapClient::__getLastRequest' => ['string'], -'SoapClient::__getLastRequestHeaders' => ['string'], -'SoapClient::__getLastResponse' => ['string'], -'SoapClient::__getLastResponseHeaders' => ['string'], -'SoapClient::__getTypes' => ['array'], +'SoapClient::__getFunctions' => ['array|null'], +'SoapClient::__getLastRequest' => ['string|null'], +'SoapClient::__getLastRequestHeaders' => ['string|null'], +'SoapClient::__getLastResponse' => ['string|null'], +'SoapClient::__getLastResponseHeaders' => ['string|null'], +'SoapClient::__getTypes' => ['array|null'], 'SoapClient::__setCookie' => ['', 'name'=>'string', 'value='=>'string'], -'SoapClient::__setLocation' => ['string', 'new_location='=>'string'], +'SoapClient::__setLocation' => ['string|null', 'new_location='=>'string'], 'SoapClient::__setSoapHeaders' => ['bool', 'soapheaders='=>''], -'SoapClient::__soapCall' => ['', 'function_name'=>'string', 'arguments'=>'array', 'options='=>'array', 'input_headers='=>'SoapHeader|array', '&w_output_headers='=>'array'], +'SoapClient::__soapCall' => ['mixed', 'function_name'=>'string', 'arguments'=>'array', 'options='=>'array', 'input_headers='=>'SoapHeader|array', '&w_output_headers='=>'array'], 'SoapClient::SoapClient' => ['object', 'wsdl'=>'mixed', 'options='=>'array|null'], 'SoapFault::__construct' => ['void', 'faultcode'=>'string', 'string'=>'string', 'faultactor='=>'string', 'detail='=>'string', 'faultname='=>'string', 'headerfault='=>'string'], 'SoapFault::__toString' => ['string'], From a2bdfd148559dc5314dbd38ef6bb6f70aad0c351 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 22 Sep 2021 10:03:03 +0200 Subject: [PATCH 0357/1284] sprintf format string needs to be sanitized first --- src/Internal/SprintfHelper.php | 13 ++++++++ .../NonexistentOffsetInArrayDimFetchRule.php | 3 +- src/Rules/AttributesCheck.php | 27 +++++++++-------- src/Rules/Classes/ClassConstantRule.php | 3 +- src/Rules/Classes/InstantiationRule.php | 27 +++++++++-------- .../UnusedConstructorParametersRule.php | 3 +- src/Rules/Functions/CallCallablesRule.php | 3 +- .../CallToFunctionParametersRule.php | 28 +++++++++-------- .../ExistingClassesInTypehintsRule.php | 3 +- src/Rules/Generics/ClassAncestorsRule.php | 23 +++++++------- src/Rules/Generics/ClassTemplateTypeRule.php | 3 +- .../FunctionSignatureVarianceRule.php | 3 +- .../Generics/FunctionTemplateTypeRule.php | 11 ++++--- src/Rules/Generics/InterfaceAncestorsRule.php | 17 ++++++----- .../Generics/InterfaceTemplateTypeRule.php | 11 ++++--- .../Generics/MethodSignatureVarianceRule.php | 3 +- src/Rules/Generics/MethodTemplateTypeRule.php | 11 ++++--- src/Rules/Generics/TemplateTypeCheck.php | 10 ++++--- src/Rules/Generics/TraitTemplateTypeRule.php | 11 ++++--- src/Rules/Generics/UsedTraitsRule.php | 5 ++-- src/Rules/Methods/CallMethodsRule.php | 5 ++-- src/Rules/Methods/CallStaticMethodsRule.php | 11 +++---- .../ExistingClassesInTypehintsRule.php | 16 ++++++---- ...ncompatibleClassConstantPhpDocTypeRule.php | 20 ++++++++----- .../PhpDoc/IncompatiblePhpDocTypeRule.php | 11 ++++--- .../IncompatiblePropertyPhpDocTypeRule.php | 20 ++++++++----- .../PhpDoc/InvalidPhpDocVarTagTypeRule.php | 10 ++++--- src/Rules/Properties/AccessPropertiesRule.php | 3 +- .../Properties/AccessStaticPropertiesRule.php | 3 +- ...nexistentOffsetInArrayDimFetchRuleTest.php | 11 +++++++ tests/PHPStan/Rules/Arrays/data/bug-5669.php | 30 +++++++++++++++++++ 31 files changed, 235 insertions(+), 123 deletions(-) create mode 100644 src/Internal/SprintfHelper.php create mode 100644 tests/PHPStan/Rules/Arrays/data/bug-5669.php diff --git a/src/Internal/SprintfHelper.php b/src/Internal/SprintfHelper.php new file mode 100644 index 0000000000..76f4fa19bd --- /dev/null +++ b/src/Internal/SprintfHelper.php @@ -0,0 +1,13 @@ +dim !== null) { $dimType = $scope->getType($node->dim); - $unknownClassPattern = sprintf('Access to offset %s on an unknown class %%s.', $dimType->describe(VerbosityLevel::value())); + $unknownClassPattern = sprintf('Access to offset %s on an unknown class %%s.', SprintfHelper::escapeFormatString($dimType->describe(VerbosityLevel::value()))); } else { $dimType = null; $unknownClassPattern = 'Access to an offset on an unknown class %s.'; diff --git a/src/Rules/AttributesCheck.php b/src/Rules/AttributesCheck.php index c9080f772f..205e3994d4 100644 --- a/src/Rules/AttributesCheck.php +++ b/src/Rules/AttributesCheck.php @@ -5,6 +5,7 @@ use PhpParser\Node\AttributeGroup; use PhpParser\Node\Expr\New_; use PHPStan\Analyser\Scope; +use PHPStan\Internal\SprintfHelper; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Reflection\ReflectionProvider; @@ -90,25 +91,27 @@ public function check( $errors[] = RuleErrorBuilder::message(sprintf('Constructor of attribute class %s is not public.', $name))->line($attribute->getLine())->build(); } + $attributeClassName = SprintfHelper::escapeFormatString($attributeClass->getDisplayName()); + $parameterErrors = $this->functionCallParametersCheck->check( ParametersAcceptorSelector::selectSingle($attributeConstructor->getVariants()), $scope, $attributeConstructor->getDeclaringClass()->isBuiltin(), new New_($attribute->name, $attribute->args, $attribute->getAttributes()), [ - 'Attribute class ' . $attributeClass->getDisplayName() . ' constructor invoked with %d parameter, %d required.', - 'Attribute class ' . $attributeClass->getDisplayName() . ' constructor invoked with %d parameters, %d required.', - 'Attribute class ' . $attributeClass->getDisplayName() . ' constructor invoked with %d parameter, at least %d required.', - 'Attribute class ' . $attributeClass->getDisplayName() . ' constructor invoked with %d parameters, at least %d required.', - 'Attribute class ' . $attributeClass->getDisplayName() . ' constructor invoked with %d parameter, %d-%d required.', - 'Attribute class ' . $attributeClass->getDisplayName() . ' constructor invoked with %d parameters, %d-%d required.', - 'Parameter %s of attribute class ' . $attributeClass->getDisplayName() . ' constructor expects %s, %s given.', + 'Attribute class ' . $attributeClassName . ' constructor invoked with %d parameter, %d required.', + 'Attribute class ' . $attributeClassName . ' constructor invoked with %d parameters, %d required.', + 'Attribute class ' . $attributeClassName . ' constructor invoked with %d parameter, at least %d required.', + 'Attribute class ' . $attributeClassName . ' constructor invoked with %d parameters, at least %d required.', + 'Attribute class ' . $attributeClassName . ' constructor invoked with %d parameter, %d-%d required.', + 'Attribute class ' . $attributeClassName . ' constructor invoked with %d parameters, %d-%d required.', + 'Parameter %s of attribute class ' . $attributeClassName . ' constructor expects %s, %s given.', '', // constructor does not have a return type - 'Parameter %s of attribute class ' . $attributeClass->getDisplayName() . ' constructor is passed by reference, so it expects variables only', - 'Unable to resolve the template type %s in instantiation of attribute class ' . $attributeClass->getDisplayName(), - 'Missing parameter $%s in call to ' . $attributeClass->getDisplayName() . ' constructor.', - 'Unknown parameter $%s in call to ' . $attributeClass->getDisplayName() . ' constructor.', - 'Return type of call to ' . $attributeClass->getDisplayName() . ' constructor contains unresolvable type.', + 'Parameter %s of attribute class ' . $attributeClassName . ' constructor is passed by reference, so it expects variables only', + 'Unable to resolve the template type %s in instantiation of attribute class ' . $attributeClassName, + 'Missing parameter $%s in call to ' . $attributeClassName . ' constructor.', + 'Unknown parameter $%s in call to ' . $attributeClassName . ' constructor.', + 'Return type of call to ' . $attributeClassName . ' constructor contains unresolvable type.', ] ); diff --git a/src/Rules/Classes/ClassConstantRule.php b/src/Rules/Classes/ClassConstantRule.php index 64d4e11bec..4ab9d2e083 100644 --- a/src/Rules/Classes/ClassConstantRule.php +++ b/src/Rules/Classes/ClassConstantRule.php @@ -5,6 +5,7 @@ use PhpParser\Node; use PhpParser\Node\Expr\ClassConstFetch; use PHPStan\Analyser\Scope; +use PHPStan\Internal\SprintfHelper; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\ClassCaseSensitivityCheck; @@ -118,7 +119,7 @@ public function processNode(Node $node, Scope $scope): array $classTypeResult = $this->ruleLevelHelper->findTypeToCheck( $scope, $class, - sprintf('Access to constant %s on an unknown class %%s.', $constantName), + sprintf('Access to constant %s on an unknown class %%s.', SprintfHelper::escapeFormatString($constantName)), static function (Type $type) use ($constantName): bool { return $type->canAccessConstants()->yes() && $type->hasConstant($constantName)->yes(); } diff --git a/src/Rules/Classes/InstantiationRule.php b/src/Rules/Classes/InstantiationRule.php index b1fb7c1402..71d4b62949 100644 --- a/src/Rules/Classes/InstantiationRule.php +++ b/src/Rules/Classes/InstantiationRule.php @@ -5,6 +5,7 @@ use PhpParser\Node; use PhpParser\Node\Expr\New_; use PHPStan\Analyser\Scope; +use PHPStan\Internal\SprintfHelper; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Reflection\Php\PhpMethodReflection; use PHPStan\Reflection\ReflectionProvider; @@ -175,6 +176,8 @@ private function checkClassName(string $class, bool $isName, Node $node, Scope $ ))->build(); } + $classDisplayName = SprintfHelper::escapeFormatString($classReflection->getDisplayName()); + return array_merge($messages, $this->check->check( ParametersAcceptorSelector::selectFromArgs( $scope, @@ -185,19 +188,19 @@ private function checkClassName(string $class, bool $isName, Node $node, Scope $ $constructorReflection->getDeclaringClass()->isBuiltin(), $node, [ - 'Class ' . $classReflection->getDisplayName() . ' constructor invoked with %d parameter, %d required.', - 'Class ' . $classReflection->getDisplayName() . ' constructor invoked with %d parameters, %d required.', - 'Class ' . $classReflection->getDisplayName() . ' constructor invoked with %d parameter, at least %d required.', - 'Class ' . $classReflection->getDisplayName() . ' constructor invoked with %d parameters, at least %d required.', - 'Class ' . $classReflection->getDisplayName() . ' constructor invoked with %d parameter, %d-%d required.', - 'Class ' . $classReflection->getDisplayName() . ' constructor invoked with %d parameters, %d-%d required.', - 'Parameter %s of class ' . $classReflection->getDisplayName() . ' constructor expects %s, %s given.', + 'Class ' . $classDisplayName . ' constructor invoked with %d parameter, %d required.', + 'Class ' . $classDisplayName . ' constructor invoked with %d parameters, %d required.', + 'Class ' . $classDisplayName . ' constructor invoked with %d parameter, at least %d required.', + 'Class ' . $classDisplayName . ' constructor invoked with %d parameters, at least %d required.', + 'Class ' . $classDisplayName . ' constructor invoked with %d parameter, %d-%d required.', + 'Class ' . $classDisplayName . ' constructor invoked with %d parameters, %d-%d required.', + 'Parameter %s of class ' . $classDisplayName . ' constructor expects %s, %s given.', '', // constructor does not have a return type - 'Parameter %s of class ' . $classReflection->getDisplayName() . ' constructor is passed by reference, so it expects variables only', - 'Unable to resolve the template type %s in instantiation of class ' . $classReflection->getDisplayName(), - 'Missing parameter $%s in call to ' . $classReflection->getDisplayName() . ' constructor.', - 'Unknown parameter $%s in call to ' . $classReflection->getDisplayName() . ' constructor.', - 'Return type of call to ' . $classReflection->getDisplayName() . ' constructor contains unresolvable type.', + 'Parameter %s of class ' . $classDisplayName . ' constructor is passed by reference, so it expects variables only', + 'Unable to resolve the template type %s in instantiation of class ' . $classDisplayName, + 'Missing parameter $%s in call to ' . $classDisplayName . ' constructor.', + 'Unknown parameter $%s in call to ' . $classDisplayName . ' constructor.', + 'Return type of call to ' . $classDisplayName . ' constructor contains unresolvable type.', ] )); } diff --git a/src/Rules/Classes/UnusedConstructorParametersRule.php b/src/Rules/Classes/UnusedConstructorParametersRule.php index 1d27034d71..fcfbfd1547 100644 --- a/src/Rules/Classes/UnusedConstructorParametersRule.php +++ b/src/Rules/Classes/UnusedConstructorParametersRule.php @@ -6,6 +6,7 @@ use PhpParser\Node\Expr\Variable; use PhpParser\Node\Param; use PHPStan\Analyser\Scope; +use PHPStan\Internal\SprintfHelper; use PHPStan\Node\InClassMethodNode; use PHPStan\Reflection\MethodReflection; use PHPStan\Rules\UnusedFunctionParametersCheck; @@ -50,7 +51,7 @@ public function processNode(Node $node, Scope $scope): array $message = sprintf( 'Constructor of class %s has an unused parameter $%%s.', - $scope->getClassReflection()->getDisplayName() + SprintfHelper::escapeFormatString($scope->getClassReflection()->getDisplayName()) ); if ($scope->getClassReflection()->isAnonymous()) { $message = 'Constructor of an anonymous class has an unused parameter $%s.'; diff --git a/src/Rules/Functions/CallCallablesRule.php b/src/Rules/Functions/CallCallablesRule.php index b0efc815d6..cbe6728ed7 100644 --- a/src/Rules/Functions/CallCallablesRule.php +++ b/src/Rules/Functions/CallCallablesRule.php @@ -3,6 +3,7 @@ namespace PHPStan\Rules\Functions; use PHPStan\Analyser\Scope; +use PHPStan\Internal\SprintfHelper; use PHPStan\Reflection\InaccessibleMethod; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Rules\FunctionCallParametersCheck; @@ -104,7 +105,7 @@ static function (Type $type): bool { if ($type instanceof ClosureType) { $callableDescription = 'closure'; } else { - $callableDescription = sprintf('callable %s', $type->describe(VerbosityLevel::value())); + $callableDescription = sprintf('callable %s', SprintfHelper::escapeFormatString($type->describe(VerbosityLevel::value()))); } return array_merge( diff --git a/src/Rules/Functions/CallToFunctionParametersRule.php b/src/Rules/Functions/CallToFunctionParametersRule.php index f7fd2b7d27..81371eadb7 100644 --- a/src/Rules/Functions/CallToFunctionParametersRule.php +++ b/src/Rules/Functions/CallToFunctionParametersRule.php @@ -5,6 +5,7 @@ use PhpParser\Node; use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; +use PHPStan\Internal\SprintfHelper; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\FunctionCallParametersCheck; @@ -41,6 +42,7 @@ public function processNode(Node $node, Scope $scope): array } $function = $this->reflectionProvider->getFunction($node->name, $scope); + $functionName = SprintfHelper::escapeFormatString($function->getName()); return $this->check->check( ParametersAcceptorSelector::selectFromArgs( @@ -52,19 +54,19 @@ public function processNode(Node $node, Scope $scope): array $function->isBuiltin(), $node, [ - 'Function ' . $function->getName() . ' invoked with %d parameter, %d required.', - 'Function ' . $function->getName() . ' invoked with %d parameters, %d required.', - 'Function ' . $function->getName() . ' invoked with %d parameter, at least %d required.', - 'Function ' . $function->getName() . ' invoked with %d parameters, at least %d required.', - 'Function ' . $function->getName() . ' invoked with %d parameter, %d-%d required.', - 'Function ' . $function->getName() . ' invoked with %d parameters, %d-%d required.', - 'Parameter %s of function ' . $function->getName() . ' expects %s, %s given.', - 'Result of function ' . $function->getName() . ' (void) is used.', - 'Parameter %s of function ' . $function->getName() . ' is passed by reference, so it expects variables only.', - 'Unable to resolve the template type %s in call to function ' . $function->getName(), - 'Missing parameter $%s in call to function ' . $function->getName() . '.', - 'Unknown parameter $%s in call to function ' . $function->getName() . '.', - 'Return type of call to function ' . $function->getName() . ' contains unresolvable type.', + 'Function ' . $functionName . ' invoked with %d parameter, %d required.', + 'Function ' . $functionName . ' invoked with %d parameters, %d required.', + 'Function ' . $functionName . ' invoked with %d parameter, at least %d required.', + 'Function ' . $functionName . ' invoked with %d parameters, at least %d required.', + 'Function ' . $functionName . ' invoked with %d parameter, %d-%d required.', + 'Function ' . $functionName . ' invoked with %d parameters, %d-%d required.', + 'Parameter %s of function ' . $functionName . ' expects %s, %s given.', + 'Result of function ' . $functionName . ' (void) is used.', + 'Parameter %s of function ' . $functionName . ' is passed by reference, so it expects variables only.', + 'Unable to resolve the template type %s in call to function ' . $functionName, + 'Missing parameter $%s in call to function ' . $functionName . '.', + 'Unknown parameter $%s in call to function ' . $functionName . '.', + 'Return type of call to function ' . $functionName . ' contains unresolvable type.', ] ); } diff --git a/src/Rules/Functions/ExistingClassesInTypehintsRule.php b/src/Rules/Functions/ExistingClassesInTypehintsRule.php index 0b3cfdc77b..04256e7074 100644 --- a/src/Rules/Functions/ExistingClassesInTypehintsRule.php +++ b/src/Rules/Functions/ExistingClassesInTypehintsRule.php @@ -4,6 +4,7 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\Internal\SprintfHelper; use PHPStan\Node\InFunctionNode; use PHPStan\Reflection\Php\PhpFunctionFromParserNodeReflection; use PHPStan\Rules\FunctionDefinitionCheck; @@ -32,7 +33,7 @@ public function processNode(Node $node, Scope $scope): array return []; } - $functionName = $scope->getFunction()->getName(); + $functionName = SprintfHelper::escapeFormatString($scope->getFunction()->getName()); return $this->check->checkFunction( $node->getOriginalNode(), diff --git a/src/Rules/Generics/ClassAncestorsRule.php b/src/Rules/Generics/ClassAncestorsRule.php index 00a83bac5c..96c64dfef7 100644 --- a/src/Rules/Generics/ClassAncestorsRule.php +++ b/src/Rules/Generics/ClassAncestorsRule.php @@ -4,6 +4,7 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\Internal\SprintfHelper; use PHPStan\Node\InClassNode; use PHPStan\PhpDoc\Tag\ExtendsTag; use PHPStan\PhpDoc\Tag\ImplementsTag; @@ -69,21 +70,23 @@ public function processNode(Node $node, Scope $scope): array $implementsTags = $resolvedPhpDoc->getImplementsTags(); } + $escapedClassName = SprintfHelper::escapeFormatString($className); + $extendsErrors = $this->genericAncestorsCheck->check( $originalNode->extends !== null ? [$originalNode->extends] : [], array_map(static function (ExtendsTag $tag): Type { return $tag->getType(); }, $extendsTags), - sprintf('Class %s @extends tag contains incompatible type %%s.', $className), - sprintf('Class %s has @extends tag, but does not extend any class.', $className), - sprintf('The @extends tag of class %s describes %%s but the class extends %%s.', $className), + sprintf('Class %s @extends tag contains incompatible type %%s.', $escapedClassName), + sprintf('Class %s has @extends tag, but does not extend any class.', $escapedClassName), + sprintf('The @extends tag of class %s describes %%s but the class extends %%s.', $escapedClassName), 'PHPDoc tag @extends contains generic type %s but class %s is not generic.', 'Generic type %s in PHPDoc tag @extends does not specify all template types of class %s: %s', 'Generic type %s in PHPDoc tag @extends specifies %d template types, but class %s supports only %d: %s', 'Type %s in generic type %s in PHPDoc tag @extends is not subtype of template type %s of class %s.', 'PHPDoc tag @extends has invalid type %s.', - sprintf('Class %s extends generic class %%s but does not specify its types: %%s', $className), - sprintf('in extended type %%s of class %s', $className) + sprintf('Class %s extends generic class %%s but does not specify its types: %%s', $escapedClassName), + sprintf('in extended type %%s of class %s', $escapedClassName) ); $implementsErrors = $this->genericAncestorsCheck->check( @@ -91,16 +94,16 @@ public function processNode(Node $node, Scope $scope): array array_map(static function (ImplementsTag $tag): Type { return $tag->getType(); }, $implementsTags), - sprintf('Class %s @implements tag contains incompatible type %%s.', $className), - sprintf('Class %s has @implements tag, but does not implement any interface.', $className), - sprintf('The @implements tag of class %s describes %%s but the class implements: %%s', $className), + sprintf('Class %s @implements tag contains incompatible type %%s.', $escapedClassName), + sprintf('Class %s has @implements tag, but does not implement any interface.', $escapedClassName), + sprintf('The @implements tag of class %s describes %%s but the class implements: %%s', $escapedClassName), 'PHPDoc tag @implements contains generic type %s but interface %s is not generic.', 'Generic type %s in PHPDoc tag @implements does not specify all template types of interface %s: %s', 'Generic type %s in PHPDoc tag @implements specifies %d template types, but interface %s supports only %d: %s', 'Type %s in generic type %s in PHPDoc tag @implements is not subtype of template type %s of interface %s.', 'PHPDoc tag @implements has invalid type %s.', - sprintf('Class %s implements generic interface %%s but does not specify its types: %%s', $className), - sprintf('in implemented type %%s of class %s', $className) + sprintf('Class %s implements generic interface %%s but does not specify its types: %%s', $escapedClassName), + sprintf('in implemented type %%s of class %s', $escapedClassName) ); foreach ($this->crossCheckInterfacesHelper->check($classReflection) as $error) { diff --git a/src/Rules/Generics/ClassTemplateTypeRule.php b/src/Rules/Generics/ClassTemplateTypeRule.php index ec726ce894..f611c41093 100644 --- a/src/Rules/Generics/ClassTemplateTypeRule.php +++ b/src/Rules/Generics/ClassTemplateTypeRule.php @@ -4,6 +4,7 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\Internal\SprintfHelper; use PHPStan\Node\InClassNode; use PHPStan\Rules\Rule; use PHPStan\Type\Generic\TemplateTypeScope; @@ -38,7 +39,7 @@ public function processNode(Node $node, Scope $scope): array if ($classReflection->isAnonymous()) { $displayName = 'anonymous class'; } else { - $displayName = 'class ' . $classReflection->getDisplayName(); + $displayName = 'class ' . SprintfHelper::escapeFormatString($classReflection->getDisplayName()); } return $this->templateTypeCheck->check( diff --git a/src/Rules/Generics/FunctionSignatureVarianceRule.php b/src/Rules/Generics/FunctionSignatureVarianceRule.php index a6d4699100..6e07cbc4c5 100644 --- a/src/Rules/Generics/FunctionSignatureVarianceRule.php +++ b/src/Rules/Generics/FunctionSignatureVarianceRule.php @@ -4,6 +4,7 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\Internal\SprintfHelper; use PHPStan\Node\InFunctionNode; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Rules\Rule; @@ -37,7 +38,7 @@ public function processNode(Node $node, Scope $scope): array return $this->varianceCheck->checkParametersAcceptor( ParametersAcceptorSelector::selectSingle($functionReflection->getVariants()), - sprintf('in parameter %%s of function %s()', $functionName), + sprintf('in parameter %%s of function %s()', SprintfHelper::escapeFormatString($functionName)), sprintf('in return type of function %s()', $functionName), sprintf('in function %s()', $functionName), false diff --git a/src/Rules/Generics/FunctionTemplateTypeRule.php b/src/Rules/Generics/FunctionTemplateTypeRule.php index 2a6d2bc921..03d59f3397 100644 --- a/src/Rules/Generics/FunctionTemplateTypeRule.php +++ b/src/Rules/Generics/FunctionTemplateTypeRule.php @@ -4,6 +4,7 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\Internal\SprintfHelper; use PHPStan\Rules\Rule; use PHPStan\Type\FileTypeMapper; use PHPStan\Type\Generic\TemplateTypeScope; @@ -52,14 +53,16 @@ public function processNode(Node $node, Scope $scope): array $docComment->getText() ); + $escapedFunctionName = SprintfHelper::escapeFormatString($functionName); + return $this->templateTypeCheck->check( $node, TemplateTypeScope::createWithFunction($functionName), $resolvedPhpDoc->getTemplateTags(), - sprintf('PHPDoc tag @template for function %s() cannot have existing class %%s as its name.', $functionName), - sprintf('PHPDoc tag @template for function %s() cannot have existing type alias %%s as its name.', $functionName), - sprintf('PHPDoc tag @template %%s for function %s() has invalid bound type %%s.', $functionName), - sprintf('PHPDoc tag @template %%s for function %s() with bound type %%s is not supported.', $functionName) + sprintf('PHPDoc tag @template for function %s() cannot have existing class %%s as its name.', $escapedFunctionName), + sprintf('PHPDoc tag @template for function %s() cannot have existing type alias %%s as its name.', $escapedFunctionName), + sprintf('PHPDoc tag @template %%s for function %s() has invalid bound type %%s.', $escapedFunctionName), + sprintf('PHPDoc tag @template %%s for function %s() with bound type %%s is not supported.', $escapedFunctionName) ); } diff --git a/src/Rules/Generics/InterfaceAncestorsRule.php b/src/Rules/Generics/InterfaceAncestorsRule.php index ee0e33b4e9..fad7ac5ba4 100644 --- a/src/Rules/Generics/InterfaceAncestorsRule.php +++ b/src/Rules/Generics/InterfaceAncestorsRule.php @@ -4,6 +4,7 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\Internal\SprintfHelper; use PHPStan\Node\InClassNode; use PHPStan\PhpDoc\Tag\ExtendsTag; use PHPStan\PhpDoc\Tag\ImplementsTag; @@ -66,21 +67,23 @@ public function processNode(Node $node, Scope $scope): array $implementsTags = $resolvedPhpDoc->getImplementsTags(); } + $escapedInterfaceName = SprintfHelper::escapeFormatString($interfaceName); + $extendsErrors = $this->genericAncestorsCheck->check( $originalNode->extends, array_map(static function (ExtendsTag $tag): Type { return $tag->getType(); }, $extendsTags), - sprintf('Interface %s @extends tag contains incompatible type %%s.', $interfaceName), - sprintf('Interface %s has @extends tag, but does not extend any interface.', $interfaceName), - sprintf('The @extends tag of interface %s describes %%s but the interface extends: %%s', $interfaceName), + sprintf('Interface %s @extends tag contains incompatible type %%s.', $escapedInterfaceName), + sprintf('Interface %s has @extends tag, but does not extend any interface.', $escapedInterfaceName), + sprintf('The @extends tag of interface %s describes %%s but the interface extends: %%s', $escapedInterfaceName), 'PHPDoc tag @extends contains generic type %s but interface %s is not generic.', 'Generic type %s in PHPDoc tag @extends does not specify all template types of interface %s: %s', 'Generic type %s in PHPDoc tag @extends specifies %d template types, but interface %s supports only %d: %s', 'Type %s in generic type %s in PHPDoc tag @extends is not subtype of template type %s of interface %s.', 'PHPDoc tag @extends has invalid type %s.', - sprintf('Interface %s extends generic interface %%s but does not specify its types: %%s', $interfaceName), - sprintf('in extended type %%s of interface %s', $interfaceName) + sprintf('Interface %s extends generic interface %%s but does not specify its types: %%s', $escapedInterfaceName), + sprintf('in extended type %%s of interface %s', $escapedInterfaceName) ); $implementsErrors = $this->genericAncestorsCheck->check( @@ -88,8 +91,8 @@ public function processNode(Node $node, Scope $scope): array array_map(static function (ImplementsTag $tag): Type { return $tag->getType(); }, $implementsTags), - sprintf('Interface %s @implements tag contains incompatible type %%s.', $interfaceName), - sprintf('Interface %s has @implements tag, but can not implement any interface, must extend from it.', $interfaceName), + sprintf('Interface %s @implements tag contains incompatible type %%s.', $escapedInterfaceName), + sprintf('Interface %s has @implements tag, but can not implement any interface, must extend from it.', $escapedInterfaceName), '', '', '', diff --git a/src/Rules/Generics/InterfaceTemplateTypeRule.php b/src/Rules/Generics/InterfaceTemplateTypeRule.php index 87f8b8cded..b26ab74fea 100644 --- a/src/Rules/Generics/InterfaceTemplateTypeRule.php +++ b/src/Rules/Generics/InterfaceTemplateTypeRule.php @@ -4,6 +4,7 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\Internal\SprintfHelper; use PHPStan\Rules\Rule; use PHPStan\Type\FileTypeMapper; use PHPStan\Type\Generic\TemplateTypeScope; @@ -52,14 +53,16 @@ public function processNode(Node $node, Scope $scope): array $docComment->getText() ); + $escapadInterfaceName = SprintfHelper::escapeFormatString($interfaceName); + return $this->templateTypeCheck->check( $node, TemplateTypeScope::createWithClass($interfaceName), $resolvedPhpDoc->getTemplateTags(), - sprintf('PHPDoc tag @template for interface %s cannot have existing class %%s as its name.', $interfaceName), - sprintf('PHPDoc tag @template for interface %s cannot have existing type alias %%s as its name.', $interfaceName), - sprintf('PHPDoc tag @template %%s for interface %s has invalid bound type %%s.', $interfaceName), - sprintf('PHPDoc tag @template %%s for interface %s with bound type %%s is not supported.', $interfaceName) + sprintf('PHPDoc tag @template for interface %s cannot have existing class %%s as its name.', $escapadInterfaceName), + sprintf('PHPDoc tag @template for interface %s cannot have existing type alias %%s as its name.', $escapadInterfaceName), + sprintf('PHPDoc tag @template %%s for interface %s has invalid bound type %%s.', $escapadInterfaceName), + sprintf('PHPDoc tag @template %%s for interface %s with bound type %%s is not supported.', $escapadInterfaceName) ); } diff --git a/src/Rules/Generics/MethodSignatureVarianceRule.php b/src/Rules/Generics/MethodSignatureVarianceRule.php index 49e452476a..0b96397f2a 100644 --- a/src/Rules/Generics/MethodSignatureVarianceRule.php +++ b/src/Rules/Generics/MethodSignatureVarianceRule.php @@ -4,6 +4,7 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\Internal\SprintfHelper; use PHPStan\Node\InClassMethodNode; use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\ParametersAcceptorSelector; @@ -36,7 +37,7 @@ public function processNode(Node $node, Scope $scope): array return $this->varianceCheck->checkParametersAcceptor( ParametersAcceptorSelector::selectSingle($method->getVariants()), - sprintf('in parameter %%s of method %s::%s()', $method->getDeclaringClass()->getDisplayName(), $method->getName()), + sprintf('in parameter %%s of method %s::%s()', SprintfHelper::escapeFormatString($method->getDeclaringClass()->getDisplayName()), SprintfHelper::escapeFormatString($method->getName())), sprintf('in return type of method %s::%s()', $method->getDeclaringClass()->getDisplayName(), $method->getName()), sprintf('in method %s::%s()', $method->getDeclaringClass()->getDisplayName(), $method->getName()), $method->getName() === '__construct' || $method->isStatic() diff --git a/src/Rules/Generics/MethodTemplateTypeRule.php b/src/Rules/Generics/MethodTemplateTypeRule.php index 57ac147a2c..09e7b26b54 100644 --- a/src/Rules/Generics/MethodTemplateTypeRule.php +++ b/src/Rules/Generics/MethodTemplateTypeRule.php @@ -4,6 +4,7 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\Internal\SprintfHelper; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\FileTypeMapper; @@ -57,14 +58,16 @@ public function processNode(Node $node, Scope $scope): array ); $methodTemplateTags = $resolvedPhpDoc->getTemplateTags(); + $escapedClassName = SprintfHelper::escapeFormatString($className); + $escapedMethodName = SprintfHelper::escapeFormatString($methodName); $messages = $this->templateTypeCheck->check( $node, TemplateTypeScope::createWithMethod($className, $methodName), $methodTemplateTags, - sprintf('PHPDoc tag @template for method %s::%s() cannot have existing class %%s as its name.', $className, $methodName), - sprintf('PHPDoc tag @template for method %s::%s() cannot have existing type alias %%s as its name.', $className, $methodName), - sprintf('PHPDoc tag @template %%s for method %s::%s() has invalid bound type %%s.', $className, $methodName), - sprintf('PHPDoc tag @template %%s for method %s::%s() with bound type %%s is not supported.', $className, $methodName) + sprintf('PHPDoc tag @template for method %s::%s() cannot have existing class %%s as its name.', $escapedClassName, $escapedMethodName), + sprintf('PHPDoc tag @template for method %s::%s() cannot have existing type alias %%s as its name.', $escapedClassName, $escapedMethodName), + sprintf('PHPDoc tag @template %%s for method %s::%s() has invalid bound type %%s.', $escapedClassName, $escapedMethodName), + sprintf('PHPDoc tag @template %%s for method %s::%s() with bound type %%s is not supported.', $escapedClassName, $escapedMethodName) ); $classTemplateTypes = $classReflection->getTemplateTypeMap()->getTypes(); diff --git a/src/Rules/Generics/TemplateTypeCheck.php b/src/Rules/Generics/TemplateTypeCheck.php index 1d6ca66b19..5286a2625b 100644 --- a/src/Rules/Generics/TemplateTypeCheck.php +++ b/src/Rules/Generics/TemplateTypeCheck.php @@ -3,6 +3,7 @@ namespace PHPStan\Rules\Generics; use PhpParser\Node; +use PHPStan\Internal\SprintfHelper; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\ClassCaseSensitivityCheck; use PHPStan\Rules\ClassNameNodePair; @@ -132,12 +133,13 @@ public function check( return $type; }); + $escapedTemplateTagName = SprintfHelper::escapeFormatString($templateTagName); $genericObjectErrors = $this->genericObjectTypeCheck->check( $boundType, - sprintf('PHPDoc tag @template %s bound contains generic type %%s but class %%s is not generic.', $templateTagName), - sprintf('PHPDoc tag @template %s bound has type %%s which does not specify all template types of class %%s: %%s', $templateTagName), - sprintf('PHPDoc tag @template %s bound has type %%s which specifies %%d template types, but class %%s supports only %%d: %%s', $templateTagName), - sprintf('Type %%s in generic type %%s in PHPDoc tag @template %s is not subtype of template type %%s of class %%s.', $templateTagName) + sprintf('PHPDoc tag @template %s bound contains generic type %%s but class %%s is not generic.', $escapedTemplateTagName), + sprintf('PHPDoc tag @template %s bound has type %%s which does not specify all template types of class %%s: %%s', $escapedTemplateTagName), + sprintf('PHPDoc tag @template %s bound has type %%s which specifies %%d template types, but class %%s supports only %%d: %%s', $escapedTemplateTagName), + sprintf('Type %%s in generic type %%s in PHPDoc tag @template %s is not subtype of template type %%s of class %%s.', $escapedTemplateTagName) ); foreach ($genericObjectErrors as $genericObjectError) { $messages[] = $genericObjectError; diff --git a/src/Rules/Generics/TraitTemplateTypeRule.php b/src/Rules/Generics/TraitTemplateTypeRule.php index 21b89339cd..3f756febf4 100644 --- a/src/Rules/Generics/TraitTemplateTypeRule.php +++ b/src/Rules/Generics/TraitTemplateTypeRule.php @@ -4,6 +4,7 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\Internal\SprintfHelper; use PHPStan\Rules\Rule; use PHPStan\Type\FileTypeMapper; use PHPStan\Type\Generic\TemplateTypeScope; @@ -52,14 +53,16 @@ public function processNode(Node $node, Scope $scope): array $docComment->getText() ); + $escapedTraitName = SprintfHelper::escapeFormatString($traitName); + return $this->templateTypeCheck->check( $node, TemplateTypeScope::createWithClass($traitName), $resolvedPhpDoc->getTemplateTags(), - sprintf('PHPDoc tag @template for trait %s cannot have existing class %%s as its name.', $traitName), - sprintf('PHPDoc tag @template for trait %s cannot have existing type alias %%s as its name.', $traitName), - sprintf('PHPDoc tag @template %%s for trait %s has invalid bound type %%s.', $traitName), - sprintf('PHPDoc tag @template %%s for trait %s with bound type %%s is not supported.', $traitName) + sprintf('PHPDoc tag @template for trait %s cannot have existing class %%s as its name.', $escapedTraitName), + sprintf('PHPDoc tag @template for trait %s cannot have existing type alias %%s as its name.', $escapedTraitName), + sprintf('PHPDoc tag @template %%s for trait %s has invalid bound type %%s.', $escapedTraitName), + sprintf('PHPDoc tag @template %%s for trait %s with bound type %%s is not supported.', $escapedTraitName) ); } diff --git a/src/Rules/Generics/UsedTraitsRule.php b/src/Rules/Generics/UsedTraitsRule.php index 9e102bf341..49ff292ece 100644 --- a/src/Rules/Generics/UsedTraitsRule.php +++ b/src/Rules/Generics/UsedTraitsRule.php @@ -4,6 +4,7 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\Internal\SprintfHelper; use PHPStan\PhpDoc\Tag\UsesTag; use PHPStan\Rules\Rule; use PHPStan\Type\FileTypeMapper; @@ -57,10 +58,10 @@ public function processNode(Node $node, Scope $scope): array $useTags = $resolvedPhpDoc->getUsesTags(); } - $description = sprintf('class %s', $className); + $description = sprintf('class %s', SprintfHelper::escapeFormatString($className)); $typeDescription = 'class'; if ($traitName !== null) { - $description = sprintf('trait %s', $traitName); + $description = sprintf('trait %s', SprintfHelper::escapeFormatString($traitName)); $typeDescription = 'trait'; } diff --git a/src/Rules/Methods/CallMethodsRule.php b/src/Rules/Methods/CallMethodsRule.php index c64ea1db91..8007179b40 100644 --- a/src/Rules/Methods/CallMethodsRule.php +++ b/src/Rules/Methods/CallMethodsRule.php @@ -5,6 +5,7 @@ use PhpParser\Node; use PhpParser\Node\Expr\MethodCall; use PHPStan\Analyser\Scope; +use PHPStan\Internal\SprintfHelper; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\FunctionCallParametersCheck; @@ -60,7 +61,7 @@ public function processNode(Node $node, Scope $scope): array $typeResult = $this->ruleLevelHelper->findTypeToCheck( $scope, $node->var, - sprintf('Call to method %s() on an unknown class %%s.', $name), + sprintf('Call to method %s() on an unknown class %%s.', SprintfHelper::escapeFormatString($name)), static function (Type $type) use ($name): bool { return $type->canCallMethods()->yes() && $type->hasMethod($name)->yes(); } @@ -124,7 +125,7 @@ static function (Type $type) use ($name): bool { $methodReflection = $type->getMethod($name, $scope); $declaringClass = $methodReflection->getDeclaringClass(); - $messagesMethodName = $declaringClass->getDisplayName() . '::' . $methodReflection->getName() . '()'; + $messagesMethodName = SprintfHelper::escapeFormatString($declaringClass->getDisplayName() . '::' . $methodReflection->getName() . '()'); $errors = []; if (!$scope->canCallMethod($methodReflection)) { $errors[] = RuleErrorBuilder::message(sprintf( diff --git a/src/Rules/Methods/CallStaticMethodsRule.php b/src/Rules/Methods/CallStaticMethodsRule.php index 68fcacc52f..cdef6c8fdf 100644 --- a/src/Rules/Methods/CallStaticMethodsRule.php +++ b/src/Rules/Methods/CallStaticMethodsRule.php @@ -6,6 +6,7 @@ use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Name; use PHPStan\Analyser\Scope; +use PHPStan\Internal\SprintfHelper; use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\Native\NativeMethodReflection; use PHPStan\Reflection\ParametersAcceptorSelector; @@ -150,7 +151,7 @@ public function processNode(Node $node, Scope $scope): array $classTypeResult = $this->ruleLevelHelper->findTypeToCheck( $scope, $class, - sprintf('Call to static method %s() on an unknown class %%s.', $methodName), + sprintf('Call to static method %s() on an unknown class %%s.', SprintfHelper::escapeFormatString($methodName)), static function (Type $type) use ($methodName): bool { return $type->canCallMethods()->yes() && $type->hasMethod($methodName)->yes(); } @@ -256,16 +257,16 @@ static function (Type $type) use ($methodName): bool { ]; } - $lowercasedMethodName = sprintf( + $lowercasedMethodName = SprintfHelper::escapeFormatString(sprintf( '%s %s', $method->isStatic() ? 'static method' : 'method', $method->getDeclaringClass()->getDisplayName() . '::' . $method->getName() . '()' - ); - $displayMethodName = sprintf( + )); + $displayMethodName = SprintfHelper::escapeFormatString(sprintf( '%s %s', $method->isStatic() ? 'Static method' : 'Method', $method->getDeclaringClass()->getDisplayName() . '::' . $method->getName() . '()' - ); + )); $errors = array_merge($errors, $this->check->check( ParametersAcceptorSelector::selectFromArgs( diff --git a/src/Rules/Methods/ExistingClassesInTypehintsRule.php b/src/Rules/Methods/ExistingClassesInTypehintsRule.php index 3ca81d6c3b..7632ef1489 100644 --- a/src/Rules/Methods/ExistingClassesInTypehintsRule.php +++ b/src/Rules/Methods/ExistingClassesInTypehintsRule.php @@ -4,6 +4,7 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\Internal\SprintfHelper; use PHPStan\Node\InClassMethodNode; use PHPStan\Reflection\Php\PhpMethodFromParserNodeReflection; use PHPStan\Rules\FunctionDefinitionCheck; @@ -36,21 +37,24 @@ public function processNode(Node $node, Scope $scope): array throw new \PHPStan\ShouldNotHappenException(); } + $className = SprintfHelper::escapeFormatString($scope->getClassReflection()->getDisplayName()); + $methodName = SprintfHelper::escapeFormatString($methodReflection->getName()); + return $this->check->checkClassMethod( $methodReflection, $node->getOriginalNode(), sprintf( 'Parameter $%%s of method %s::%s() has invalid type %%s.', - $scope->getClassReflection()->getDisplayName(), - $methodReflection->getName() + $className, + $methodName ), sprintf( 'Method %s::%s() has invalid return type %%s.', - $scope->getClassReflection()->getDisplayName(), - $methodReflection->getName() + $className, + $methodName ), - sprintf('Method %s::%s() uses native union types but they\'re supported only on PHP 8.0 and later.', $scope->getClassReflection()->getDisplayName(), $methodReflection->getName()), - sprintf('Template type %%s of method %s::%s() is not referenced in a parameter.', $scope->getClassReflection()->getDisplayName(), $methodReflection->getName()) + sprintf('Method %s::%s() uses native union types but they\'re supported only on PHP 8.0 and later.', $className, $methodName), + sprintf('Template type %%s of method %s::%s() is not referenced in a parameter.', $className, $methodName) ); } diff --git a/src/Rules/PhpDoc/IncompatibleClassConstantPhpDocTypeRule.php b/src/Rules/PhpDoc/IncompatibleClassConstantPhpDocTypeRule.php index ac85f668dd..83e97fa8a3 100644 --- a/src/Rules/PhpDoc/IncompatibleClassConstantPhpDocTypeRule.php +++ b/src/Rules/PhpDoc/IncompatibleClassConstantPhpDocTypeRule.php @@ -4,6 +4,7 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\Internal\SprintfHelper; use PHPStan\Reflection\ClassConstantReflection; use PHPStan\Reflection\ClassReflection; use PHPStan\Rules\Generics\GenericObjectTypeCheck; @@ -102,27 +103,30 @@ private function processSingleConstant(ClassReflection $classReflection, string } } + $className = SprintfHelper::escapeFormatString($constantReflection->getDeclaringClass()->getDisplayName()); + $escapedConstantName = SprintfHelper::escapeFormatString($constantName); + return array_merge($errors, $this->genericObjectTypeCheck->check( $phpDocType, sprintf( 'PHPDoc tag @var for constant %s::%s contains generic type %%s but class %%s is not generic.', - $constantReflection->getDeclaringClass()->getDisplayName(), - $constantName + $className, + $escapedConstantName ), sprintf( 'Generic type %%s in PHPDoc tag @var for constant %s::%s does not specify all template types of class %%s: %%s', - $constantReflection->getDeclaringClass()->getDisplayName(), - $constantName + $className, + $escapedConstantName ), sprintf( 'Generic type %%s in PHPDoc tag @var for constant %s::%s specifies %%d template types, but class %%s supports only %%d: %%s', - $constantReflection->getDeclaringClass()->getDisplayName(), - $constantName + $className, + $escapedConstantName ), sprintf( 'Type %%s in generic type %%s in PHPDoc tag @var for constant %s::%s is not subtype of template type %%s of class %%s.', - $constantReflection->getDeclaringClass()->getDisplayName(), - $constantName + $className, + $escapedConstantName ) )); } diff --git a/src/Rules/PhpDoc/IncompatiblePhpDocTypeRule.php b/src/Rules/PhpDoc/IncompatiblePhpDocTypeRule.php index 6a05e6131b..5da471cdb6 100644 --- a/src/Rules/PhpDoc/IncompatiblePhpDocTypeRule.php +++ b/src/Rules/PhpDoc/IncompatiblePhpDocTypeRule.php @@ -5,6 +5,7 @@ use PhpParser\Node; use PhpParser\Node\Expr\Variable; use PHPStan\Analyser\Scope; +use PHPStan\Internal\SprintfHelper; use PHPStan\Rules\Generics\GenericObjectTypeCheck; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\ArrayType; @@ -96,23 +97,25 @@ public function processNode(Node $node, Scope $scope): array } $isParamSuperType = $nativeParamType->isSuperTypeOf(TemplateTypeHelper::resolveToBounds($phpDocParamType)); + $escapedParameterName = SprintfHelper::escapeFormatString($parameterName); + $errors = array_merge($errors, $this->genericObjectTypeCheck->check( $phpDocParamType, sprintf( 'PHPDoc tag @param for parameter $%s contains generic type %%s but class %%s is not generic.', - $parameterName + $escapedParameterName ), sprintf( 'Generic type %%s in PHPDoc tag @param for parameter $%s does not specify all template types of class %%s: %%s', - $parameterName + $escapedParameterName ), sprintf( 'Generic type %%s in PHPDoc tag @param for parameter $%s specifies %%d template types, but class %%s supports only %%d: %%s', - $parameterName + $escapedParameterName ), sprintf( 'Type %%s in generic type %%s in PHPDoc tag @param for parameter $%s is not subtype of template type %%s of class %%s.', - $parameterName + $escapedParameterName ) )); diff --git a/src/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRule.php b/src/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRule.php index e2494d919f..0f2ab5119d 100644 --- a/src/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRule.php +++ b/src/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRule.php @@ -4,6 +4,7 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\Internal\SprintfHelper; use PHPStan\Node\ClassPropertyNode; use PHPStan\Rules\Generics\GenericObjectTypeCheck; use PHPStan\Rules\Rule; @@ -88,31 +89,34 @@ public function processNode(Node $node, Scope $scope): array ))->build(); } + $className = SprintfHelper::escapeFormatString($propertyReflection->getDeclaringClass()->getDisplayName()); + $escapedPropertyName = SprintfHelper::escapeFormatString($propertyName); + $messages = array_merge($messages, $this->genericObjectTypeCheck->check( $phpDocType, sprintf( '%s for property %s::$%s contains generic type %%s but class %%s is not generic.', $description, - $propertyReflection->getDeclaringClass()->getDisplayName(), - $propertyName + $className, + $escapedPropertyName ), sprintf( 'Generic type %%s in %s for property %s::$%s does not specify all template types of class %%s: %%s', $description, - $propertyReflection->getDeclaringClass()->getDisplayName(), - $propertyName + $className, + $escapedPropertyName ), sprintf( 'Generic type %%s in %s for property %s::$%s specifies %%d template types, but class %%s supports only %%d: %%s', $description, - $propertyReflection->getDeclaringClass()->getDisplayName(), - $propertyName + $className, + $escapedPropertyName ), sprintf( 'Type %%s in generic type %%s in %s for property %s::$%s is not subtype of template type %%s of class %%s.', $description, - $propertyReflection->getDeclaringClass()->getDisplayName(), - $propertyName + $className, + $escapedPropertyName ) )); diff --git a/src/Rules/PhpDoc/InvalidPhpDocVarTagTypeRule.php b/src/Rules/PhpDoc/InvalidPhpDocVarTagTypeRule.php index 9acb9457f1..c5d0c35576 100644 --- a/src/Rules/PhpDoc/InvalidPhpDocVarTagTypeRule.php +++ b/src/Rules/PhpDoc/InvalidPhpDocVarTagTypeRule.php @@ -4,6 +4,7 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\Internal\SprintfHelper; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\ClassCaseSensitivityCheck; use PHPStan\Rules\ClassNameNodePair; @@ -113,12 +114,13 @@ public function processNode(Node $node, Scope $scope): array } } + $escapedIdentifier = SprintfHelper::escapeFormatString($identifier); $errors = array_merge($errors, $this->genericObjectTypeCheck->check( $varTagType, - sprintf('%s contains generic type %%s but class %%s is not generic.', $identifier), - sprintf('Generic type %%s in %s does not specify all template types of class %%s: %%s', $identifier), - sprintf('Generic type %%s in %s specifies %%d template types, but class %%s supports only %%d: %%s', $identifier), - sprintf('Type %%s in generic type %%s in %s is not subtype of template type %%s of class %%s.', $identifier) + sprintf('%s contains generic type %%s but class %%s is not generic.', $escapedIdentifier), + sprintf('Generic type %%s in %s does not specify all template types of class %%s: %%s', $escapedIdentifier), + sprintf('Generic type %%s in %s specifies %%d template types, but class %%s supports only %%d: %%s', $escapedIdentifier), + sprintf('Type %%s in generic type %%s in %s is not subtype of template type %%s of class %%s.', $escapedIdentifier) )); foreach ($this->missingTypehintCheck->getNonGenericObjectTypesWithGenericClass($varTagType) as [$innerName, $genericTypeNames]) { diff --git a/src/Rules/Properties/AccessPropertiesRule.php b/src/Rules/Properties/AccessPropertiesRule.php index 7ab6fe9409..6de912f2fe 100644 --- a/src/Rules/Properties/AccessPropertiesRule.php +++ b/src/Rules/Properties/AccessPropertiesRule.php @@ -5,6 +5,7 @@ use PhpParser\Node\Expr\PropertyFetch; use PhpParser\Node\Identifier; use PHPStan\Analyser\Scope; +use PHPStan\Internal\SprintfHelper; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\RuleError; use PHPStan\Rules\RuleErrorBuilder; @@ -72,7 +73,7 @@ private function processSingleProperty(Scope $scope, PropertyFetch $node, string $typeResult = $this->ruleLevelHelper->findTypeToCheck( $scope, $node->var, - sprintf('Access to property $%s on an unknown class %%s.', $name), + sprintf('Access to property $%s on an unknown class %%s.', SprintfHelper::escapeFormatString($name)), static function (Type $type) use ($name): bool { return $type->canAccessProperties()->yes() && $type->hasProperty($name)->yes(); } diff --git a/src/Rules/Properties/AccessStaticPropertiesRule.php b/src/Rules/Properties/AccessStaticPropertiesRule.php index 4233156fdf..b29beb813b 100644 --- a/src/Rules/Properties/AccessStaticPropertiesRule.php +++ b/src/Rules/Properties/AccessStaticPropertiesRule.php @@ -6,6 +6,7 @@ use PhpParser\Node\Expr\StaticPropertyFetch; use PhpParser\Node\Name; use PHPStan\Analyser\Scope; +use PHPStan\Internal\SprintfHelper; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\ClassCaseSensitivityCheck; use PHPStan\Rules\ClassNameNodePair; @@ -146,7 +147,7 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node, $classTypeResult = $this->ruleLevelHelper->findTypeToCheck( $scope, $node->class, - sprintf('Access to static property $%s on an unknown class %%s.', $name), + sprintf('Access to static property $%s on an unknown class %%s.', SprintfHelper::escapeFormatString($name)), static function (Type $type) use ($name): bool { return $type->canAccessProperties()->yes() && $type->hasProperty($name)->yes(); } diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index 9c042af244..30277d2480 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -303,4 +303,15 @@ public function testBug4842(): void $this->analyse([__DIR__ . '/data/bug-4842.php'], []); } + public function testBug5669(): void + { + $this->analyse([__DIR__ . '/data/bug-5669.php'], [ + [ + 'Access to offset \'%customer…\' on an unknown class Bug5669\arr.', + 26, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Arrays/data/bug-5669.php b/tests/PHPStan/Rules/Arrays/data/bug-5669.php new file mode 100644 index 0000000000..a280cce420 --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/bug-5669.php @@ -0,0 +1,30 @@ + + */ + public function getReplacer() + { + return []; + } + +} + +class c extends a +{ + + public function getReplacer() + { + $replacer = parent::getReplacer(); + $replacer['%customer_salutation%'] = 'test'; + + return $replacer; + } +} From 43c192b23f4159beb122d07ec8b4e2d84628236e Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 22 Sep 2021 14:48:39 +0200 Subject: [PATCH 0358/1284] more precise `SpecifiedTypes` phpdocs --- src/Analyser/MutatingScope.php | 2 ++ src/Analyser/SpecifiedTypes.php | 14 ++++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 39c8eac68a..1ddb8c26a4 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -3869,6 +3869,7 @@ public function filterBySpecifiedTypes(SpecifiedTypes $specifiedTypes): self } usort($typeSpecifications, static function (array $a, array $b): int { + // @phpstan-ignore-next-line $length = strlen((string) $a['exprString']) - strlen((string) $b['exprString']); if ($length !== 0) { return $length; @@ -3900,6 +3901,7 @@ public function filterBySpecifiedTypes(SpecifiedTypes $specifiedTypes): self || !is_string($expr->name) || $specifiedTypes->shouldOverwrite() ) { + // @phpstan-ignore-next-line $match = \Nette\Utils\Strings::match((string) $typeSpecification['exprString'], '#^\$([a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*)#'); if ($match !== null) { $skipVariables[$match[1]] = true; diff --git a/src/Analyser/SpecifiedTypes.php b/src/Analyser/SpecifiedTypes.php index ec3005c8c1..73e4ff2559 100644 --- a/src/Analyser/SpecifiedTypes.php +++ b/src/Analyser/SpecifiedTypes.php @@ -2,15 +2,17 @@ namespace PHPStan\Analyser; +use PhpParser\Node\Expr; +use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; class SpecifiedTypes { - /** @var mixed[] */ + /** @var array */ private array $sureTypes; - /** @var mixed[] */ + /** @var array */ private array $sureNotTypes; private bool $overwrite; @@ -20,8 +22,8 @@ class SpecifiedTypes /** * @api - * @param mixed[] $sureTypes - * @param mixed[] $sureNotTypes + * @param array $sureTypes + * @param array $sureNotTypes * @param bool $overwrite * @param array $newConditionalExpressionHolders */ @@ -40,7 +42,7 @@ public function __construct( /** * @api - * @return mixed[] + * @return array */ public function getSureTypes(): array { @@ -49,7 +51,7 @@ public function getSureTypes(): array /** * @api - * @return mixed[] + * @return array */ public function getSureNotTypes(): array { From ac7a60a92078517e27aaaea0989d47c87412e933 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 22 Sep 2021 15:01:44 +0200 Subject: [PATCH 0359/1284] While loop's condition with a return is not reported as always true --- src/Analyser/NodeScopeResolver.php | 5 ++--- tests/PHPStan/Rules/Comparison/data/while-loop-true.php | 9 +++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 8c6dc0db93..6f96a4cf18 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -900,13 +900,12 @@ private function processStmtNode( $isIterableAtLeastOnce = $beforeCondBooleanType instanceof ConstantBooleanType && $beforeCondBooleanType->getValue(); $alwaysIterates = $condBooleanType instanceof ConstantBooleanType && $condBooleanType->getValue(); $neverIterates = $condBooleanType instanceof ConstantBooleanType && !$condBooleanType->getValue(); - $breakCount = count($finalScopeResult->getExitPointsByType(Break_::class)); - if ($breakCount === 0) { + if (count($finalScopeResult->getExitPoints()) === 0) { $nodeCallback(new BreaklessWhileLoopNode($stmt), $bodyScopeMaybeRan); } if ($alwaysIterates) { - $isAlwaysTerminating = $breakCount === 0; + $isAlwaysTerminating = count($finalScopeResult->getExitPointsByType(Break_::class)) === 0; } elseif ($isIterableAtLeastOnce) { $isAlwaysTerminating = $finalScopeResult->isAlwaysTerminating(); } else { diff --git a/tests/PHPStan/Rules/Comparison/data/while-loop-true.php b/tests/PHPStan/Rules/Comparison/data/while-loop-true.php index 717e753e9d..e6bb1624b2 100644 --- a/tests/PHPStan/Rules/Comparison/data/while-loop-true.php +++ b/tests/PHPStan/Rules/Comparison/data/while-loop-true.php @@ -51,4 +51,13 @@ public function doBar4(): void } } + public function doBar5(): void + { + while (true) { + if (rand(0, 1)) { + return; + } + } + } + } From be79bcef9a3a2dd1c2e81a7472f886158e9fd959 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 22 Sep 2021 15:20:47 +0200 Subject: [PATCH 0360/1284] Level 5 - ImplodeFunctionRule --- conf/config.level5.neon | 2 + src/Rules/Functions/ImplodeFunctionRule.php | 82 +++++++++++++++++++ tests/PHPStan/Levels/data/acceptTypes-5.json | 5 ++ tests/PHPStan/Levels/data/acceptTypes-6.json | 15 ++++ tests/PHPStan/Levels/data/acceptTypes-7.json | 5 ++ tests/PHPStan/Levels/data/acceptTypes.php | 16 ++++ .../Functions/ImplodeFunctionRuleTest.php | 49 +++++++++++ .../PHPStan/Rules/Functions/data/implode.php | 25 ++++++ 8 files changed, 199 insertions(+) create mode 100644 src/Rules/Functions/ImplodeFunctionRule.php create mode 100644 tests/PHPStan/Rules/Functions/ImplodeFunctionRuleTest.php create mode 100644 tests/PHPStan/Rules/Functions/data/implode.php diff --git a/conf/config.level5.neon b/conf/config.level5.neon index 57dc11d4a0..cde1e6e556 100644 --- a/conf/config.level5.neon +++ b/conf/config.level5.neon @@ -11,6 +11,8 @@ parameters: checkFunctionArgumentTypes: true checkArgumentsPassedByReference: true +rules: + - PHPStan\Rules\Functions\ImplodeFunctionRule services: - diff --git a/src/Rules/Functions/ImplodeFunctionRule.php b/src/Rules/Functions/ImplodeFunctionRule.php new file mode 100644 index 0000000000..0bcbb6ad73 --- /dev/null +++ b/src/Rules/Functions/ImplodeFunctionRule.php @@ -0,0 +1,82 @@ + + */ +class ImplodeFunctionRule implements \PHPStan\Rules\Rule +{ + + private RuleLevelHelper $ruleLevelHelper; + + private ReflectionProvider $reflectionProvider; + + public function __construct( + ReflectionProvider $reflectionProvider, + RuleLevelHelper $ruleLevelHelper + ) + { + $this->reflectionProvider = $reflectionProvider; + $this->ruleLevelHelper = $ruleLevelHelper; + } + + public function getNodeType(): string + { + return FuncCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!($node->name instanceof \PhpParser\Node\Name)) { + return []; + } + + $functionName = $this->reflectionProvider->resolveFunctionName($node->name, $scope); + if (!in_array($functionName, ['implode', 'join'], true)) { + return []; + } + + $args = $node->getArgs(); + if (count($args) === 1) { + $arrayArg = $args[0]->value; + $paramNo = 1; + } elseif (count($args) === 2) { + $arrayArg = $args[1]->value; + $paramNo = 2; + } else { + return []; + } + + $typeResult = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $arrayArg, + '', + static function (Type $type): bool { + return !$type->getIterableValueType()->toString() instanceof ErrorType; + } + ); + + if ($typeResult->getType() instanceof ErrorType + || !$typeResult->getType()->getIterableValueType()->toString() instanceof ErrorType) { + return []; + } + + return [ + RuleErrorBuilder::message( + sprintf('Parameter #%d $array of function %s expects array, %s given.', $paramNo, $functionName, $typeResult->getType()->getIterableValueType()->describe(VerbosityLevel::typeOnly())) + )->build(), + ]; + } + +} diff --git a/tests/PHPStan/Levels/data/acceptTypes-5.json b/tests/PHPStan/Levels/data/acceptTypes-5.json index 5e4de19fb5..78f19787d7 100644 --- a/tests/PHPStan/Levels/data/acceptTypes-5.json +++ b/tests/PHPStan/Levels/data/acceptTypes-5.json @@ -198,5 +198,10 @@ "message": "Parameter #1 $nonEmpty of method Levels\\AcceptTypes\\AcceptNonEmpty::doBar() expects array&nonEmpty, array given.", "line": 735, "ignorable": true + }, + { + "message": "Parameter #2 $pieces of function implode expects array, int given.", + "line": 763, + "ignorable": true } ] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/acceptTypes-6.json b/tests/PHPStan/Levels/data/acceptTypes-6.json index e4fa1ffa2c..35bd142bb9 100644 --- a/tests/PHPStan/Levels/data/acceptTypes-6.json +++ b/tests/PHPStan/Levels/data/acceptTypes-6.json @@ -148,5 +148,20 @@ "message": "Method Levels\\AcceptTypes\\ArrayShapes::doBar() has no return type specified.", "line": 603, "ignorable": true + }, + { + "message": "Method Levels\\AcceptTypes\\Implode::partlySupportedUnion() has no return type specified.", + "line": 755, + "ignorable": true + }, + { + "message": "Method Levels\\AcceptTypes\\Implode::partlySupportedUnion() has parameter $union with no value type specified in iterable type array.", + "line": 755, + "ignorable": true + }, + { + "message": "Method Levels\\AcceptTypes\\Implode::invalidType() has no return type specified.", + "line": 762, + "ignorable": true } ] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/acceptTypes-7.json b/tests/PHPStan/Levels/data/acceptTypes-7.json index 76c6e1399e..4dd170abf9 100644 --- a/tests/PHPStan/Levels/data/acceptTypes-7.json +++ b/tests/PHPStan/Levels/data/acceptTypes-7.json @@ -153,5 +153,10 @@ "message": "Parameter #1 $min (int<-1, 1>) of function random_int expects lower number than parameter #2 $max (int<-1, 1>).", "line": 692, "ignorable": true + }, + { + "message": "Parameter #2 $pieces of function implode expects array, array|int|string given.", + "line": 756, + "ignorable": true } ] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/acceptTypes.php b/tests/PHPStan/Levels/data/acceptTypes.php index bc99693645..d16184f062 100644 --- a/tests/PHPStan/Levels/data/acceptTypes.php +++ b/tests/PHPStan/Levels/data/acceptTypes.php @@ -747,3 +747,19 @@ public function doBar( } } + +class Implode { + /** + * @param string|int|array $union + */ + public function partlySupportedUnion($union) { + $imploded = implode('abc', $union); + } + + /** + * @param int $invalid + */ + public function invalidType($invalid) { + $imploded = implode('abc', $invalid); + } +} diff --git a/tests/PHPStan/Rules/Functions/ImplodeFunctionRuleTest.php b/tests/PHPStan/Rules/Functions/ImplodeFunctionRuleTest.php new file mode 100644 index 0000000000..9baec6e5e6 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/ImplodeFunctionRuleTest.php @@ -0,0 +1,49 @@ + + */ +class ImplodeFunctionRuleTest extends \PHPStan\Testing\RuleTestCase +{ + + protected function getRule(): \PHPStan\Rules\Rule + { + $broker = $this->createReflectionProvider(); + return new ImplodeFunctionRule($broker, new RuleLevelHelper($broker, true, false, true, false)); + } + + public function testFile(): void + { + $this->analyse([__DIR__ . '/data/implode.php'], [ + [ + 'Parameter #2 $array of function implode expects array, array|string given.', + 9, + ], + [ + 'Parameter #1 $array of function implode expects array, array given.', + 11, + ], + [ + 'Parameter #1 $array of function implode expects array, array given.', + 12, + ], + [ + 'Parameter #1 $array of function implode expects array, array given.', + 13, + ], + [ + 'Parameter #2 $array of function implode expects array, array given.', + 15, + ], + [ + 'Parameter #2 $array of function join expects array, array given.', + 16, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Functions/data/implode.php b/tests/PHPStan/Rules/Functions/data/implode.php new file mode 100644 index 0000000000..d034500d28 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/implode.php @@ -0,0 +1,25 @@ + Date: Wed, 22 Sep 2021 15:49:07 +0200 Subject: [PATCH 0361/1284] Resolve `PHP_INT_MAX` as positive integer --- src/Analyser/MutatingScope.php | 3 +++ tests/PHPStan/Analyser/AnalyserIntegrationTest.php | 6 ++++++ tests/PHPStan/Analyser/data/bug-5657.php | 13 +++++++++++++ 3 files changed, 22 insertions(+) create mode 100644 tests/PHPStan/Analyser/data/bug-5657.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 1ddb8c26a4..feaa721167 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -1978,6 +1978,9 @@ private function resolveType(Expr $node): Type if ($resolvedConstantName === '__COMPILER_HALT_OFFSET__') { return new IntegerType(); } + if ($resolvedConstantName === 'PHP_INT_MAX') { + return IntegerRangeType::fromInterval(1, null); + } $constantType = $this->reflectionProvider->getConstant($node->name, $this)->getValueType(); diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 1a7bebb039..4e522467da 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -447,6 +447,12 @@ public function testBug5639(): void $this->assertCount(0, $errors); } + public function testBug5657(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-5657.php'); + $this->assertCount(0, $errors); + } + /** * @param string $file * @return \PHPStan\Analyser\Error[] diff --git a/tests/PHPStan/Analyser/data/bug-5657.php b/tests/PHPStan/Analyser/data/bug-5657.php new file mode 100644 index 0000000000..7cb6fa91f0 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-5657.php @@ -0,0 +1,13 @@ + Date: Wed, 22 Sep 2021 15:35:07 +0200 Subject: [PATCH 0362/1284] Do-while loop - constant condition rule --- conf/config.level4.neon | 7 ++ src/Analyser/NodeScopeResolver.php | 3 + src/Analyser/StatementResult.php | 1 + src/Node/DoWhileLoopConditionNode.php | 53 ++++++++++ .../DoWhileLoopConstantConditionRule.php | 93 ++++++++++++++++++ .../DoWhileLoopConstantConditionRuleTest.php | 65 ++++++++++++ .../Rules/Comparison/data/do-while-loop.php | 98 +++++++++++++++++++ 7 files changed, 320 insertions(+) create mode 100644 src/Node/DoWhileLoopConditionNode.php create mode 100644 src/Rules/Comparison/DoWhileLoopConstantConditionRule.php create mode 100644 tests/PHPStan/Rules/Comparison/DoWhileLoopConstantConditionRuleTest.php create mode 100644 tests/PHPStan/Rules/Comparison/data/do-while-loop.php diff --git a/conf/config.level4.neon b/conf/config.level4.neon index 43fe1bddf6..d14c723e71 100644 --- a/conf/config.level4.neon +++ b/conf/config.level4.neon @@ -64,6 +64,13 @@ services: tags: - phpstan.rules.rule + - + class: PHPStan\Rules\Comparison\DoWhileLoopConstantConditionRule + arguments: + treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% + tags: + - phpstan.rules.rule + - class: PHPStan\Rules\Comparison\ElseIfConstantConditionRule arguments: diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 6f96a4cf18..8471ca54b4 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -64,6 +64,7 @@ use PHPStan\Node\ClassPropertyNode; use PHPStan\Node\ClassStatementsGatherer; use PHPStan\Node\ClosureReturnStatementsNode; +use PHPStan\Node\DoWhileLoopConditionNode; use PHPStan\Node\ExecutionEndNode; use PHPStan\Node\FinallyExitPointsNode; use PHPStan\Node\FunctionReturnStatementsNode; @@ -973,6 +974,8 @@ private function processStmtNode( $condBooleanType = $bodyScope->getType($stmt->cond)->toBoolean(); $alwaysIterates = $condBooleanType instanceof ConstantBooleanType && $condBooleanType->getValue(); + $nodeCallback(new DoWhileLoopConditionNode($stmt->cond, $bodyScopeResult->getExitPoints()), $bodyScope); + if ($alwaysIterates) { $alwaysTerminating = count($bodyScopeResult->getExitPointsByType(Break_::class)) === 0; } else { diff --git a/src/Analyser/StatementResult.php b/src/Analyser/StatementResult.php index ed4a85465f..9636198738 100644 --- a/src/Analyser/StatementResult.php +++ b/src/Analyser/StatementResult.php @@ -136,6 +136,7 @@ public function getExitPointsForOuterLoop(): array foreach ($this->exitPoints as $exitPoint) { $statement = $exitPoint->getStatement(); if (!$statement instanceof Stmt\Continue_ && !$statement instanceof Stmt\Break_) { + $exitPoints[] = $exitPoint; continue; } if ($statement->num === null) { diff --git a/src/Node/DoWhileLoopConditionNode.php b/src/Node/DoWhileLoopConditionNode.php new file mode 100644 index 0000000000..6fdc87cfb0 --- /dev/null +++ b/src/Node/DoWhileLoopConditionNode.php @@ -0,0 +1,53 @@ +getAttributes()); + $this->cond = $cond; + $this->exitPoints = $exitPoints; + } + + public function getCond(): Expr + { + return $this->cond; + } + + /** + * @return StatementExitPoint[] + */ + public function getExitPoints(): array + { + return $this->exitPoints; + } + + public function getType(): string + { + return 'PHPStan_Node_ClosureReturnStatementsNode'; + } + + /** + * @return string[] + */ + public function getSubNodeNames(): array + { + return []; + } + +} diff --git a/src/Rules/Comparison/DoWhileLoopConstantConditionRule.php b/src/Rules/Comparison/DoWhileLoopConstantConditionRule.php new file mode 100644 index 0000000000..2b24587113 --- /dev/null +++ b/src/Rules/Comparison/DoWhileLoopConstantConditionRule.php @@ -0,0 +1,93 @@ + + */ +class DoWhileLoopConstantConditionRule implements Rule +{ + + private ConstantConditionRuleHelper $helper; + + private bool $treatPhpDocTypesAsCertain; + + public function __construct( + ConstantConditionRuleHelper $helper, + bool $treatPhpDocTypesAsCertain + ) + { + $this->helper = $helper; + $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; + } + + public function getNodeType(): string + { + return DoWhileLoopConditionNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $exprType = $this->helper->getBooleanType($scope, $node->getCond()); + if ($exprType instanceof ConstantBooleanType) { + if ($exprType->getValue()) { + foreach ($node->getExitPoints() as $exitPoint) { + $statement = $exitPoint->getStatement(); + if ($statement instanceof Break_) { + return []; + } + if (!$statement instanceof Continue_) { + return []; + } + if ($statement->num === null) { + continue; + } + if (!$statement->num instanceof LNumber) { + continue; + } + $value = $statement->num->value; + if ($value === 1) { + continue; + } + + if ($value > 1) { + return []; + } + } + } + + $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node): RuleErrorBuilder { + if (!$this->treatPhpDocTypesAsCertain) { + return $ruleErrorBuilder; + } + + $booleanNativeType = $this->helper->getNativeBooleanType($scope, $node->getCond()); + if ($booleanNativeType instanceof ConstantBooleanType) { + return $ruleErrorBuilder; + } + + return $ruleErrorBuilder->tip('Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'); + }; + + return [ + $addTip(RuleErrorBuilder::message(sprintf( + 'Do-while loop condition is always %s.', + $exprType->getValue() ? 'true' : 'false' + )))->line($node->getCond()->getLine())->build(), + ]; + } + + return []; + } + +} diff --git a/tests/PHPStan/Rules/Comparison/DoWhileLoopConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/DoWhileLoopConstantConditionRuleTest.php new file mode 100644 index 0000000000..c064156ea5 --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/DoWhileLoopConstantConditionRuleTest.php @@ -0,0 +1,65 @@ + + */ +class DoWhileLoopConstantConditionRuleTest extends \PHPStan\Testing\RuleTestCase +{ + + /** @var bool */ + private $treatPhpDocTypesAsCertain = true; + + protected function getRule(): \PHPStan\Rules\Rule + { + return new DoWhileLoopConstantConditionRule( + new ConstantConditionRuleHelper( + new ImpossibleCheckTypeHelper( + $this->createReflectionProvider(), + $this->getTypeSpecifier(), + [], + $this->treatPhpDocTypesAsCertain + ), + $this->treatPhpDocTypesAsCertain + ), + $this->treatPhpDocTypesAsCertain + ); + } + + protected function shouldTreatPhpDocTypesAsCertain(): bool + { + return $this->treatPhpDocTypesAsCertain; + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/do-while-loop.php'], [ + [ + 'Do-while loop condition is always true.', + 12, + ], + [ + 'Do-while loop condition is always false.', + 37, + ], + [ + 'Do-while loop condition is always false.', + 46, + ], + [ + 'Do-while loop condition is always false.', + 55, + ], + [ + 'Do-while loop condition is always true.', + 64, + ], + [ + 'Do-while loop condition is always false.', + 73, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Comparison/data/do-while-loop.php b/tests/PHPStan/Rules/Comparison/data/do-while-loop.php new file mode 100644 index 0000000000..02eb303d34 --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/do-while-loop.php @@ -0,0 +1,98 @@ + Date: Wed, 22 Sep 2021 15:47:15 +0200 Subject: [PATCH 0363/1284] Fix while-loop --- src/Analyser/NodeScopeResolver.php | 4 +-- src/Node/BreaklessWhileLoopNode.php | 18 ++++++++++- .../WhileLoopAlwaysTrueConditionRule.php | 26 ++++++++++++++++ .../WhileLoopAlwaysTrueConditionRuleTest.php | 4 +++ .../Rules/Comparison/data/while-loop-true.php | 31 +++++++++++++++++++ 5 files changed, 79 insertions(+), 4 deletions(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 8471ca54b4..07fbc7590d 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -901,9 +901,7 @@ private function processStmtNode( $isIterableAtLeastOnce = $beforeCondBooleanType instanceof ConstantBooleanType && $beforeCondBooleanType->getValue(); $alwaysIterates = $condBooleanType instanceof ConstantBooleanType && $condBooleanType->getValue(); $neverIterates = $condBooleanType instanceof ConstantBooleanType && !$condBooleanType->getValue(); - if (count($finalScopeResult->getExitPoints()) === 0) { - $nodeCallback(new BreaklessWhileLoopNode($stmt), $bodyScopeMaybeRan); - } + $nodeCallback(new BreaklessWhileLoopNode($stmt, $finalScopeResult->getExitPoints()), $bodyScopeMaybeRan); if ($alwaysIterates) { $isAlwaysTerminating = count($finalScopeResult->getExitPointsByType(Break_::class)) === 0; diff --git a/src/Node/BreaklessWhileLoopNode.php b/src/Node/BreaklessWhileLoopNode.php index f662a139fe..9ba48fdf8b 100644 --- a/src/Node/BreaklessWhileLoopNode.php +++ b/src/Node/BreaklessWhileLoopNode.php @@ -4,6 +4,7 @@ use PhpParser\Node\Stmt\While_; use PhpParser\NodeAbstract; +use PHPStan\Analyser\StatementExitPoint; /** @api */ class BreaklessWhileLoopNode extends NodeAbstract implements VirtualNode @@ -11,10 +12,17 @@ class BreaklessWhileLoopNode extends NodeAbstract implements VirtualNode private While_ $originalNode; - public function __construct(While_ $originalNode) + /** @var StatementExitPoint[] */ + private array $exitPoints; + + /** + * @param StatementExitPoint[] $exitPoints + */ + public function __construct(While_ $originalNode, array $exitPoints) { parent::__construct($originalNode->getAttributes()); $this->originalNode = $originalNode; + $this->exitPoints = $exitPoints; } public function getOriginalNode(): While_ @@ -22,6 +30,14 @@ public function getOriginalNode(): While_ return $this->originalNode; } + /** + * @return StatementExitPoint[] + */ + public function getExitPoints(): array + { + return $this->exitPoints; + } + public function getType(): string { return 'PHPStan_Node_BreaklessWhileLoop'; diff --git a/src/Rules/Comparison/WhileLoopAlwaysTrueConditionRule.php b/src/Rules/Comparison/WhileLoopAlwaysTrueConditionRule.php index 15d696408b..ebb5325d31 100644 --- a/src/Rules/Comparison/WhileLoopAlwaysTrueConditionRule.php +++ b/src/Rules/Comparison/WhileLoopAlwaysTrueConditionRule.php @@ -2,6 +2,9 @@ namespace PHPStan\Rules\Comparison; +use PhpParser\Node\Scalar\LNumber; +use PhpParser\Node\Stmt\Break_; +use PhpParser\Node\Stmt\Continue_; use PHPStan\Node\BreaklessWhileLoopNode; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\Constant\ConstantBooleanType; @@ -35,6 +38,29 @@ public function processNode( \PHPStan\Analyser\Scope $scope ): array { + foreach ($node->getExitPoints() as $exitPoint) { + $statement = $exitPoint->getStatement(); + if ($statement instanceof Break_) { + return []; + } + if (!$statement instanceof Continue_) { + return []; + } + if ($statement->num === null) { + continue; + } + if (!$statement->num instanceof LNumber) { + continue; + } + $value = $statement->num->value; + if ($value === 1) { + continue; + } + + if ($value > 1) { + return []; + } + } $originalNode = $node->getOriginalNode(); $exprType = $this->helper->getBooleanType($scope, $originalNode->cond); if ($exprType instanceof ConstantBooleanType && $exprType->getValue()) { diff --git a/tests/PHPStan/Rules/Comparison/WhileLoopAlwaysTrueConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/WhileLoopAlwaysTrueConditionRuleTest.php index f827c11f93..e077e19f48 100644 --- a/tests/PHPStan/Rules/Comparison/WhileLoopAlwaysTrueConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/WhileLoopAlwaysTrueConditionRuleTest.php @@ -44,6 +44,10 @@ public function testRule(): void 20, 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', ], + [ + 'While loop condition is always true.', + 65, + ], ]); } diff --git a/tests/PHPStan/Rules/Comparison/data/while-loop-true.php b/tests/PHPStan/Rules/Comparison/data/while-loop-true.php index e6bb1624b2..193ba74fa8 100644 --- a/tests/PHPStan/Rules/Comparison/data/while-loop-true.php +++ b/tests/PHPStan/Rules/Comparison/data/while-loop-true.php @@ -60,4 +60,35 @@ public function doBar5(): void } } + public function doBar6(): void + { + while (true) { + if (rand(0, 1)) { + continue; + } + } + } + + public function doBar7(array $a): void + { + foreach ($a as $v) { + while (true) { + if (rand(0, 1)) { + continue 2; + } + } + } + } + + public function doBar8(array $a): void + { + foreach ($a as $v) { + while (true) { + if (rand(0, 1)) { + break 2; + } + } + } + } + } From ce28254fb97e94170c67f7b6f5fbadd5a2f8d1f6 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 22 Sep 2021 18:03:18 +0200 Subject: [PATCH 0364/1284] ImplodeFunctionRule - fix error message --- src/Rules/Functions/ImplodeFunctionRule.php | 2 +- .../Rules/Functions/ImplodeFunctionRuleTest.php | 12 ++++++------ tests/PHPStan/Rules/Functions/data/implode.php | 10 ++++++++++ 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/Rules/Functions/ImplodeFunctionRule.php b/src/Rules/Functions/ImplodeFunctionRule.php index 0bcbb6ad73..0bd596d75a 100644 --- a/src/Rules/Functions/ImplodeFunctionRule.php +++ b/src/Rules/Functions/ImplodeFunctionRule.php @@ -74,7 +74,7 @@ static function (Type $type): bool { return [ RuleErrorBuilder::message( - sprintf('Parameter #%d $array of function %s expects array, %s given.', $paramNo, $functionName, $typeResult->getType()->getIterableValueType()->describe(VerbosityLevel::typeOnly())) + sprintf('Parameter #%d $array of function %s expects array, %s given.', $paramNo, $functionName, $typeResult->getType()->describe(VerbosityLevel::typeOnly())) )->build(), ]; } diff --git a/tests/PHPStan/Rules/Functions/ImplodeFunctionRuleTest.php b/tests/PHPStan/Rules/Functions/ImplodeFunctionRuleTest.php index 9baec6e5e6..a06fbb4fc3 100644 --- a/tests/PHPStan/Rules/Functions/ImplodeFunctionRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ImplodeFunctionRuleTest.php @@ -20,27 +20,27 @@ public function testFile(): void { $this->analyse([__DIR__ . '/data/implode.php'], [ [ - 'Parameter #2 $array of function implode expects array, array|string given.', + 'Parameter #2 $array of function implode expects array, array|string> given.', 9, ], [ - 'Parameter #1 $array of function implode expects array, array given.', + 'Parameter #1 $array of function implode expects array, array> given.', 11, ], [ - 'Parameter #1 $array of function implode expects array, array given.', + 'Parameter #1 $array of function implode expects array, array> given.', 12, ], [ - 'Parameter #1 $array of function implode expects array, array given.', + 'Parameter #1 $array of function implode expects array, array> given.', 13, ], [ - 'Parameter #2 $array of function implode expects array, array given.', + 'Parameter #2 $array of function implode expects array, array> given.', 15, ], [ - 'Parameter #2 $array of function join expects array, array given.', + 'Parameter #2 $array of function join expects array, array> given.', 16, ], ]); diff --git a/tests/PHPStan/Rules/Functions/data/implode.php b/tests/PHPStan/Rules/Functions/data/implode.php index d034500d28..fae187e6bc 100644 --- a/tests/PHPStan/Rules/Functions/data/implode.php +++ b/tests/PHPStan/Rules/Functions/data/implode.php @@ -22,4 +22,14 @@ public function valid() { implode('', ['12', '345']); join('', ['12', '345']); } + + public function doFoo() + { + $parameters = [ + array_map( + fn ($value) => $this->getFactory()->getParameterType()->toString($value), + $this->values, + ), + ]; + } } From 0714d925e04f03b2d14b17b06d748d9f7c053560 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 22 Sep 2021 18:27:17 +0200 Subject: [PATCH 0365/1284] Fix --- tests/PHPStan/Rules/Functions/data/implode.php | 9 --------- 1 file changed, 9 deletions(-) diff --git a/tests/PHPStan/Rules/Functions/data/implode.php b/tests/PHPStan/Rules/Functions/data/implode.php index fae187e6bc..800a61397d 100644 --- a/tests/PHPStan/Rules/Functions/data/implode.php +++ b/tests/PHPStan/Rules/Functions/data/implode.php @@ -23,13 +23,4 @@ public function valid() { join('', ['12', '345']); } - public function doFoo() - { - $parameters = [ - array_map( - fn ($value) => $this->getFactory()->getParameterType()->toString($value), - $this->values, - ), - ]; - } } From adb4603fd42fa481ebf135d06e00b05eadb05d6a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 22 Sep 2021 18:09:19 +0200 Subject: [PATCH 0366/1284] File whitespace rule --- conf/bleedingEdge.neon | 1 - conf/config.level0.neon | 6 +----- conf/config.neon | 2 -- 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 8171700845..9cddf7bcd0 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -2,7 +2,6 @@ parameters: featureToggles: bleedingEdge: true randomIntParameters: true - fileWhitespace: true readComposerPhpVersion: true dateTimeInstantiation: true checkLogicalAndConstantCondition: true diff --git a/conf/config.level0.neon b/conf/config.level0.neon index 696ee23a62..db52e93ac7 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -20,8 +20,6 @@ conditionalTags: phpstan.rules.rule: %featureToggles.apiRules% PHPStan\Rules\Constants\OverridingConstantRule: phpstan.rules.rule: %featureToggles.classConstants% - PHPStan\Rules\Whitespace\FileWhitespaceRule: - phpstan.rules.rule: %featureToggles.fileWhitespace% PHPStan\Rules\Properties\UninitializedPropertyRule: phpstan.rules.rule: %checkUninitializedProperties% PHPStan\Rules\Properties\OverridingPropertyRule: @@ -69,6 +67,7 @@ rules: - PHPStan\Rules\Properties\PropertyAttributesRule - PHPStan\Rules\Properties\ReadOnlyPropertyRule - PHPStan\Rules\Variables\UnsetRule + - PHPStan\Rules\Whitespace\FileWhitespaceRule services: - @@ -245,9 +244,6 @@ services: tags: - phpstan.rules.rule - - - class: PHPStan\Rules\Whitespace\FileWhitespaceRule - - class: PHPStan\Rules\Classes\LocalTypeAliasesRule arguments: diff --git a/conf/config.neon b/conf/config.neon index c2aecae121..1a9e63c51d 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -22,7 +22,6 @@ parameters: bleedingEdge: false disableRuntimeReflectionProvider: false randomIntParameters: false - fileWhitespace: false readComposerPhpVersion: false dateTimeInstantiation: false checkLogicalAndConstantCondition: false @@ -194,7 +193,6 @@ parametersSchema: bleedingEdge: bool(), disableRuntimeReflectionProvider: bool(), randomIntParameters: bool(), - fileWhitespace: bool(), readComposerPhpVersion: bool(), dateTimeInstantiation: bool(), checkLogicalAndConstantCondition: bool(), From 73edae3a03c4cc98b98d4eaaeeae5e5fa0547139 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 22 Sep 2021 18:10:43 +0200 Subject: [PATCH 0367/1284] Read PHP version from config.platform.php in composer.json --- build/ignore-by-php-version.neon.php | 2 ++ build/phpstan.neon | 1 - conf/bleedingEdge.neon | 1 - conf/config.neon | 3 --- src/Php/PhpVersionFactoryFactory.php | 7 +------ 5 files changed, 3 insertions(+), 11 deletions(-) diff --git a/build/ignore-by-php-version.neon.php b/build/ignore-by-php-version.neon.php index 44c106ee82..bd061ad3a1 100644 --- a/build/ignore-by-php-version.neon.php +++ b/build/ignore-by-php-version.neon.php @@ -18,4 +18,6 @@ $config = array_merge_recursive($config, $adapter->load(__DIR__ . '/ignore-gte-php7.4-errors.neon')); } +$config['parameters']['phpVersion'] = PHP_VERSION_ID; + return $config; diff --git a/build/phpstan.neon b/build/phpstan.neon index a42918c5dc..5795712326 100644 --- a/build/phpstan.neon +++ b/build/phpstan.neon @@ -66,7 +66,6 @@ parameters: missingCheckedExceptionInThrows: true tooWideThrowType: true featureToggles: - readComposerPhpVersion: false apiRules: false ignoreErrors: - '#^Dynamic call to static method PHPUnit\\Framework\\\S+\(\)\.$#' diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 9cddf7bcd0..42906356ff 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -2,7 +2,6 @@ parameters: featureToggles: bleedingEdge: true randomIntParameters: true - readComposerPhpVersion: true dateTimeInstantiation: true checkLogicalAndConstantCondition: true checkLogicalOrConstantCondition: true diff --git a/conf/config.neon b/conf/config.neon index 1a9e63c51d..68e9261384 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -22,7 +22,6 @@ parameters: bleedingEdge: false disableRuntimeReflectionProvider: false randomIntParameters: false - readComposerPhpVersion: false dateTimeInstantiation: false checkLogicalAndConstantCondition: false checkLogicalOrConstantCondition: false @@ -193,7 +192,6 @@ parametersSchema: bleedingEdge: bool(), disableRuntimeReflectionProvider: bool(), randomIntParameters: bool(), - readComposerPhpVersion: bool(), dateTimeInstantiation: bool(), checkLogicalAndConstantCondition: bool(), checkLogicalOrConstantCondition: bool(), @@ -370,7 +368,6 @@ services: class: PHPStan\Php\PhpVersionFactoryFactory arguments: versionId: %phpVersion% - readComposerPhpVersion: %featureToggles.readComposerPhpVersion% composerAutoloaderProjectPaths: %composerAutoloaderProjectPaths% - diff --git a/src/Php/PhpVersionFactoryFactory.php b/src/Php/PhpVersionFactoryFactory.php index 92fd1aa675..f301adb29b 100644 --- a/src/Php/PhpVersionFactoryFactory.php +++ b/src/Php/PhpVersionFactoryFactory.php @@ -10,30 +10,25 @@ class PhpVersionFactoryFactory private ?int $versionId; - private bool $readComposerPhpVersion; - /** @var string[] */ private array $composerAutoloaderProjectPaths; /** - * @param bool $readComposerPhpVersion * @param string[] $composerAutoloaderProjectPaths */ public function __construct( ?int $versionId, - bool $readComposerPhpVersion, array $composerAutoloaderProjectPaths ) { $this->versionId = $versionId; - $this->readComposerPhpVersion = $readComposerPhpVersion; $this->composerAutoloaderProjectPaths = $composerAutoloaderProjectPaths; } public function create(): PhpVersionFactory { $composerPhpVersion = null; - if ($this->readComposerPhpVersion && count($this->composerAutoloaderProjectPaths) > 0) { + if (count($this->composerAutoloaderProjectPaths) > 0) { $composerJsonPath = end($this->composerAutoloaderProjectPaths) . '/composer.json'; if (is_file($composerJsonPath)) { try { From c378eeeaedf4321f44c5ce1dbab71b1b546cd2f7 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 22 Sep 2021 18:13:07 +0200 Subject: [PATCH 0368/1284] DateTime instantiation rule --- conf/bleedingEdge.neon | 1 - conf/config.level5.neon | 5 +---- conf/config.neon | 2 -- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 42906356ff..e1de36c0c3 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -2,7 +2,6 @@ parameters: featureToggles: bleedingEdge: true randomIntParameters: true - dateTimeInstantiation: true checkLogicalAndConstantCondition: true checkLogicalOrConstantCondition: true checkMissingTemplateTypeInParameter: true diff --git a/conf/config.level5.neon b/conf/config.level5.neon index cde1e6e556..da9bfe5870 100644 --- a/conf/config.level5.neon +++ b/conf/config.level5.neon @@ -4,14 +4,13 @@ includes: conditionalTags: PHPStan\Rules\Functions\RandomIntParametersRule: phpstan.rules.rule: %featureToggles.randomIntParameters% - PHPStan\Rules\DateTimeInstantiationRule: - phpstan.rules.rule: %featureToggles.dateTimeInstantiation% parameters: checkFunctionArgumentTypes: true checkArgumentsPassedByReference: true rules: + - PHPStan\Rules\DateTimeInstantiationRule - PHPStan\Rules\Functions\ImplodeFunctionRule services: @@ -19,5 +18,3 @@ services: class: PHPStan\Rules\Functions\RandomIntParametersRule arguments: reportMaybes: %reportMaybes% - - - class: PHPStan\Rules\DateTimeInstantiationRule diff --git a/conf/config.neon b/conf/config.neon index 68e9261384..8af42335e4 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -22,7 +22,6 @@ parameters: bleedingEdge: false disableRuntimeReflectionProvider: false randomIntParameters: false - dateTimeInstantiation: false checkLogicalAndConstantCondition: false checkLogicalOrConstantCondition: false checkMissingTemplateTypeInParameter: false @@ -192,7 +191,6 @@ parametersSchema: bleedingEdge: bool(), disableRuntimeReflectionProvider: bool(), randomIntParameters: bool(), - dateTimeInstantiation: bool(), checkLogicalAndConstantCondition: bool(), checkLogicalOrConstantCondition: bool(), checkMissingTemplateTypeInParameter: bool(), From 828ecf9d145c23ffcdf60d0e4fb550b149857c91 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 22 Sep 2021 18:14:31 +0200 Subject: [PATCH 0369/1284] Wrong `@var` usage --- conf/bleedingEdge.neon | 1 - conf/config.level2.neon | 7 +--- conf/config.neon | 2 - .../PhpDoc/WrongVariableNameInVarTagRule.php | 41 ++++++++----------- .../WrongVariableNameInVarTagRuleTest.php | 3 +- 5 files changed, 19 insertions(+), 35 deletions(-) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index e1de36c0c3..736a17617f 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -5,7 +5,6 @@ parameters: checkLogicalAndConstantCondition: true checkLogicalOrConstantCondition: true checkMissingTemplateTypeInParameter: true - wrongVarUsage: true arrayDestructuring: true skipCheckGenericClasses: [] rememberFunctionValues: true diff --git a/conf/config.level2.neon b/conf/config.level2.neon index 995020dfa5..cec7483898 100644 --- a/conf/config.level2.neon +++ b/conf/config.level2.neon @@ -40,6 +40,7 @@ rules: - PHPStan\Rules\PhpDoc\InvalidPhpDocTagValueRule - PHPStan\Rules\PhpDoc\InvalidPHPStanDocTagRule - PHPStan\Rules\PhpDoc\InvalidThrowsPhpDocValueRule + - PHPStan\Rules\PhpDoc\WrongVariableNameInVarTagRule services: - @@ -77,9 +78,3 @@ services: checkMissingVarTagTypehint: %checkMissingVarTagTypehint% tags: - phpstan.rules.rule - - - class: PHPStan\Rules\PhpDoc\WrongVariableNameInVarTagRule - arguments: - checkWrongVarUsage: %featureToggles.wrongVarUsage% - tags: - - phpstan.rules.rule diff --git a/conf/config.neon b/conf/config.neon index 8af42335e4..6c870ca3df 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -25,7 +25,6 @@ parameters: checkLogicalAndConstantCondition: false checkLogicalOrConstantCondition: false checkMissingTemplateTypeInParameter: false - wrongVarUsage: false arrayDestructuring: false skipCheckGenericClasses: [] rememberFunctionValues: false @@ -194,7 +193,6 @@ parametersSchema: checkLogicalAndConstantCondition: bool(), checkLogicalOrConstantCondition: bool(), checkMissingTemplateTypeInParameter: bool(), - wrongVarUsage: bool(), arrayDestructuring: bool(), skipCheckGenericClasses: listOf(string()), rememberFunctionValues: bool(), diff --git a/src/Rules/PhpDoc/WrongVariableNameInVarTagRule.php b/src/Rules/PhpDoc/WrongVariableNameInVarTagRule.php index 18c95f0bc4..dee13b4357 100644 --- a/src/Rules/PhpDoc/WrongVariableNameInVarTagRule.php +++ b/src/Rules/PhpDoc/WrongVariableNameInVarTagRule.php @@ -22,15 +22,11 @@ class WrongVariableNameInVarTagRule implements Rule private FileTypeMapper $fileTypeMapper; - private bool $checkWrongVarUsage; - public function __construct( - FileTypeMapper $fileTypeMapper, - bool $checkWrongVarUsage = false + FileTypeMapper $fileTypeMapper ) { $this->fileTypeMapper = $fileTypeMapper; - $this->checkWrongVarUsage = $checkWrongVarUsage; } public function getNodeType(): string @@ -93,27 +89,24 @@ public function processNode(Node $node, Scope $scope): array } if ($node instanceof InClassNode || $node instanceof InClassMethodNode || $node instanceof InFunctionNode) { - if ($this->checkWrongVarUsage) { - $description = 'a function'; - $originalNode = $node->getOriginalNode(); - if ($originalNode instanceof Node\Stmt\Interface_) { - $description = 'an interface'; - } elseif ($originalNode instanceof Node\Stmt\Class_) { - $description = 'a class'; - } elseif ($originalNode instanceof Node\Stmt\Trait_) { - throw new \PHPStan\ShouldNotHappenException(); - } elseif ($originalNode instanceof Node\Stmt\ClassMethod) { - $description = 'a method'; - } - return [ - RuleErrorBuilder::message(sprintf( - 'PHPDoc tag @var above %s has no effect.', - $description - ))->build(), - ]; + $description = 'a function'; + $originalNode = $node->getOriginalNode(); + if ($originalNode instanceof Node\Stmt\Interface_) { + $description = 'an interface'; + } elseif ($originalNode instanceof Node\Stmt\Class_) { + $description = 'a class'; + } elseif ($originalNode instanceof Node\Stmt\Trait_) { + throw new \PHPStan\ShouldNotHappenException(); + } elseif ($originalNode instanceof Node\Stmt\ClassMethod) { + $description = 'a method'; } - return []; + return [ + RuleErrorBuilder::message(sprintf( + 'PHPDoc tag @var above %s has no effect.', + $description + ))->build(), + ]; } return $this->processStmt($scope, $varTags, null); diff --git a/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php b/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php index 05d47dbc03..12b5f8eaa8 100644 --- a/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php @@ -15,8 +15,7 @@ class WrongVariableNameInVarTagRuleTest extends RuleTestCase protected function getRule(): Rule { return new WrongVariableNameInVarTagRule( - self::getContainer()->getByType(FileTypeMapper::class), - true + self::getContainer()->getByType(FileTypeMapper::class) ); } From 10cc9e853bcf2d0fe34e93cba40b948ddea8a973 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 22 Sep 2021 18:37:12 +0200 Subject: [PATCH 0370/1284] Check LogicalOr and LogicalAnd constant conditions --- composer.lock | 9 +++++---- conf/bleedingEdge.neon | 2 -- conf/config.level4.neon | 2 -- conf/config.neon | 4 ---- .../Comparison/BooleanAndConstantConditionRule.php | 14 +------------- .../Comparison/BooleanOrConstantConditionRule.php | 11 +---------- .../BooleanAndConstantConditionRuleTest.php | 3 +-- .../BooleanOrConstantConditionRuleTest.php | 3 +-- 8 files changed, 9 insertions(+), 39 deletions(-) diff --git a/composer.lock b/composer.lock index a414f5e41e..57c650acf5 100644 --- a/composer.lock +++ b/composer.lock @@ -4472,12 +4472,12 @@ "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "46cf4e8f72713a4c90cbf19c34205cba8cdde95e" + "reference": "17d7c7a10e2cb6317807996ce647f2f823ac97ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/46cf4e8f72713a4c90cbf19c34205cba8cdde95e", - "reference": "46cf4e8f72713a4c90cbf19c34205cba8cdde95e", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/17d7c7a10e2cb6317807996ce647f2f823ac97ff", + "reference": "17d7c7a10e2cb6317807996ce647f2f823ac97ff", "shasum": "" }, "require": { @@ -4485,6 +4485,7 @@ "phpstan/phpstan": "^1.0" }, "require-dev": { + "nikic/php-parser": "^4.13.0", "php-parallel-lint/php-parallel-lint": "^1.2", "phpstan/phpstan-phpunit": "^1.0", "phpunit/phpunit": "^9.5" @@ -4515,7 +4516,7 @@ "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", "source": "https://github.com/phpstan/phpstan-strict-rules/tree/master" }, - "time": "2021-09-13T18:59:45+00:00" + "time": "2021-09-20T16:02:01+00:00" }, { "name": "phpunit/php-code-coverage", diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 736a17617f..4b76e35bfe 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -2,8 +2,6 @@ parameters: featureToggles: bleedingEdge: true randomIntParameters: true - checkLogicalAndConstantCondition: true - checkLogicalOrConstantCondition: true checkMissingTemplateTypeInParameter: true arrayDestructuring: true skipCheckGenericClasses: [] diff --git a/conf/config.level4.neon b/conf/config.level4.neon index d14c723e71..61f32bd2f7 100644 --- a/conf/config.level4.neon +++ b/conf/config.level4.neon @@ -36,7 +36,6 @@ services: class: PHPStan\Rules\Comparison\BooleanAndConstantConditionRule arguments: treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% - checkLogicalAndConstantCondition: %featureToggles.checkLogicalAndConstantCondition% tags: - phpstan.rules.rule @@ -44,7 +43,6 @@ services: class: PHPStan\Rules\Comparison\BooleanOrConstantConditionRule arguments: treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% - checkLogicalOrConstantCondition: %featureToggles.checkLogicalOrConstantCondition% tags: - phpstan.rules.rule diff --git a/conf/config.neon b/conf/config.neon index 6c870ca3df..4e0c223287 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -22,8 +22,6 @@ parameters: bleedingEdge: false disableRuntimeReflectionProvider: false randomIntParameters: false - checkLogicalAndConstantCondition: false - checkLogicalOrConstantCondition: false checkMissingTemplateTypeInParameter: false arrayDestructuring: false skipCheckGenericClasses: [] @@ -190,8 +188,6 @@ parametersSchema: bleedingEdge: bool(), disableRuntimeReflectionProvider: bool(), randomIntParameters: bool(), - checkLogicalAndConstantCondition: bool(), - checkLogicalOrConstantCondition: bool(), checkMissingTemplateTypeInParameter: bool(), arrayDestructuring: bool(), skipCheckGenericClasses: listOf(string()), diff --git a/src/Rules/Comparison/BooleanAndConstantConditionRule.php b/src/Rules/Comparison/BooleanAndConstantConditionRule.php index 9feac9d37c..f4dc1fde2d 100644 --- a/src/Rules/Comparison/BooleanAndConstantConditionRule.php +++ b/src/Rules/Comparison/BooleanAndConstantConditionRule.php @@ -2,8 +2,6 @@ namespace PHPStan\Rules\Comparison; -use PhpParser\Node\Expr\BinaryOp\BooleanAnd; -use PhpParser\Node\Expr\BinaryOp\LogicalAnd; use PHPStan\Node\BooleanAndNode; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\Constant\ConstantBooleanType; @@ -18,17 +16,13 @@ class BooleanAndConstantConditionRule implements \PHPStan\Rules\Rule private bool $treatPhpDocTypesAsCertain; - private bool $checkLogicalAndConstantCondition; - public function __construct( ConstantConditionRuleHelper $helper, - bool $treatPhpDocTypesAsCertain, - bool $checkLogicalAndConstantCondition + bool $treatPhpDocTypesAsCertain ) { $this->helper = $helper; $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; - $this->checkLogicalAndConstantCondition = $checkLogicalAndConstantCondition; } public function getNodeType(): string @@ -42,13 +36,7 @@ public function processNode( ): array { $errors = []; - - /** @var BooleanAnd|LogicalAnd $originalNode */ $originalNode = $node->getOriginalNode(); - if (!$originalNode instanceof BooleanAnd && !$this->checkLogicalAndConstantCondition) { - return []; - } - $leftType = $this->helper->getBooleanType($scope, $originalNode->left); $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; if ($leftType instanceof ConstantBooleanType) { diff --git a/src/Rules/Comparison/BooleanOrConstantConditionRule.php b/src/Rules/Comparison/BooleanOrConstantConditionRule.php index 4b29a14d4d..cb521e5035 100644 --- a/src/Rules/Comparison/BooleanOrConstantConditionRule.php +++ b/src/Rules/Comparison/BooleanOrConstantConditionRule.php @@ -2,7 +2,6 @@ namespace PHPStan\Rules\Comparison; -use PhpParser\Node\Expr\BinaryOp\BooleanOr; use PHPStan\Node\BooleanOrNode; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\Constant\ConstantBooleanType; @@ -17,17 +16,13 @@ class BooleanOrConstantConditionRule implements \PHPStan\Rules\Rule private bool $treatPhpDocTypesAsCertain; - private bool $checkLogicalOrConstantCondition; - public function __construct( ConstantConditionRuleHelper $helper, - bool $treatPhpDocTypesAsCertain, - bool $checkLogicalOrConstantCondition + bool $treatPhpDocTypesAsCertain ) { $this->helper = $helper; $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; - $this->checkLogicalOrConstantCondition = $checkLogicalOrConstantCondition; } public function getNodeType(): string @@ -41,10 +36,6 @@ public function processNode( ): array { $originalNode = $node->getOriginalNode(); - if (!$originalNode instanceof BooleanOr && !$this->checkLogicalOrConstantCondition) { - return []; - } - $messages = []; $leftType = $this->helper->getBooleanType($scope, $originalNode->left); $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; diff --git a/tests/PHPStan/Rules/Comparison/BooleanAndConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/BooleanAndConstantConditionRuleTest.php index 26c45e4313..e18f5a4486 100644 --- a/tests/PHPStan/Rules/Comparison/BooleanAndConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/BooleanAndConstantConditionRuleTest.php @@ -23,8 +23,7 @@ protected function getRule(): \PHPStan\Rules\Rule ), $this->treatPhpDocTypesAsCertain ), - $this->treatPhpDocTypesAsCertain, - true + $this->treatPhpDocTypesAsCertain ); } diff --git a/tests/PHPStan/Rules/Comparison/BooleanOrConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/BooleanOrConstantConditionRuleTest.php index f02dc93cbb..bf0cffe434 100644 --- a/tests/PHPStan/Rules/Comparison/BooleanOrConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/BooleanOrConstantConditionRuleTest.php @@ -23,8 +23,7 @@ protected function getRule(): \PHPStan\Rules\Rule ), $this->treatPhpDocTypesAsCertain ), - $this->treatPhpDocTypesAsCertain, - true + $this->treatPhpDocTypesAsCertain ); } From ba350b54a1f45a26b8d5a17d0633cac5cf49f492 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 22 Sep 2021 18:42:21 +0200 Subject: [PATCH 0371/1284] Update phpstan/* deps --- composer.lock | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/composer.lock b/composer.lock index 57c650acf5..b908faaf7c 100644 --- a/composer.lock +++ b/composer.lock @@ -4301,12 +4301,12 @@ "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-nette.git", - "reference": "a265edd771369bbbaee0be78dded1c0a00972503" + "reference": "f4654b27b107241e052755ec187a0b1964541ba6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-nette/zipball/a265edd771369bbbaee0be78dded1c0a00972503", - "reference": "a265edd771369bbbaee0be78dded1c0a00972503", + "url": "https://api.github.com/repos/phpstan/phpstan-nette/zipball/f4654b27b107241e052755ec187a0b1964541ba6", + "reference": "f4654b27b107241e052755ec187a0b1964541ba6", "shasum": "" }, "require": { @@ -4324,6 +4324,7 @@ "require-dev": { "nette/forms": "^3.0", "nette/utils": "^2.3.0 || ^3.0.0", + "nikic/php-parser": "^4.13.0", "php-parallel-lint/php-parallel-lint": "^1.2", "phpstan/phpstan-php-parser": "^1.0", "phpstan/phpstan-phpunit": "^1.0", @@ -4357,7 +4358,7 @@ "issues": "https://github.com/phpstan/phpstan-nette/issues", "source": "https://github.com/phpstan/phpstan-nette/tree/master" }, - "time": "2021-09-14T13:09:34+00:00" + "time": "2021-09-20T16:12:57+00:00" }, { "name": "phpstan/phpstan-php-parser", @@ -4417,12 +4418,12 @@ "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-phpunit.git", - "reference": "a316f5d59f620ef06f888f6fc95fead9d38e0377" + "reference": "234cb5563905c448083dcd9d315ed882494798ab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/a316f5d59f620ef06f888f6fc95fead9d38e0377", - "reference": "a316f5d59f620ef06f888f6fc95fead9d38e0377", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/234cb5563905c448083dcd9d315ed882494798ab", + "reference": "234cb5563905c448083dcd9d315ed882494798ab", "shasum": "" }, "require": { @@ -4433,6 +4434,7 @@ "phpunit/phpunit": "<7.0" }, "require-dev": { + "nikic/php-parser": "^4.13.0", "php-parallel-lint/php-parallel-lint": "^1.2", "phpstan/phpstan-strict-rules": "^1.0", "phpunit/phpunit": "^9.5" @@ -4464,7 +4466,7 @@ "issues": "https://github.com/phpstan/phpstan-phpunit/issues", "source": "https://github.com/phpstan/phpstan-phpunit/tree/master" }, - "time": "2021-09-12T21:47:21+00:00" + "time": "2021-09-20T15:59:00+00:00" }, { "name": "phpstan/phpstan-strict-rules", @@ -4472,12 +4474,12 @@ "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "17d7c7a10e2cb6317807996ce647f2f823ac97ff" + "reference": "6c7a9815bb3af49113a6f85d073e3e2fc01f7ffa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/17d7c7a10e2cb6317807996ce647f2f823ac97ff", - "reference": "17d7c7a10e2cb6317807996ce647f2f823ac97ff", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/6c7a9815bb3af49113a6f85d073e3e2fc01f7ffa", + "reference": "6c7a9815bb3af49113a6f85d073e3e2fc01f7ffa", "shasum": "" }, "require": { @@ -4516,7 +4518,7 @@ "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", "source": "https://github.com/phpstan/phpstan-strict-rules/tree/master" }, - "time": "2021-09-20T16:02:01+00:00" + "time": "2021-09-22T16:41:47+00:00" }, { "name": "phpunit/php-code-coverage", From afb5e0c40bad383b9eb6f772d9fe2c64872d7fc9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 22 Sep 2021 18:44:04 +0200 Subject: [PATCH 0372/1284] Check missing function-level scope template type in a parameter --- conf/bleedingEdge.neon | 1 - conf/config.neon | 3 -- src/Rules/FunctionDefinitionCheck.php | 36 ++++++++----------- ...lassesInArrowFunctionTypehintsRuleTest.php | 2 +- ...stingClassesInClosureTypehintsRuleTest.php | 2 +- .../ExistingClassesInTypehintsRuleTest.php | 2 +- .../ExistingClassesInTypehintsRuleTest.php | 2 +- 7 files changed, 19 insertions(+), 29 deletions(-) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 4b76e35bfe..6c2a2263d5 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -2,7 +2,6 @@ parameters: featureToggles: bleedingEdge: true randomIntParameters: true - checkMissingTemplateTypeInParameter: true arrayDestructuring: true skipCheckGenericClasses: [] rememberFunctionValues: true diff --git a/conf/config.neon b/conf/config.neon index 4e0c223287..9e144072c8 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -22,7 +22,6 @@ parameters: bleedingEdge: false disableRuntimeReflectionProvider: false randomIntParameters: false - checkMissingTemplateTypeInParameter: false arrayDestructuring: false skipCheckGenericClasses: [] rememberFunctionValues: false @@ -188,7 +187,6 @@ parametersSchema: bleedingEdge: bool(), disableRuntimeReflectionProvider: bool(), randomIntParameters: bool(), - checkMissingTemplateTypeInParameter: bool(), arrayDestructuring: bool(), skipCheckGenericClasses: listOf(string()), rememberFunctionValues: bool(), @@ -779,7 +777,6 @@ services: arguments: checkClassCaseSensitivity: %checkClassCaseSensitivity% checkThisOnly: %checkThisOnly% - checkMissingTemplateTypeInParameter: %featureToggles.checkMissingTemplateTypeInParameter% - class: PHPStan\Rules\FunctionReturnTypeCheck diff --git a/src/Rules/FunctionDefinitionCheck.php b/src/Rules/FunctionDefinitionCheck.php index ea08541362..db0d5b9c3a 100644 --- a/src/Rules/FunctionDefinitionCheck.php +++ b/src/Rules/FunctionDefinitionCheck.php @@ -39,15 +39,12 @@ class FunctionDefinitionCheck private bool $checkThisOnly; - private bool $checkMissingTemplateTypeInParameter; - public function __construct( ReflectionProvider $reflectionProvider, ClassCaseSensitivityCheck $classCaseSensitivityCheck, PhpVersion $phpVersion, bool $checkClassCaseSensitivity, - bool $checkThisOnly, - bool $checkMissingTemplateTypeInParameter + bool $checkThisOnly ) { $this->reflectionProvider = $reflectionProvider; @@ -55,7 +52,6 @@ public function __construct( $this->phpVersion = $phpVersion; $this->checkClassCaseSensitivity = $checkClassCaseSensitivity; $this->checkThisOnly = $checkThisOnly; - $this->checkMissingTemplateTypeInParameter = $checkMissingTemplateTypeInParameter; } /** @@ -316,24 +312,22 @@ private function checkParametersAcceptor( $errors[] = RuleErrorBuilder::message(sprintf($returnMessage, $parametersAcceptor->getReturnType()->describe(VerbosityLevel::typeOnly())))->line($returnTypeNode->getLine())->build(); } - if ($this->checkMissingTemplateTypeInParameter) { - $templateTypeMap = $parametersAcceptor->getTemplateTypeMap(); - $templateTypes = $templateTypeMap->getTypes(); - if (count($templateTypes) > 0) { - foreach ($parametersAcceptor->getParameters() as $parameter) { - TypeTraverser::map($parameter->getType(), static function (Type $type, callable $traverse) use (&$templateTypes): Type { - if ($type instanceof TemplateType) { - unset($templateTypes[$type->getName()]); - return $traverse($type); - } - + $templateTypeMap = $parametersAcceptor->getTemplateTypeMap(); + $templateTypes = $templateTypeMap->getTypes(); + if (count($templateTypes) > 0) { + foreach ($parametersAcceptor->getParameters() as $parameter) { + TypeTraverser::map($parameter->getType(), static function (Type $type, callable $traverse) use (&$templateTypes): Type { + if ($type instanceof TemplateType) { + unset($templateTypes[$type->getName()]); return $traverse($type); - }); - } + } - foreach (array_keys($templateTypes) as $templateTypeName) { - $errors[] = RuleErrorBuilder::message(sprintf($templateTypeMissingInParameterMessage, $templateTypeName))->build(); - } + return $traverse($type); + }); + } + + foreach (array_keys($templateTypes) as $templateTypeName) { + $errors[] = RuleErrorBuilder::message(sprintf($templateTypeMissingInParameterMessage, $templateTypeName))->build(); } } diff --git a/tests/PHPStan/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRuleTest.php b/tests/PHPStan/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRuleTest.php index f3ad46c3ff..4c01c5a4fe 100644 --- a/tests/PHPStan/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRuleTest.php @@ -18,7 +18,7 @@ class ExistingClassesInArrowFunctionTypehintsRuleTest extends \PHPStan\Testing\R protected function getRule(): \PHPStan\Rules\Rule { $broker = $this->createReflectionProvider(); - return new ExistingClassesInArrowFunctionTypehintsRule(new FunctionDefinitionCheck($broker, new ClassCaseSensitivityCheck($broker), new PhpVersion($this->phpVersionId), true, false, true)); + return new ExistingClassesInArrowFunctionTypehintsRule(new FunctionDefinitionCheck($broker, new ClassCaseSensitivityCheck($broker), new PhpVersion($this->phpVersionId), true, false)); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Functions/ExistingClassesInClosureTypehintsRuleTest.php b/tests/PHPStan/Rules/Functions/ExistingClassesInClosureTypehintsRuleTest.php index 4d5d5a511c..d7252b4ca4 100644 --- a/tests/PHPStan/Rules/Functions/ExistingClassesInClosureTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ExistingClassesInClosureTypehintsRuleTest.php @@ -18,7 +18,7 @@ class ExistingClassesInClosureTypehintsRuleTest extends \PHPStan\Testing\RuleTes protected function getRule(): \PHPStan\Rules\Rule { $broker = $this->createReflectionProvider(); - return new ExistingClassesInClosureTypehintsRule(new FunctionDefinitionCheck($broker, new ClassCaseSensitivityCheck($broker), new PhpVersion($this->phpVersionId), true, false, true)); + return new ExistingClassesInClosureTypehintsRule(new FunctionDefinitionCheck($broker, new ClassCaseSensitivityCheck($broker), new PhpVersion($this->phpVersionId), true, false)); } public function testExistingClassInTypehint(): void diff --git a/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php b/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php index b28263881f..859b7a2e12 100644 --- a/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php @@ -19,7 +19,7 @@ class ExistingClassesInTypehintsRuleTest extends \PHPStan\Testing\RuleTestCase protected function getRule(): \PHPStan\Rules\Rule { $broker = $this->createReflectionProvider(); - return new ExistingClassesInTypehintsRule(new FunctionDefinitionCheck($broker, new ClassCaseSensitivityCheck($broker), new PhpVersion($this->phpVersionId), true, false, true)); + return new ExistingClassesInTypehintsRule(new FunctionDefinitionCheck($broker, new ClassCaseSensitivityCheck($broker), new PhpVersion($this->phpVersionId), true, false)); } public function testExistingClassInTypehint(): void diff --git a/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php b/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php index ba82c2b2aa..70be8fb673 100644 --- a/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php @@ -19,7 +19,7 @@ class ExistingClassesInTypehintsRuleTest extends \PHPStan\Testing\RuleTestCase protected function getRule(): \PHPStan\Rules\Rule { $broker = $this->createReflectionProvider(); - return new ExistingClassesInTypehintsRule(new FunctionDefinitionCheck($broker, new ClassCaseSensitivityCheck($broker), new PhpVersion($this->phpVersionId), true, false, true)); + return new ExistingClassesInTypehintsRule(new FunctionDefinitionCheck($broker, new ClassCaseSensitivityCheck($broker), new PhpVersion($this->phpVersionId), true, false)); } public function testExistingClassInTypehint(): void From 58c29fd92956a15e73f34b57a21fef2535b8435c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 22 Sep 2021 18:46:51 +0200 Subject: [PATCH 0373/1284] Check unresolvable type after resolving generics in a function call --- conf/bleedingEdge.neon | 1 - conf/config.neon | 3 --- src/Rules/FunctionCallParametersCheck.php | 9 ++------- tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php | 1 - .../Rules/Classes/ClassConstantAttributesRuleTest.php | 1 - tests/PHPStan/Rules/Classes/InstantiationRuleTest.php | 2 +- .../Rules/Functions/ArrowFunctionAttributesRuleTest.php | 1 - tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php | 1 - .../Rules/Functions/CallToFunctionParametersRuleTest.php | 2 +- .../Rules/Functions/ClosureAttributesRuleTest.php | 1 - .../Rules/Functions/FunctionAttributesRuleTest.php | 1 - .../PHPStan/Rules/Functions/ParamAttributesRuleTest.php | 1 - tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php | 6 +----- .../PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php | 2 +- tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php | 1 - .../Rules/Properties/PropertyAttributesRuleTest.php | 1 - 16 files changed, 6 insertions(+), 28 deletions(-) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 6c2a2263d5..402df7ce3b 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -6,7 +6,6 @@ parameters: skipCheckGenericClasses: [] rememberFunctionValues: true apiRules: true - neverInGenericReturnType: true finalByPhpDocTag: true classConstants: true privateStaticCall: true diff --git a/conf/config.neon b/conf/config.neon index 9e144072c8..e2730e8b54 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -26,7 +26,6 @@ parameters: skipCheckGenericClasses: [] rememberFunctionValues: false apiRules: false - neverInGenericReturnType: false finalByPhpDocTag: false classConstants: false privateStaticCall: false @@ -191,7 +190,6 @@ parametersSchema: skipCheckGenericClasses: listOf(string()), rememberFunctionValues: bool(), apiRules: bool(), - neverInGenericReturnType: bool(), finalByPhpDocTag: bool(), classConstants: bool(), privateStaticCall: bool(), @@ -770,7 +768,6 @@ services: checkArgumentsPassedByReference: %checkArgumentsPassedByReference% checkExtraArguments: %checkExtraArguments% checkMissingTypehints: %checkMissingTypehints% - checkNeverInGenericReturnType: %featureToggles.neverInGenericReturnType% - class: PHPStan\Rules\FunctionDefinitionCheck diff --git a/src/Rules/FunctionCallParametersCheck.php b/src/Rules/FunctionCallParametersCheck.php index 0faba58866..f672293d66 100644 --- a/src/Rules/FunctionCallParametersCheck.php +++ b/src/Rules/FunctionCallParametersCheck.php @@ -37,8 +37,6 @@ class FunctionCallParametersCheck private bool $checkMissingTypehints; - private bool $checkNeverInGenericReturnType; - public function __construct( RuleLevelHelper $ruleLevelHelper, NullsafeCheck $nullsafeCheck, @@ -47,8 +45,7 @@ public function __construct( bool $checkArgumentTypes, bool $checkArgumentsPassedByReference, bool $checkExtraArguments, - bool $checkMissingTypehints, - bool $checkNeverInGenericReturnType + bool $checkMissingTypehints ) { $this->ruleLevelHelper = $ruleLevelHelper; @@ -59,7 +56,6 @@ public function __construct( $this->checkArgumentsPassedByReference = $checkArgumentsPassedByReference; $this->checkExtraArguments = $checkExtraArguments; $this->checkMissingTypehints = $checkMissingTypehints; - $this->checkNeverInGenericReturnType = $checkNeverInGenericReturnType; } /** @@ -357,8 +353,7 @@ static function (Type $type): bool { } if ( - $this->checkNeverInGenericReturnType - && !$this->unresolvableTypeHelper->containsUnresolvableType($originalParametersAcceptor->getReturnType()) + !$this->unresolvableTypeHelper->containsUnresolvableType($originalParametersAcceptor->getReturnType()) && $this->unresolvableTypeHelper->containsUnresolvableType($parametersAcceptor->getReturnType()) ) { $errors[] = RuleErrorBuilder::message($messages[12])->line($funcCall->getLine())->build(); diff --git a/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php b/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php index 64cb2a425a..02894dedbd 100644 --- a/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php @@ -32,7 +32,6 @@ protected function getRule(): Rule true, true, true, - true, true ), new ClassCaseSensitivityCheck($reflectionProvider, false) diff --git a/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php b/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php index 124770a220..b233afdb49 100644 --- a/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php @@ -32,7 +32,6 @@ protected function getRule(): Rule true, true, true, - true, true ), new ClassCaseSensitivityCheck($reflectionProvider, false) diff --git a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php index af33293ae9..c9f7a241b6 100644 --- a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php +++ b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php @@ -20,7 +20,7 @@ protected function getRule(): \PHPStan\Rules\Rule $broker = $this->createReflectionProvider(); return new InstantiationRule( $broker, - new FunctionCallParametersCheck(new RuleLevelHelper($broker, true, false, true, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), true, true, true, true, true), + new FunctionCallParametersCheck(new RuleLevelHelper($broker, true, false, true, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), true, true, true, true), new ClassCaseSensitivityCheck($broker) ); } diff --git a/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php index 9331bf4ba0..64fcae7e6a 100644 --- a/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php @@ -32,7 +32,6 @@ protected function getRule(): Rule true, true, true, - true, true ), new ClassCaseSensitivityCheck($reflectionProvider, false) diff --git a/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php b/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php index 3fb303f6d9..221ba5c90b 100644 --- a/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php @@ -29,7 +29,6 @@ protected function getRule(): \PHPStan\Rules\Rule true, true, true, - true, true ), $ruleLevelHelper, diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index ad590cb524..89544277ab 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -23,7 +23,7 @@ protected function getRule(): \PHPStan\Rules\Rule $broker = $this->createReflectionProvider(); return new CallToFunctionParametersRule( $broker, - new FunctionCallParametersCheck(new RuleLevelHelper($broker, true, false, true, $this->checkExplicitMixed), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), true, true, true, true, true) + new FunctionCallParametersCheck(new RuleLevelHelper($broker, true, false, true, $this->checkExplicitMixed), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), true, true, true, true) ); } diff --git a/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php index b4d9b5f166..ba897cdaa6 100644 --- a/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php @@ -32,7 +32,6 @@ protected function getRule(): Rule true, true, true, - true, true ), new ClassCaseSensitivityCheck($reflectionProvider, false) diff --git a/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php index 32e84bf76b..799e1a0b90 100644 --- a/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php @@ -32,7 +32,6 @@ protected function getRule(): Rule true, true, true, - true, true ), new ClassCaseSensitivityCheck($reflectionProvider, false) diff --git a/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php index aa63e32e66..e64960fdbe 100644 --- a/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php @@ -32,7 +32,6 @@ protected function getRule(): Rule true, true, true, - true, true ), new ClassCaseSensitivityCheck($reflectionProvider, false) diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 2e3c6c9204..463b8fff5b 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -31,16 +31,13 @@ class CallMethodsRuleTest extends \PHPStan\Testing\RuleTestCase /** @var int */ private $phpVersion = PHP_VERSION_ID; - /** @var bool */ - private $checkNeverInGenericReturnType = false; - protected function getRule(): Rule { $broker = $this->createReflectionProvider(); $ruleLevelHelper = new RuleLevelHelper($broker, $this->checkNullables, $this->checkThisOnly, $this->checkUnionTypes, $this->checkExplicitMixed); return new CallMethodsRule( $broker, - new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new PhpVersion($this->phpVersion), new UnresolvableTypeHelper(), true, true, true, true, $this->checkNeverInGenericReturnType), + new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new PhpVersion($this->phpVersion), new UnresolvableTypeHelper(), true, true, true, true), $ruleLevelHelper, true, true @@ -1987,7 +1984,6 @@ public function testGenericReturnTypeResolvedToNever(): void $this->checkThisOnly = false; $this->checkNullables = true; $this->checkUnionTypes = true; - $this->checkNeverInGenericReturnType = true; $this->analyse([__DIR__ . '/data/generic-return-type-never.php'], [ [ 'Return type of call to method GenericReturnTypeNever\Foo::doBar() contains unresolvable type.', diff --git a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php index e5943c2fbd..d0c05ddd38 100644 --- a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php @@ -24,7 +24,7 @@ protected function getRule(): \PHPStan\Rules\Rule $ruleLevelHelper = new RuleLevelHelper($broker, true, $this->checkThisOnly, true, false); return new CallStaticMethodsRule( $broker, - new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), true, true, true, true, true), + new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), true, true, true, true), $ruleLevelHelper, new ClassCaseSensitivityCheck($broker), true, diff --git a/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php b/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php index 52c329544f..7bae918088 100644 --- a/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php @@ -32,7 +32,6 @@ protected function getRule(): Rule true, true, true, - true, true ), new ClassCaseSensitivityCheck($reflectionProvider, false) diff --git a/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php b/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php index f4e02a304c..92b41031aa 100644 --- a/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php @@ -32,7 +32,6 @@ protected function getRule(): Rule true, true, true, - true, true ), new ClassCaseSensitivityCheck($reflectionProvider, false) From c91edd25007bb1a4b875f4eda5722038ca18d68c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 22 Sep 2021 18:49:44 +0200 Subject: [PATCH 0374/1284] OverridingPropertyRule --- conf/bleedingEdge.neon | 1 - conf/config.level0.neon | 4 ++-- conf/config.neon | 2 -- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 402df7ce3b..0a05d4b86c 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -9,4 +9,3 @@ parameters: finalByPhpDocTag: true classConstants: true privateStaticCall: true - overridingProperty: true diff --git a/conf/config.level0.neon b/conf/config.level0.neon index db52e93ac7..fd162fd1db 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -22,8 +22,6 @@ conditionalTags: phpstan.rules.rule: %featureToggles.classConstants% PHPStan\Rules\Properties\UninitializedPropertyRule: phpstan.rules.rule: %checkUninitializedProperties% - PHPStan\Rules\Properties\OverridingPropertyRule: - phpstan.rules.rule: %featureToggles.overridingProperty% rules: - PHPStan\Rules\Arrays\DuplicateKeysInLiteralArraysRule @@ -204,6 +202,8 @@ services: arguments: checkPhpDocMethodSignatures: %checkPhpDocMethodSignatures% reportMaybes: %reportMaybesInPropertyPhpDocTypes% + tags: + - phpstan.rules.rule - class: PHPStan\Rules\Properties\UninitializedPropertyRule diff --git a/conf/config.neon b/conf/config.neon index e2730e8b54..dd51fe0d14 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -29,7 +29,6 @@ parameters: finalByPhpDocTag: false classConstants: false privateStaticCall: false - overridingProperty: false fileExtensions: - php checkAdvancedIsset: false @@ -193,7 +192,6 @@ parametersSchema: finalByPhpDocTag: bool(), classConstants: bool(), privateStaticCall: bool(), - overridingProperty: bool() ]) fileExtensions: listOf(string()) checkAdvancedIsset: bool() From 952db4db03933ad3df97d8ad5c7ff7002aaa6dcd Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 22 Sep 2021 18:52:26 +0200 Subject: [PATCH 0375/1284] Rules for accessing private class elements through static:: --- conf/bleedingEdge.neon | 1 - conf/config.level2.neon | 15 +++------------ 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 0a05d4b86c..caf584bab1 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -8,4 +8,3 @@ parameters: apiRules: true finalByPhpDocTag: true classConstants: true - privateStaticCall: true diff --git a/conf/config.level2.neon b/conf/config.level2.neon index cec7483898..37f6914f4d 100644 --- a/conf/config.level2.neon +++ b/conf/config.level2.neon @@ -7,20 +7,15 @@ parameters: checkPhpDocMissingReturn: true conditionalTags: - PHPStan\Rules\Classes\AccessPrivateConstantThroughStaticRule: - phpstan.rules.rule: %featureToggles.privateStaticCall% PHPStan\Rules\PhpDoc\IncompatibleClassConstantPhpDocTypeRule: phpstan.rules.rule: %featureToggles.classConstants% - PHPStan\Rules\Methods\CallPrivateMethodThroughStaticRule: - phpstan.rules.rule: %featureToggles.privateStaticCall% - PHPStan\Rules\Properties\AccessPrivatePropertyThroughStaticRule: - phpstan.rules.rule: %featureToggles.privateStaticCall% rules: - PHPStan\Rules\Cast\EchoRule - PHPStan\Rules\Cast\InvalidCastRule - PHPStan\Rules\Cast\InvalidPartOfEncapsedStringRule - PHPStan\Rules\Cast\PrintRule + - PHPStan\Rules\Classes\AccessPrivateConstantThroughStaticRule - PHPStan\Rules\Comparison\UsageOfVoidMatchExpressionRule - PHPStan\Rules\Functions\IncompatibleDefaultParameterTypeRule - PHPStan\Rules\Generics\ClassTemplateTypeRule @@ -31,6 +26,7 @@ rules: - PHPStan\Rules\Generics\MethodSignatureVarianceRule - PHPStan\Rules\Generics\TraitTemplateTypeRule - PHPStan\Rules\Generics\UsedTraitsRule + - PHPStan\Rules\Methods\CallPrivateMethodThroughStaticRule - PHPStan\Rules\Methods\IncompatibleDefaultParameterTypeRule - PHPStan\Rules\Operators\InvalidBinaryOperationRule - PHPStan\Rules\Operators\InvalidUnaryOperationRule @@ -41,10 +37,9 @@ rules: - PHPStan\Rules\PhpDoc\InvalidPHPStanDocTagRule - PHPStan\Rules\PhpDoc\InvalidThrowsPhpDocValueRule - PHPStan\Rules\PhpDoc\WrongVariableNameInVarTagRule + - PHPStan\Rules\Properties\AccessPrivatePropertyThroughStaticRule services: - - - class: PHPStan\Rules\Classes\AccessPrivateConstantThroughStaticRule - class: PHPStan\Rules\Classes\MixinRule arguments: @@ -61,12 +56,8 @@ services: class: PHPStan\Rules\Generics\ClassAncestorsRule tags: - phpstan.rules.rule - - - class: PHPStan\Rules\Methods\CallPrivateMethodThroughStaticRule - class: PHPStan\Rules\PhpDoc\IncompatibleClassConstantPhpDocTypeRule - - - class: PHPStan\Rules\Properties\AccessPrivatePropertyThroughStaticRule - class: PHPStan\Rules\Generics\InterfaceAncestorsRule tags: From 69153214877f363acd93dce4fe6bf9aa391ce246 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 22 Sep 2021 18:54:38 +0200 Subject: [PATCH 0376/1284] Class constants rules - overriding constants, incompatible PHPDoc types --- conf/bleedingEdge.neon | 1 - conf/config.level0.neon | 4 ++-- conf/config.level2.neon | 7 +------ conf/config.neon | 2 -- 4 files changed, 3 insertions(+), 11 deletions(-) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index caf584bab1..c58a4c3090 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -7,4 +7,3 @@ parameters: rememberFunctionValues: true apiRules: true finalByPhpDocTag: true - classConstants: true diff --git a/conf/config.level0.neon b/conf/config.level0.neon index fd162fd1db..0ee4a24909 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -18,8 +18,6 @@ conditionalTags: phpstan.rules.rule: %featureToggles.apiRules% PHPStan\Rules\Api\PhpStanNamespaceIn3rdPartyPackageRule: phpstan.rules.rule: %featureToggles.apiRules% - PHPStan\Rules\Constants\OverridingConstantRule: - phpstan.rules.rule: %featureToggles.classConstants% PHPStan\Rules\Properties\UninitializedPropertyRule: phpstan.rules.rule: %checkUninitializedProperties% @@ -140,6 +138,8 @@ services: class: PHPStan\Rules\Constants\OverridingConstantRule arguments: checkPhpDocMethodSignatures: %checkPhpDocMethodSignatures% + tags: + - phpstan.rules.rule - class: PHPStan\Rules\Methods\OverridingMethodRule diff --git a/conf/config.level2.neon b/conf/config.level2.neon index 37f6914f4d..0a4e60096b 100644 --- a/conf/config.level2.neon +++ b/conf/config.level2.neon @@ -6,10 +6,6 @@ parameters: checkThisOnly: false checkPhpDocMissingReturn: true -conditionalTags: - PHPStan\Rules\PhpDoc\IncompatibleClassConstantPhpDocTypeRule: - phpstan.rules.rule: %featureToggles.classConstants% - rules: - PHPStan\Rules\Cast\EchoRule - PHPStan\Rules\Cast\InvalidCastRule @@ -31,6 +27,7 @@ rules: - PHPStan\Rules\Operators\InvalidBinaryOperationRule - PHPStan\Rules\Operators\InvalidUnaryOperationRule - PHPStan\Rules\Operators\InvalidComparisonOperationRule + - PHPStan\Rules\PhpDoc\IncompatibleClassConstantPhpDocTypeRule - PHPStan\Rules\PhpDoc\IncompatiblePhpDocTypeRule - PHPStan\Rules\PhpDoc\IncompatiblePropertyPhpDocTypeRule - PHPStan\Rules\PhpDoc\InvalidPhpDocTagValueRule @@ -56,8 +53,6 @@ services: class: PHPStan\Rules\Generics\ClassAncestorsRule tags: - phpstan.rules.rule - - - class: PHPStan\Rules\PhpDoc\IncompatibleClassConstantPhpDocTypeRule - class: PHPStan\Rules\Generics\InterfaceAncestorsRule tags: diff --git a/conf/config.neon b/conf/config.neon index dd51fe0d14..f282b44167 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -27,7 +27,6 @@ parameters: rememberFunctionValues: false apiRules: false finalByPhpDocTag: false - classConstants: false privateStaticCall: false fileExtensions: - php @@ -190,7 +189,6 @@ parametersSchema: rememberFunctionValues: bool(), apiRules: bool(), finalByPhpDocTag: bool(), - classConstants: bool(), privateStaticCall: bool(), ]) fileExtensions: listOf(string()) From 09abbe8bd4b634380db569efd28b682b94807232 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 22 Sep 2021 18:56:11 +0200 Subject: [PATCH 0377/1284] Array destructuring rule --- conf/bleedingEdge.neon | 1 - conf/config.level3.neon | 8 +------- conf/config.neon | 2 -- 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index c58a4c3090..38c5eb29b4 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -2,7 +2,6 @@ parameters: featureToggles: bleedingEdge: true randomIntParameters: true - arrayDestructuring: true skipCheckGenericClasses: [] rememberFunctionValues: true apiRules: true diff --git a/conf/config.level3.neon b/conf/config.level3.neon index 939c6adb06..4f6affaa1f 100644 --- a/conf/config.level3.neon +++ b/conf/config.level3.neon @@ -3,6 +3,7 @@ includes: rules: - PHPStan\Rules\Arrays\AppendedArrayItemTypeRule + - PHPStan\Rules\Arrays\ArrayDestructuringRule - PHPStan\Rules\Arrays\IterableInForeachRule - PHPStan\Rules\Arrays\OffsetAccessAssignmentRule - PHPStan\Rules\Arrays\OffsetAccessAssignOpRule @@ -17,10 +18,6 @@ rules: - PHPStan\Rules\Variables\ThrowTypeRule - PHPStan\Rules\Variables\VariableCloningRule -conditionalTags: - PHPStan\Rules\Arrays\ArrayDestructuringRule: - phpstan.rules.rule: %featureToggles.arrayDestructuring% - parameters: checkPhpDocMethodSignatures: true @@ -32,9 +29,6 @@ services: tags: - phpstan.rules.rule - - - class: PHPStan\Rules\Arrays\ArrayDestructuringRule - - class: PHPStan\Rules\Arrays\InvalidKeyInArrayDimFetchRule arguments: diff --git a/conf/config.neon b/conf/config.neon index f282b44167..3d7b42784e 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -22,7 +22,6 @@ parameters: bleedingEdge: false disableRuntimeReflectionProvider: false randomIntParameters: false - arrayDestructuring: false skipCheckGenericClasses: [] rememberFunctionValues: false apiRules: false @@ -184,7 +183,6 @@ parametersSchema: bleedingEdge: bool(), disableRuntimeReflectionProvider: bool(), randomIntParameters: bool(), - arrayDestructuring: bool(), skipCheckGenericClasses: listOf(string()), rememberFunctionValues: bool(), apiRules: bool(), From 4847656dda41b5b927b08e189573e6039e7312a8 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 22 Sep 2021 18:59:19 +0200 Subject: [PATCH 0378/1284] random_int rule --- conf/bleedingEdge.neon | 1 - conf/config.level5.neon | 6 ++---- conf/config.neon | 2 -- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 38c5eb29b4..a4ddee503f 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -1,7 +1,6 @@ parameters: featureToggles: bleedingEdge: true - randomIntParameters: true skipCheckGenericClasses: [] rememberFunctionValues: true apiRules: true diff --git a/conf/config.level5.neon b/conf/config.level5.neon index da9bfe5870..c890be88ec 100644 --- a/conf/config.level5.neon +++ b/conf/config.level5.neon @@ -1,10 +1,6 @@ includes: - config.level4.neon -conditionalTags: - PHPStan\Rules\Functions\RandomIntParametersRule: - phpstan.rules.rule: %featureToggles.randomIntParameters% - parameters: checkFunctionArgumentTypes: true checkArgumentsPassedByReference: true @@ -18,3 +14,5 @@ services: class: PHPStan\Rules\Functions\RandomIntParametersRule arguments: reportMaybes: %reportMaybes% + tags: + - phpstan.rules.rule diff --git a/conf/config.neon b/conf/config.neon index 3d7b42784e..07f8792189 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -21,7 +21,6 @@ parameters: featureToggles: bleedingEdge: false disableRuntimeReflectionProvider: false - randomIntParameters: false skipCheckGenericClasses: [] rememberFunctionValues: false apiRules: false @@ -182,7 +181,6 @@ parametersSchema: featureToggles: structure([ bleedingEdge: bool(), disableRuntimeReflectionProvider: bool(), - randomIntParameters: bool(), skipCheckGenericClasses: listOf(string()), rememberFunctionValues: bool(), apiRules: bool(), From 3c6a3f7c22a1282b2163fa4a7e2a00e6b94d1f3a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 22 Sep 2021 19:01:16 +0200 Subject: [PATCH 0379/1284] Check extending class that's final by `@final` PHPDoc tag --- conf/bleedingEdge.neon | 1 - conf/config.level0.neon | 2 -- conf/config.neon | 2 -- src/Rules/Classes/ExistingClassInClassExtendsRule.php | 8 ++------ .../Rules/Classes/ExistingClassInClassExtendsRuleTest.php | 3 +-- 5 files changed, 3 insertions(+), 13 deletions(-) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index a4ddee503f..0bee738694 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -4,4 +4,3 @@ parameters: skipCheckGenericClasses: [] rememberFunctionValues: true apiRules: true - finalByPhpDocTag: true diff --git a/conf/config.level0.neon b/conf/config.level0.neon index 0ee4a24909..b625d43101 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -92,8 +92,6 @@ services: - class: PHPStan\Rules\Classes\ExistingClassInClassExtendsRule - arguments: - checkFinalByPhpDocTag: %featureToggles.finalByPhpDocTag% tags: - phpstan.rules.rule diff --git a/conf/config.neon b/conf/config.neon index 07f8792189..780f59335f 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -24,7 +24,6 @@ parameters: skipCheckGenericClasses: [] rememberFunctionValues: false apiRules: false - finalByPhpDocTag: false privateStaticCall: false fileExtensions: - php @@ -184,7 +183,6 @@ parametersSchema: skipCheckGenericClasses: listOf(string()), rememberFunctionValues: bool(), apiRules: bool(), - finalByPhpDocTag: bool(), privateStaticCall: bool(), ]) fileExtensions: listOf(string()) diff --git a/src/Rules/Classes/ExistingClassInClassExtendsRule.php b/src/Rules/Classes/ExistingClassInClassExtendsRule.php index c500cc1ee0..9c39351411 100644 --- a/src/Rules/Classes/ExistingClassInClassExtendsRule.php +++ b/src/Rules/Classes/ExistingClassInClassExtendsRule.php @@ -19,17 +19,13 @@ class ExistingClassInClassExtendsRule implements \PHPStan\Rules\Rule private ReflectionProvider $reflectionProvider; - private bool $checkFinalByPhpDocTag; - public function __construct( ClassCaseSensitivityCheck $classCaseSensitivityCheck, - ReflectionProvider $reflectionProvider, - bool $checkFinalByPhpDocTag = false + ReflectionProvider $reflectionProvider ) { $this->classCaseSensitivityCheck = $classCaseSensitivityCheck; $this->reflectionProvider = $reflectionProvider; - $this->checkFinalByPhpDocTag = $checkFinalByPhpDocTag; } public function getNodeType(): string @@ -76,7 +72,7 @@ public function processNode(Node $node, Scope $scope): array $currentClassName !== null ? sprintf('Class %s', $currentClassName) : 'Anonymous class', $extendedClassName ))->nonIgnorable()->build(); - } elseif ($this->checkFinalByPhpDocTag && $reflection->isFinal()) { + } elseif ($reflection->isFinal()) { $messages[] = RuleErrorBuilder::message(sprintf( '%s extends @final class %s.', $currentClassName !== null ? sprintf('Class %s', $currentClassName) : 'Anonymous class', diff --git a/tests/PHPStan/Rules/Classes/ExistingClassInClassExtendsRuleTest.php b/tests/PHPStan/Rules/Classes/ExistingClassInClassExtendsRuleTest.php index 43c2b2242d..f429d082dd 100644 --- a/tests/PHPStan/Rules/Classes/ExistingClassInClassExtendsRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ExistingClassInClassExtendsRuleTest.php @@ -16,8 +16,7 @@ protected function getRule(): Rule $broker = $this->createReflectionProvider(); return new ExistingClassInClassExtendsRule( new ClassCaseSensitivityCheck($broker), - $broker, - true + $broker ); } From 37987d55f7b8c4f61c020fce0f9de4ef4eb931fd Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 22 Sep 2021 19:01:34 +0200 Subject: [PATCH 0380/1284] Cleanup --- conf/config.neon | 2 -- 1 file changed, 2 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index 780f59335f..cd01a2b96e 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -24,7 +24,6 @@ parameters: skipCheckGenericClasses: [] rememberFunctionValues: false apiRules: false - privateStaticCall: false fileExtensions: - php checkAdvancedIsset: false @@ -183,7 +182,6 @@ parametersSchema: skipCheckGenericClasses: listOf(string()), rememberFunctionValues: bool(), apiRules: bool(), - privateStaticCall: bool(), ]) fileExtensions: listOf(string()) checkAdvancedIsset: bool() From 26c7fd8275f39dfd3ef7ad5d4710209eb2efbc93 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 22 Sep 2021 19:03:00 +0200 Subject: [PATCH 0381/1284] TypeSpecifier - remember function values --- conf/bleedingEdge.neon | 1 - conf/config.neon | 2 -- src/Analyser/TypeSpecifier.php | 16 +++------------- src/Analyser/TypeSpecifierFactory.php | 1 - 4 files changed, 3 insertions(+), 17 deletions(-) diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 0bee738694..1c21853665 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -2,5 +2,4 @@ parameters: featureToggles: bleedingEdge: true skipCheckGenericClasses: [] - rememberFunctionValues: true apiRules: true diff --git a/conf/config.neon b/conf/config.neon index cd01a2b96e..c5a8865b2e 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -22,7 +22,6 @@ parameters: bleedingEdge: false disableRuntimeReflectionProvider: false skipCheckGenericClasses: [] - rememberFunctionValues: false apiRules: false fileExtensions: - php @@ -180,7 +179,6 @@ parametersSchema: bleedingEdge: bool(), disableRuntimeReflectionProvider: bool(), skipCheckGenericClasses: listOf(string()), - rememberFunctionValues: bool(), apiRules: bool(), ]) fileExtensions: listOf(string()) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 8ef6a2c6d8..fbf31d29f4 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -58,8 +58,6 @@ class TypeSpecifier private ReflectionProvider $reflectionProvider; - private bool $rememberFunctionValues; - /** @var \PHPStan\Type\FunctionTypeSpecifyingExtension[] */ private array $functionTypeSpecifyingExtensions; @@ -85,7 +83,6 @@ class TypeSpecifier public function __construct( \PhpParser\PrettyPrinter\Standard $printer, ReflectionProvider $reflectionProvider, - bool $rememberFunctionValues, array $functionTypeSpecifyingExtensions, array $methodTypeSpecifyingExtensions, array $staticMethodTypeSpecifyingExtensions @@ -93,7 +90,6 @@ public function __construct( { $this->printer = $printer; $this->reflectionProvider = $reflectionProvider; - $this->rememberFunctionValues = $rememberFunctionValues; foreach (array_merge($functionTypeSpecifyingExtensions, $methodTypeSpecifyingExtensions, $staticMethodTypeSpecifyingExtensions) as $extension) { if (!($extension instanceof TypeSpecifierAwareExtension)) { @@ -611,9 +607,7 @@ public function specifyTypesInCondition( } } - if ($this->rememberFunctionValues) { - return $this->handleDefaultTruthyOrFalseyContext($context, $expr, $scope); - } + return $this->handleDefaultTruthyOrFalseyContext($context, $expr, $scope); } elseif ($expr instanceof MethodCall && $expr->name instanceof Node\Identifier) { $methodCalledOnType = $scope->getType($expr->var); $referencedClasses = TypeUtils::getDirectClassNames($methodCalledOnType); @@ -634,9 +628,7 @@ public function specifyTypesInCondition( } } - if ($this->rememberFunctionValues) { - return $this->handleDefaultTruthyOrFalseyContext($context, $expr, $scope); - } + return $this->handleDefaultTruthyOrFalseyContext($context, $expr, $scope); } elseif ($expr instanceof StaticCall && $expr->name instanceof Node\Identifier) { if ($expr->class instanceof Name) { $calleeType = $scope->resolveTypeByName($expr->class); @@ -662,9 +654,7 @@ public function specifyTypesInCondition( } } - if ($this->rememberFunctionValues) { - return $this->handleDefaultTruthyOrFalseyContext($context, $expr, $scope); - } + return $this->handleDefaultTruthyOrFalseyContext($context, $expr, $scope); } elseif ($expr instanceof BooleanAnd || $expr instanceof LogicalAnd) { $leftTypes = $this->specifyTypesInCondition($scope, $expr->left, $context); $rightTypes = $this->specifyTypesInCondition($scope, $expr->right, $context); diff --git a/src/Analyser/TypeSpecifierFactory.php b/src/Analyser/TypeSpecifierFactory.php index dc022b914d..1873493047 100644 --- a/src/Analyser/TypeSpecifierFactory.php +++ b/src/Analyser/TypeSpecifierFactory.php @@ -26,7 +26,6 @@ public function create(): TypeSpecifier $typeSpecifier = new TypeSpecifier( $this->container->getByType(Standard::class), $this->container->getByType(ReflectionProvider::class), - $this->container->getParameter('featureToggles')['rememberFunctionValues'], $this->container->getServicesByTag(self::FUNCTION_TYPE_SPECIFYING_EXTENSION_TAG), $this->container->getServicesByTag(self::METHOD_TYPE_SPECIFYING_EXTENSION_TAG), $this->container->getServicesByTag(self::STATIC_METHOD_TYPE_SPECIFYING_EXTENSION_TAG) From 764ad61cc787abd8af952e9c58ec1ae99e3e8f66 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 22 Sep 2021 19:06:20 +0200 Subject: [PATCH 0382/1284] Backward Compatibility Promise API rules --- build/phpstan.neon | 2 -- conf/bleedingEdge.neon | 1 - conf/config.level0.neon | 48 +++++++---------------------------------- conf/config.neon | 2 -- 4 files changed, 8 insertions(+), 45 deletions(-) diff --git a/build/phpstan.neon b/build/phpstan.neon index 5795712326..8d5e8fb692 100644 --- a/build/phpstan.neon +++ b/build/phpstan.neon @@ -65,8 +65,6 @@ parameters: check: missingCheckedExceptionInThrows: true tooWideThrowType: true - featureToggles: - apiRules: false ignoreErrors: - '#^Dynamic call to static method PHPUnit\\Framework\\\S+\(\)\.$#' - '#should be contravariant with parameter \$node \(PhpParser\\Node\) of method PHPStan\\Rules\\Rule::processNode\(\)$#' diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index 1c21853665..0ef88e1e00 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -2,4 +2,3 @@ parameters: featureToggles: bleedingEdge: true skipCheckGenericClasses: [] - apiRules: true diff --git a/conf/config.level0.neon b/conf/config.level0.neon index b625d43101..64ec96e8cc 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -2,26 +2,18 @@ parameters: customRulesetUsed: false conditionalTags: - PHPStan\Rules\Api\ApiInstantiationRule: - phpstan.rules.rule: %featureToggles.apiRules% - PHPStan\Rules\Api\ApiClassExtendsRule: - phpstan.rules.rule: %featureToggles.apiRules% - PHPStan\Rules\Api\ApiClassImplementsRule: - phpstan.rules.rule: %featureToggles.apiRules% - PHPStan\Rules\Api\ApiInterfaceExtendsRule: - phpstan.rules.rule: %featureToggles.apiRules% - PHPStan\Rules\Api\ApiMethodCallRule: - phpstan.rules.rule: %featureToggles.apiRules% - PHPStan\Rules\Api\ApiStaticCallRule: - phpstan.rules.rule: %featureToggles.apiRules% - PHPStan\Rules\Api\ApiTraitUseRule: - phpstan.rules.rule: %featureToggles.apiRules% - PHPStan\Rules\Api\PhpStanNamespaceIn3rdPartyPackageRule: - phpstan.rules.rule: %featureToggles.apiRules% PHPStan\Rules\Properties\UninitializedPropertyRule: phpstan.rules.rule: %checkUninitializedProperties% rules: + - PHPStan\Rules\Api\ApiInstantiationRule + - PHPStan\Rules\Api\ApiClassExtendsRule + - PHPStan\Rules\Api\ApiClassImplementsRule + - PHPStan\Rules\Api\ApiInterfaceExtendsRule + - PHPStan\Rules\Api\ApiMethodCallRule + - PHPStan\Rules\Api\ApiStaticCallRule + - PHPStan\Rules\Api\ApiTraitUseRule + - PHPStan\Rules\Api\PhpStanNamespaceIn3rdPartyPackageRule - PHPStan\Rules\Arrays\DuplicateKeysInLiteralArraysRule - PHPStan\Rules\Arrays\EmptyArrayItemRule - PHPStan\Rules\Arrays\OffsetAccessWithoutDimForReadingRule @@ -66,30 +58,6 @@ rules: - PHPStan\Rules\Whitespace\FileWhitespaceRule services: - - - class: PHPStan\Rules\Api\ApiInstantiationRule - - - - class: PHPStan\Rules\Api\ApiClassExtendsRule - - - - class: PHPStan\Rules\Api\ApiClassImplementsRule - - - - class: PHPStan\Rules\Api\ApiInterfaceExtendsRule - - - - class: PHPStan\Rules\Api\ApiMethodCallRule - - - - class: PHPStan\Rules\Api\ApiStaticCallRule - - - - class: PHPStan\Rules\Api\ApiTraitUseRule - - - - class: PHPStan\Rules\Api\PhpStanNamespaceIn3rdPartyPackageRule - - class: PHPStan\Rules\Classes\ExistingClassInClassExtendsRule tags: diff --git a/conf/config.neon b/conf/config.neon index c5a8865b2e..4e8f3a95cb 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -22,7 +22,6 @@ parameters: bleedingEdge: false disableRuntimeReflectionProvider: false skipCheckGenericClasses: [] - apiRules: false fileExtensions: - php checkAdvancedIsset: false @@ -179,7 +178,6 @@ parametersSchema: bleedingEdge: bool(), disableRuntimeReflectionProvider: bool(), skipCheckGenericClasses: listOf(string()), - apiRules: bool(), ]) fileExtensions: listOf(string()) checkAdvancedIsset: bool() From 7a2a4379d9c12d4452b0b8ff78bdef7159f01e8b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 22 Sep 2021 19:30:10 +0200 Subject: [PATCH 0383/1284] Fix --- tests/PHPStan/Analyser/AnalyserIntegrationTest.php | 7 ++++--- tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php | 8 -------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 4e522467da..3a88d32854 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -411,10 +411,11 @@ public function testBug4715(): void public function testBug4734(): void { $errors = $this->runAnalyse(__DIR__ . '/data/bug-4734.php'); - $this->assertCount(2, $errors); + $this->assertCount(3, $errors); - $this->assertSame('Access to an undefined static property static(Bug4734\Foo)::$httpMethodParameterOverride3.', $errors[0]->getMessage()); - $this->assertSame('Access to an undefined property Bug4734\Foo::$httpMethodParameterOverride4.', $errors[1]->getMessage()); + $this->assertSame('Unsafe access to private property Bug4734\Foo::$httpMethodParameterOverride through static::.', $errors[0]->getMessage()); + $this->assertSame('Access to an undefined static property static(Bug4734\Foo)::$httpMethodParameterOverride3.', $errors[1]->getMessage()); + $this->assertSame('Access to an undefined property Bug4734\Foo::$httpMethodParameterOverride4.', $errors[2]->getMessage()); } public function testBug5231(): void diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 463b8fff5b..6fea88f460 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -1996,14 +1996,6 @@ public function testGenericReturnTypeResolvedToNever(): void ]); } - public function testDoNotReportGenericReturnTypeResolvedToNever(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/generic-return-type-never.php'], []); - } - public function testUnableToResolveCallbackParameterType(): void { $this->checkThisOnly = false; From 5f91da66bd865487bc411903640e21acb55f86b1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 22 Sep 2021 21:56:26 +0200 Subject: [PATCH 0384/1284] Property assignment invalidates all remembered method values on an object --- src/Analyser/MutatingScope.php | 63 ++++++++++++++++++- .../Analyser/NodeScopeResolverTest.php | 1 + tests/PHPStan/Analyser/data/bug-5501.php | 61 ++++++++++++++++++ 3 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Analyser/data/bug-5501.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index feaa721167..be1d52b3f9 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -3736,7 +3736,10 @@ public function specifyExpressionType(Expr $expr, Type $type, ?Type $nativeType public function assignExpression(Expr $expr, Type $type): self { $scope = $this; - if ($expr instanceof PropertyFetch || $expr instanceof Expr\StaticPropertyFetch) { + if ($expr instanceof PropertyFetch) { + $scope = $this->invalidateExpression($expr) + ->invalidateMethodsOnExpression($expr->var); + } elseif ($expr instanceof Expr\StaticPropertyFetch) { $scope = $this->invalidateExpression($expr); } @@ -3806,6 +3809,64 @@ public function invalidateExpression(Expr $expressionToInvalidate, bool $require ); } + public function invalidateMethodsOnExpression(Expr $expressionToInvalidate): self + { + $exprStringToInvalidate = $this->getNodeKey($expressionToInvalidate); + $moreSpecificTypeHolders = $this->moreSpecificTypes; + $nativeExpressionTypes = $this->nativeExpressionTypes; + $invalidated = false; + $nodeFinder = new NodeFinder(); + foreach (array_keys($moreSpecificTypeHolders) as $exprString) { + $exprString = (string) $exprString; + + try { + $expr = $this->parser->parseString('findFirst([$expr->expr], function (Node $node) use ($exprStringToInvalidate): bool { + if (!$node instanceof MethodCall) { + return false; + } + + return $this->getNodeKey($node->var) === $exprStringToInvalidate; + }); + if ($found === null) { + continue; + } + + unset($moreSpecificTypeHolders[$exprString]); + unset($nativeExpressionTypes[$exprString]); + $invalidated = true; + } + + if (!$invalidated) { + return $this; + } + + return $this->scopeFactory->create( + $this->context, + $this->isDeclareStrictTypes(), + $this->constantTypes, + $this->getFunction(), + $this->getNamespace(), + $this->getVariableTypes(), + $moreSpecificTypeHolders, + $this->conditionalExpressions, + $this->inClosureBindScopeClass, + $this->anonymousFunctionReflection, + $this->inFirstLevelStatement, + $this->currentlyAssignedExpressions, + $nativeExpressionTypes, + [], + $this->afterExtractCall, + $this->parentScope + ); + } + public function removeTypeFromExpression(Expr $expr, Type $typeToRemove): self { $exprType = $this->getType($expr); diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index e441801ed3..826b433188 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -510,6 +510,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/eval-implicit-throw.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5628.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5501.php'); } /** diff --git a/tests/PHPStan/Analyser/data/bug-5501.php b/tests/PHPStan/Analyser/data/bug-5501.php new file mode 100644 index 0000000000..13f5261152 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-5501.php @@ -0,0 +1,61 @@ +prop2 = 5; + + if ($this->isBroken()){ + return; + } + + assertType('false', $this->isBroken()); + assertType('5', $this->prop2); + + $this->damage = min($this->damage + $amount, 5); + + assertType('bool', $this->isBroken()); + assertType('5', $this->prop2); + } + + public function applyDamage2(int $amount): void + { + $this->prop2 = 5; + + if ($this->isBroken()){ + return; + } + + assertType('false', $this->isBroken()); + assertType('5', $this->prop2); + + $this->array['foo'] = min($this->damage + $amount, 5); + + assertType('bool', $this->isBroken()); + assertType('5', $this->prop2); + } + + protected function onBroken(): void + { + + } + + public function isBroken(): bool{ + return $this->damage >= 5; + } +} From 6754df565a75125a800b04892e467d8c0868ab41 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 22 Sep 2021 22:14:09 +0200 Subject: [PATCH 0385/1284] Fix array_splice bugs --- conf/config.neon | 5 ++ src/Analyser/NodeScopeResolver.php | 17 ++++++ ...ArraySpliceFunctionReturnTypeExtension.php | 35 ++++++++++++ .../Analyser/NodeScopeResolverTest.php | 2 + tests/PHPStan/Analyser/data/bug-4743.php | 34 +++++++++++ tests/PHPStan/Analyser/data/bug-5017.php | 56 +++++++++++++++++++ 6 files changed, 149 insertions(+) create mode 100644 src/Type/Php/ArraySpliceFunctionReturnTypeExtension.php create mode 100644 tests/PHPStan/Analyser/data/bug-4743.php create mode 100644 tests/PHPStan/Analyser/data/bug-5017.php diff --git a/conf/config.neon b/conf/config.neon index 4e8f3a95cb..f74919aafc 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -948,6 +948,11 @@ services: tags: - phpstan.broker.dynamicFunctionReturnTypeExtension + - + class: PHPStan\Type\Php\ArraySpliceFunctionReturnTypeExtension + tags: + - phpstan.broker.dynamicFunctionReturnTypeExtension + - class: PHPStan\Type\Php\ArraySearchFunctionDynamicReturnTypeExtension tags: diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 07fbc7590d..5e3e16c307 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -1934,6 +1934,23 @@ function (MutatingScope $scope) use ($expr, $nodeCallback, $context): Expression $scope = $scope->assignVariable('http_response_header', new ArrayType(new IntegerType(), new StringType())); } + if ( + isset($functionReflection) + && $functionReflection->getName() === 'array_splice' + && count($expr->getArgs()) >= 1 + ) { + $arrayArg = $expr->getArgs()[0]->value; + $arrayArgType = $scope->getType($arrayArg); + $valueType = $arrayArgType->getIterableValueType(); + if (count($expr->getArgs()) >= 4) { + $valueType = TypeCombinator::union($valueType, $scope->getType($expr->getArgs()[3]->value)->getIterableValueType()); + } + $scope = $scope->invalidateExpression($arrayArg)->specifyExpressionType( + $arrayArg, + new ArrayType($arrayArgType->getIterableKeyType(), $valueType) + ); + } + if (isset($functionReflection) && $functionReflection->getName() === 'extract') { $scope = $scope->afterExtractCall(); } diff --git a/src/Type/Php/ArraySpliceFunctionReturnTypeExtension.php b/src/Type/Php/ArraySpliceFunctionReturnTypeExtension.php new file mode 100644 index 0000000000..7024a7d64e --- /dev/null +++ b/src/Type/Php/ArraySpliceFunctionReturnTypeExtension.php @@ -0,0 +1,35 @@ +getName() === 'array_splice'; + } + + public function getTypeFromFunctionCall( + FunctionReflection $functionReflection, + FuncCall $functionCall, + Scope $scope + ): Type + { + if (!isset($functionCall->getArgs()[0])) { + return ParametersAcceptorSelector::selectFromArgs($scope, $functionCall->getArgs(), $functionReflection->getVariants())->getReturnType(); + } + + $arrayArg = $scope->getType($functionCall->getArgs()[0]->value); + + return new ArrayType($arrayArg->getIterableKeyType(), $arrayArg->getIterableValueType()); + } + +} diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 826b433188..8021fa2ccd 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -511,6 +511,8 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/eval-implicit-throw.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5628.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5501.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4743.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5017.php'); } /** diff --git a/tests/PHPStan/Analyser/data/bug-4743.php b/tests/PHPStan/Analyser/data/bug-4743.php new file mode 100644 index 0000000000..a5aeab2223 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-4743.php @@ -0,0 +1,34 @@ + + */ + private $nodes; + + /** + * @phpstan-param array $nodes + */ + public function __construct(array $nodes) + { + $this->nodes = $nodes; + } + + public function splice(int $offset, int $length): void + { + $newNodes = array_splice($this->nodes, $offset, $length); + + assertType('array', $this->nodes); + assertType('array', $newNodes); + } +} diff --git a/tests/PHPStan/Analyser/data/bug-5017.php b/tests/PHPStan/Analyser/data/bug-5017.php new file mode 100644 index 0000000000..94ac0efcf1 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-5017.php @@ -0,0 +1,56 @@ +&nonEmpty', $items); + $batch = array_splice($items, 0, 2); + assertType('array<0|1|2|3|4, 0|1|2|3|4>', $items); + assertType('array<0|1|2|3|4, 0|1|2|3|4>', $batch); + } + } + + /** + * @param int[] $items + */ + public function doBar($items) + { + while ($items) { + assertType('array&nonEmpty', $items); + $batch = array_splice($items, 0, 2); + assertType('array', $items); + assertType('array', $batch); + } + } + + public function doBar2() + { + $items = [0, 1, 2, 3, 4]; + assertType('array(0, 1, 2, 3, 4)', $items); + $batch = array_splice($items, 0, 2); + assertType('array<0|1|2|3|4, 0|1|2|3|4>', $items); + assertType('array<0|1|2|3|4, 0|1|2|3|4>', $batch); + } + + /** + * @param int[] $ints + * @param string[] $strings + */ + public function doBar3(array $ints, array $strings) + { + $removed = array_splice($ints, 0, 2, $strings); + assertType('array', $removed); + assertType('array', $ints); + assertType('array', $strings); + } + +} From a321a3e23f039afa67b283b5e4dd06d494d9efba Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 22 Sep 2021 23:02:21 +0200 Subject: [PATCH 0386/1284] Regression test Closes https://github.com/phpstan/phpstan/issues/2760 --- .../Analyser/NodeScopeResolverTest.php | 1 + tests/PHPStan/Analyser/data/bug-2760.php | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 tests/PHPStan/Analyser/data/bug-2760.php diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 8021fa2ccd..b095b3db2a 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -513,6 +513,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5501.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4743.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5017.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2760.php'); } /** diff --git a/tests/PHPStan/Analyser/data/bug-2760.php b/tests/PHPStan/Analyser/data/bug-2760.php new file mode 100644 index 0000000000..72e2e6a516 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-2760.php @@ -0,0 +1,24 @@ + Date: Thu, 23 Sep 2021 10:07:14 +0200 Subject: [PATCH 0387/1284] [BCB] Changed return types of various methods that had |false to |null --- composer.lock | 8 +-- src/Analyser/FileAnalyser.php | 2 +- src/Analyser/MutatingScope.php | 8 +-- src/Analyser/NodeScopeResolver.php | 2 +- .../ResultCache/ResultCacheManager.php | 2 +- src/Analyser/TypeSpecifier.php | 2 +- src/Dependency/DependencyResolver.php | 2 +- src/Dependency/NodeDependencies.php | 2 +- src/PhpDoc/PhpDocBlock.php | 2 +- src/PhpDoc/TypeNodeResolver.php | 4 +- .../BetterReflectionProvider.php | 7 ++- src/Reflection/ClassConstantReflection.php | 7 +-- src/Reflection/ClassReflection.php | 54 ++++++++----------- src/Reflection/FunctionReflectionFactory.php | 4 +- .../Php/BuiltinMethodReflection.php | 15 ++---- .../Php/FakeBuiltinMethodReflection.php | 21 +++----- .../Php/NativeBuiltinMethodReflection.php | 36 +++++++------ .../Php/PhpClassReflectionExtension.php | 12 ++--- src/Reflection/Php/PhpFunctionReflection.php | 18 +++---- src/Reflection/Php/PhpMethodReflection.php | 2 +- src/Reflection/ReflectionWithFilename.php | 5 +- .../Runtime/RuntimeReflectionProvider.php | 2 +- src/Rules/Classes/ClassConstantRule.php | 2 +- src/Rules/Classes/InstantiationRule.php | 2 +- .../Constants/OverridingConstantRule.php | 2 +- .../Generics/CrossCheckInterfacesHelper.php | 4 +- src/Rules/Methods/CallMethodsRule.php | 2 +- src/Rules/Methods/CallStaticMethodsRule.php | 2 +- src/Rules/Methods/MethodSignatureRule.php | 2 +- src/Rules/Methods/OverridingMethodRule.php | 2 +- src/Rules/Properties/AccessPropertiesRule.php | 2 +- .../Properties/AccessStaticPropertiesRule.php | 2 +- .../Properties/OverridingPropertyRule.php | 2 +- src/Type/FileTypeMapper.php | 2 +- src/Type/ObjectType.php | 4 +- src/Type/ParserNodeTypeToPHPStanType.php | 2 +- ...lassDynamicFunctionReturnTypeExtension.php | 2 +- src/Type/TypehintHelper.php | 2 +- .../Reflection/ClassReflectionTest.php | 2 +- 39 files changed, 110 insertions(+), 145 deletions(-) diff --git a/composer.lock b/composer.lock index b908faaf7c..2c36f66299 100644 --- a/composer.lock +++ b/composer.lock @@ -4474,12 +4474,12 @@ "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "6c7a9815bb3af49113a6f85d073e3e2fc01f7ffa" + "reference": "a9b2cb1ee2668ac3a66b5f76f37122e64299c2ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/6c7a9815bb3af49113a6f85d073e3e2fc01f7ffa", - "reference": "6c7a9815bb3af49113a6f85d073e3e2fc01f7ffa", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/a9b2cb1ee2668ac3a66b5f76f37122e64299c2ca", + "reference": "a9b2cb1ee2668ac3a66b5f76f37122e64299c2ca", "shasum": "" }, "require": { @@ -4518,7 +4518,7 @@ "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", "source": "https://github.com/phpstan/phpstan-strict-rules/tree/master" }, - "time": "2021-09-22T16:41:47+00:00" + "time": "2021-09-23T08:29:12+00:00" }, { "name": "phpunit/php-code-coverage", diff --git a/src/Analyser/FileAnalyser.php b/src/Analyser/FileAnalyser.php index af21ba3f71..c13f5429b3 100644 --- a/src/Analyser/FileAnalyser.php +++ b/src/Analyser/FileAnalyser.php @@ -108,7 +108,7 @@ public function analyseFile( $metadata = []; if ($scope->isInTrait()) { $traitReflection = $scope->getTraitReflection(); - if ($traitReflection->getFileName() !== false) { + if ($traitReflection->getFileName() !== null) { $traitFilePath = $traitReflection->getFileName(); } } diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index be1d52b3f9..395b8126dd 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -292,7 +292,7 @@ public function getFileDescription(): string } $traitReflection = $this->context->getTraitReflection(); - if ($traitReflection->getFileName() === false) { + if ($traitReflection->getFileName() === null) { throw new \PHPStan\ShouldNotHappenException(); } @@ -2588,7 +2588,7 @@ private function resolveExactName(Name $name): ?string return null; } $currentClassReflection = $this->getClassReflection(); - if ($currentClassReflection->getParentClass() !== false) { + if ($currentClassReflection->getParentClass() !== null) { return $currentClassReflection->getParentClass()->getName(); } return null; @@ -2614,7 +2614,7 @@ public function resolveName(Name $name): string return $this->getClassReflection()->getName(); } elseif ($originalClass === 'parent') { $currentClassReflection = $this->getClassReflection(); - if ($currentClassReflection->getParentClass() !== false) { + if ($currentClassReflection->getParentClass() !== null) { return $currentClassReflection->getParentClass()->getName(); } } @@ -3385,7 +3385,7 @@ public function getFunctionType($type, bool $isNullable, bool $isVariadic): Type $className = (string) $type; $lowercasedClassName = strtolower($className); if ($lowercasedClassName === 'parent') { - if ($this->isInClass() && $this->getClassReflection()->getParentClass() !== false) { + if ($this->isInClass() && $this->getClassReflection()->getParentClass() !== null) { return new ObjectType($this->getClassReflection()->getParentClass()->getName()); } diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 5e3e16c307..cbde3f80ee 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -3663,7 +3663,7 @@ private function processTraitUse(Node\Stmt\TraitUse $node, MutatingScope $classS } $traitReflection = $this->reflectionProvider->getClass($traitName); $traitFileName = $traitReflection->getFileName(); - if ($traitFileName === false) { + if ($traitFileName === null) { continue; // trait from eval or from PHP itself } $fileName = $this->fileHelper->normalizePath($traitFileName); diff --git a/src/Analyser/ResultCache/ResultCacheManager.php b/src/Analyser/ResultCache/ResultCacheManager.php index 71ae46d78c..46b2205628 100644 --- a/src/Analyser/ResultCache/ResultCacheManager.php +++ b/src/Analyser/ResultCache/ResultCacheManager.php @@ -633,7 +633,7 @@ private function getProjectExtensionFiles(?array $projectConfig, array $dependen $classReflection = $this->reflectionProvider->getClass($class); $fileName = $classReflection->getFileName(); - if ($fileName === false) { + if ($fileName === null) { continue; } diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index fbf31d29f4..5a6453374b 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -123,7 +123,7 @@ public function specifyTypesInCondition( } elseif ($lowercasedClassName === 'parent') { if ( $scope->isInClass() - && $scope->getClassReflection()->getParentClass() !== false + && $scope->getClassReflection()->getParentClass() !== null ) { $type = new ObjectType($scope->getClassReflection()->getParentClass()->getName()); } else { diff --git a/src/Dependency/DependencyResolver.php b/src/Dependency/DependencyResolver.php index 3580e529cd..5507705a72 100644 --- a/src/Dependency/DependencyResolver.php +++ b/src/Dependency/DependencyResolver.php @@ -230,7 +230,7 @@ private function addClassToDependencies(string $className, array &$dependenciesR } $classReflection = $classReflection->getParentClass(); - } while ($classReflection !== false); + } while ($classReflection !== null); } private function getFunctionReflection(\PhpParser\Node\Name $nameNode, ?Scope $scope): ReflectionWithFilename diff --git a/src/Dependency/NodeDependencies.php b/src/Dependency/NodeDependencies.php index cd75370e7d..b6fcb0ce51 100644 --- a/src/Dependency/NodeDependencies.php +++ b/src/Dependency/NodeDependencies.php @@ -41,7 +41,7 @@ public function getFileDependencies(string $currentFile, array $analysedFiles): foreach ($this->reflections as $dependencyReflection) { $dependencyFile = $dependencyReflection->getFileName(); - if ($dependencyFile === false) { + if ($dependencyFile === null) { continue; } $dependencyFile = $this->fileHelper->normalizePath($dependencyFile); diff --git a/src/PhpDoc/PhpDocBlock.php b/src/PhpDoc/PhpDocBlock.php index 92fa123c71..3127628d9f 100644 --- a/src/PhpDoc/PhpDocBlock.php +++ b/src/PhpDoc/PhpDocBlock.php @@ -341,7 +341,7 @@ private static function getParentReflections(ClassReflection $classReflection): $result = []; $parent = $classReflection->getParentClass(); - if ($parent !== false) { + if ($parent !== null) { $result[] = $parent; } diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index 10c701a90a..39ac549568 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -310,7 +310,7 @@ private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameSco case 'parent': if ($this->getReflectionProvider()->hasClass($nameScope->getClassName())) { $classReflection = $this->getReflectionProvider()->getClass($nameScope->getClassName()); - if ($classReflection->getParentClass() !== false) { + if ($classReflection->getParentClass() !== null) { return new ObjectType($classReflection->getParentClass()->getName()); } } @@ -681,7 +681,7 @@ private function resolveConstTypeNode(ConstTypeNode $typeNode, NameScope $nameSc case 'parent': if ($this->getReflectionProvider()->hasClass($nameScope->getClassName())) { $classReflection = $this->getReflectionProvider()->getClass($nameScope->getClassName()); - if ($classReflection->getParentClass() === false) { + if ($classReflection->getParentClass() === null) { return new ErrorType(); } diff --git a/src/Reflection/BetterReflection/BetterReflectionProvider.php b/src/Reflection/BetterReflection/BetterReflectionProvider.php index 850eb36364..f26041e01a 100644 --- a/src/Reflection/BetterReflection/BetterReflectionProvider.php +++ b/src/Reflection/BetterReflection/BetterReflectionProvider.php @@ -210,7 +210,7 @@ public function getAnonymousClassReflection(\PhpParser\Node\Stmt\Class_ $classNo $scopeFile = $scope->getFile(); } else { $scopeFile = $scope->getTraitReflection()->getFileName(); - if ($scopeFile === false) { + if ($scopeFile === null) { $scopeFile = $scope->getFile(); } } @@ -297,9 +297,8 @@ private function getCustomFunction(string $functionName): \PHPStan\Reflection\Ph return $parameter->getName(); }, $reflectionFunction->getParameters())); if ($resolvedPhpDoc === null && $reflectionFunction->getFileName() !== false && $reflectionFunction->getDocComment() !== false) { - $fileName = $reflectionFunction->getFileName(); $docComment = $reflectionFunction->getDocComment(); - $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc($fileName, null, null, $reflectionFunction->getName(), $docComment); + $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc($reflectionFunction->getFileName(), null, null, $reflectionFunction->getName(), $docComment); } if ($resolvedPhpDoc !== null) { @@ -326,7 +325,7 @@ private function getCustomFunction(string $functionName): \PHPStan\Reflection\Ph $isDeprecated, $isInternal, $isFinal, - $reflectionFunction->getFileName(), + $reflectionFunction->getFileName() !== false ? $reflectionFunction->getFileName() : null, $isPure ); } diff --git a/src/Reflection/ClassConstantReflection.php b/src/Reflection/ClassConstantReflection.php index 88f87bf8f7..a89d59d7cc 100644 --- a/src/Reflection/ClassConstantReflection.php +++ b/src/Reflection/ClassConstantReflection.php @@ -53,12 +53,7 @@ public function getName(): string public function getFileName(): ?string { - $fileName = $this->declaringClass->getFileName(); - if ($fileName === false) { - return null; - } - - return $fileName; + return $this->declaringClass->getFileName(); } /** diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index eee291e8a7..80b1cb4c09 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -97,16 +97,16 @@ class ClassReflection implements ReflectionWithFilename private array $subclasses = []; /** @var string|false|null */ - private $filename; + private $filename = false; /** @var string|false|null */ - private $reflectionDocComment; + private $reflectionDocComment = false; /** @var \PHPStan\Reflection\ClassReflection[]|null */ private ?array $cachedInterfaces = null; /** @var \PHPStan\Reflection\ClassReflection|false|null */ - private $cachedParentClass = null; + private $cachedParentClass = false; /** @var array|null */ private ?array $typeAliases = null; @@ -161,12 +161,9 @@ public function getNativeReflection(): \ReflectionClass return $this->reflection; } - /** - * @return string|false - */ - public function getFileName() + public function getFileName(): ?string { - if (isset($this->filename)) { + if ($this->filename !== false) { return $this->filename; } @@ -175,11 +172,11 @@ public function getFileName() } $fileName = $this->reflection->getFileName(); if ($fileName === false) { - return $this->filename = false; + return $this->filename = null; } if (!file_exists($fileName)) { - return $this->filename = false; + return $this->filename = null; } return $this->filename = $fileName; @@ -191,27 +188,19 @@ public function getFileNameWithPhpDocs(): ?string return $this->stubPhpDocBlock->getFilename(); } - $filename = $this->getFileName(); - if ($filename === false) { - return null; - } - - return $filename; + return $this->getFileName(); } - /** - * @return false|\PHPStan\Reflection\ClassReflection - */ - public function getParentClass() + public function getParentClass(): ?ClassReflection { - if ($this->cachedParentClass !== null) { + if ($this->cachedParentClass !== false) { return $this->cachedParentClass; } $parentClass = $this->reflection->getParentClass(); if ($parentClass === false) { - return $this->cachedParentClass = false; + return $this->cachedParentClass = null; } $extendsTag = $this->getFirstExtendsTag(); @@ -575,7 +564,7 @@ public function getParents(): array { $parents = []; $parent = $this->getParentClass(); - while ($parent !== false) { + while ($parent !== null) { $parents[] = $parent; $parent = $parent->getParentClass(); } @@ -595,7 +584,7 @@ public function getInterfaces(): array $interfaces = $this->getImmediateInterfaces(); $immediateInterfaces = $interfaces; $parent = $this->getParentClass(); - while ($parent !== false) { + while ($parent !== null) { foreach ($parent->getImmediateInterfaces() as $parentInterface) { $interfaces[$parentInterface->getName()] = $parentInterface; foreach ($this->collectInterfaces($parentInterface) as $parentInterfaceInterface) { @@ -640,7 +629,7 @@ public function getImmediateInterfaces(): array { $indirectInterfaceNames = []; $parent = $this->getParentClass(); - while ($parent !== false) { + while ($parent !== null) { foreach ($parent->getNativeReflection()->getInterfaceNames() as $parentInterfaceName) { $indirectInterfaceNames[] = $parentInterfaceName; } @@ -740,7 +729,7 @@ public function getParentClassesNames(): array { $parentNames = []; $currentClassReflection = $this; - while ($currentClassReflection->getParentClass() !== false) { + while ($currentClassReflection->getParentClass() !== null) { $parentNames[] = $currentClassReflection->getParentClass()->getName(); $currentClassReflection = $currentClassReflection->getParentClass(); } @@ -780,7 +769,7 @@ public function getConstant(string $name): ConstantReflection $declaringClass->getName(), $name ); - if ($resolvedPhpDoc === null && $fileName !== false) { + if ($resolvedPhpDoc === null && $fileName !== null) { $docComment = null; if ($reflectionConstant->getDocComment() !== false) { $docComment = $reflectionConstant->getDocComment(); @@ -1097,15 +1086,16 @@ public function getResolvedPhpDoc(): ?ResolvedPhpDocBlock } $fileName = $this->getFileName(); - if ($fileName === false) { + if ($fileName === null) { return null; } - if ($this->reflectionDocComment === null) { - $this->reflectionDocComment = $this->reflection->getDocComment(); + if ($this->reflectionDocComment === false) { + $docComment = $this->reflection->getDocComment(); + $this->reflectionDocComment = $docComment !== false ? $docComment : null; } - if ($this->reflectionDocComment === false) { + if ($this->reflectionDocComment === null) { return null; } @@ -1189,7 +1179,7 @@ public function getAncestors(): array } $parent = $this->getParentClass(); - if ($parent !== false) { + if ($parent !== null) { $addToAncestors($parent->getName(), $parent); foreach ($parent->getAncestors() as $name => $ancestor) { $addToAncestors($name, $ancestor); diff --git a/src/Reflection/FunctionReflectionFactory.php b/src/Reflection/FunctionReflectionFactory.php index caf4d05e72..dccd3a19b0 100644 --- a/src/Reflection/FunctionReflectionFactory.php +++ b/src/Reflection/FunctionReflectionFactory.php @@ -19,7 +19,7 @@ interface FunctionReflectionFactory * @param bool $isDeprecated * @param bool $isInternal * @param bool $isFinal - * @param string|false $filename + * @param string|null $filename * @param bool|null $isPure * @return PhpFunctionReflection */ @@ -33,7 +33,7 @@ public function create( bool $isDeprecated, bool $isInternal, bool $isFinal, - $filename, + ?string $filename, ?bool $isPure = null ): PhpFunctionReflection; diff --git a/src/Reflection/Php/BuiltinMethodReflection.php b/src/Reflection/Php/BuiltinMethodReflection.php index efc2f4e0b4..266d002075 100644 --- a/src/Reflection/Php/BuiltinMethodReflection.php +++ b/src/Reflection/Php/BuiltinMethodReflection.php @@ -11,22 +11,13 @@ public function getName(): string; public function getReflection(): ?\ReflectionMethod; - /** - * @return string|false - */ - public function getFileName(); + public function getFileName(): ?string; public function getDeclaringClass(): \ReflectionClass; - /** - * @return int|false - */ - public function getStartLine(); + public function getStartLine(): ?int; - /** - * @return int|false - */ - public function getEndLine(); + public function getEndLine(): ?int; public function getDocComment(): ?string; diff --git a/src/Reflection/Php/FakeBuiltinMethodReflection.php b/src/Reflection/Php/FakeBuiltinMethodReflection.php index a1737af26c..cfb2d1b831 100644 --- a/src/Reflection/Php/FakeBuiltinMethodReflection.php +++ b/src/Reflection/Php/FakeBuiltinMethodReflection.php @@ -30,12 +30,9 @@ public function getReflection(): ?\ReflectionMethod return null; } - /** - * @return string|false - */ - public function getFileName() + public function getFileName(): ?string { - return false; + return null; } public function getDeclaringClass(): \ReflectionClass @@ -43,20 +40,14 @@ public function getDeclaringClass(): \ReflectionClass return $this->declaringClass; } - /** - * @return int|false - */ - public function getStartLine() + public function getStartLine(): ?int { - return false; + return null; } - /** - * @return int|false - */ - public function getEndLine() + public function getEndLine(): ?int { - return false; + return null; } public function getDocComment(): ?string diff --git a/src/Reflection/Php/NativeBuiltinMethodReflection.php b/src/Reflection/Php/NativeBuiltinMethodReflection.php index 8a59bb0237..692c06a010 100644 --- a/src/Reflection/Php/NativeBuiltinMethodReflection.php +++ b/src/Reflection/Php/NativeBuiltinMethodReflection.php @@ -24,12 +24,14 @@ public function getReflection(): ?\ReflectionMethod return $this->reflection; } - /** - * @return string|false - */ - public function getFileName() + public function getFileName(): ?string { - return $this->reflection->getFileName(); + $fileName = $this->reflection->getFileName(); + if ($fileName === false) { + return null; + } + + return $fileName; } public function getDeclaringClass(): \ReflectionClass @@ -37,20 +39,24 @@ public function getDeclaringClass(): \ReflectionClass return $this->reflection->getDeclaringClass(); } - /** - * @return int|false - */ - public function getStartLine() + public function getStartLine(): ?int { - return $this->reflection->getStartLine(); + $line = $this->reflection->getStartLine(); + if ($line === false) { + return null; + } + + return $line; } - /** - * @return int|false - */ - public function getEndLine() + public function getEndLine(): ?int { - return $this->reflection->getEndLine(); + $line = $this->reflection->getEndLine(); + if ($line === false) { + return null; + } + + return $line; } public function getDocComment(): ?string diff --git a/src/Reflection/Php/PhpClassReflectionExtension.php b/src/Reflection/Php/PhpClassReflectionExtension.php index 223476e4c6..0692f245c4 100644 --- a/src/Reflection/Php/PhpClassReflectionExtension.php +++ b/src/Reflection/Php/PhpClassReflectionExtension.php @@ -219,7 +219,7 @@ private function createProperty( ); $stubPhpDocString = null; if ($resolvedPhpDoc === null) { - if ($declaringClassReflection->getFileName() !== false) { + if ($declaringClassReflection->getFileName() !== null) { $declaringTraitName = $this->findPropertyTrait($propertyReflection); $constructorName = null; if (method_exists($propertyReflection, 'isPromoted') && $propertyReflection->isPromoted()) { @@ -262,7 +262,7 @@ private function createProperty( } if ($phpDocType === null) { - if (isset($constructorName) && $declaringClassReflection->getFileName() !== false) { + if (isset($constructorName) && $declaringClassReflection->getFileName() !== null) { $constructorDocComment = $declaringClassReflection->getConstructor()->getDocComment(); $nativeClassReflection = $declaringClassReflection->getNativeReflection(); $positionalParameterNames = []; @@ -302,7 +302,7 @@ private function createProperty( if ( $phpDocType === null && $this->inferPrivatePropertyTypeFromConstructor - && $declaringClassReflection->getFileName() !== false + && $declaringClassReflection->getFileName() !== null && $propertyReflection->isPrivate() && (!method_exists($propertyReflection, 'hasType') || !$propertyReflection->hasType()) && $declaringClassReflection->hasConstructor() @@ -578,7 +578,7 @@ private function createMethod( $stubPhpDocString = null; if ($resolvedPhpDoc === null) { - if ($declaringClass->getFileName() !== false) { + if ($declaringClass->getFileName() !== null) { $docComment = $methodReflection->getDocComment(); $positionalParameterNames = array_map(static function (\ReflectionParameter $parameter): string { return $parameter->getName(); @@ -618,7 +618,7 @@ private function createMethod( if ( $methodReflection instanceof NativeBuiltinMethodReflection && $methodReflection->isConstructor() - && $declaringClass->getFileName() !== false + && $declaringClass->getFileName() !== null ) { foreach ($methodReflection->getParameters() as $parameter) { if (!method_exists($parameter, 'isPromoted') || !$parameter->isPromoted()) { @@ -923,7 +923,7 @@ private function inferAndCachePropertyTypes( if (isset($this->propertyTypesCache[$declaringClass->getName()])) { return $this->propertyTypesCache[$declaringClass->getName()]; } - if ($declaringClass->getFileName() === false) { + if ($declaringClass->getFileName() === null) { return $this->propertyTypesCache[$declaringClass->getName()] = []; } diff --git a/src/Reflection/Php/PhpFunctionReflection.php b/src/Reflection/Php/PhpFunctionReflection.php index f16327cfd5..3193522627 100644 --- a/src/Reflection/Php/PhpFunctionReflection.php +++ b/src/Reflection/Php/PhpFunctionReflection.php @@ -49,8 +49,7 @@ class PhpFunctionReflection implements FunctionReflection, ReflectionWithFilenam private bool $isFinal; - /** @var string|false */ - private $filename; + private ?string $filename; private ?bool $isPure; @@ -70,7 +69,7 @@ class PhpFunctionReflection implements FunctionReflection, ReflectionWithFilenam * @param bool $isDeprecated * @param bool $isInternal * @param bool $isFinal - * @param string|false $filename + * @param string|null $filename * @param bool|null $isPure */ public function __construct( @@ -86,7 +85,7 @@ public function __construct( bool $isDeprecated, bool $isInternal, bool $isFinal, - $filename, + ?string $filename, ?bool $isPure = null ) { @@ -111,17 +110,14 @@ public function getName(): string return $this->reflection->getName(); } - /** - * @return string|false - */ - public function getFileName() + public function getFileName(): ?string { - if ($this->filename === false) { - return false; + if ($this->filename === null) { + return null; } if (!file_exists($this->filename)) { - return false; + return null; } return $this->filename; diff --git a/src/Reflection/Php/PhpMethodReflection.php b/src/Reflection/Php/PhpMethodReflection.php index c6fab4535e..50cbd8d81a 100644 --- a/src/Reflection/Php/PhpMethodReflection.php +++ b/src/Reflection/Php/PhpMethodReflection.php @@ -268,7 +268,7 @@ private function isVariadic(): bool $filename = $this->declaringTrait->getFileName(); } - if (!$isNativelyVariadic && $filename !== false && file_exists($filename)) { + if (!$isNativelyVariadic && $filename !== null && file_exists($filename)) { $modifiedTime = filemtime($filename); if ($modifiedTime === false) { $modifiedTime = time(); diff --git a/src/Reflection/ReflectionWithFilename.php b/src/Reflection/ReflectionWithFilename.php index 5bedac9600..c626dbb7cf 100644 --- a/src/Reflection/ReflectionWithFilename.php +++ b/src/Reflection/ReflectionWithFilename.php @@ -6,9 +6,6 @@ interface ReflectionWithFilename { - /** - * @return string|false - */ - public function getFileName(); + public function getFileName(): ?string; } diff --git a/src/Reflection/Runtime/RuntimeReflectionProvider.php b/src/Reflection/Runtime/RuntimeReflectionProvider.php index 9e5a5bc98a..daf6fa24f9 100644 --- a/src/Reflection/Runtime/RuntimeReflectionProvider.php +++ b/src/Reflection/Runtime/RuntimeReflectionProvider.php @@ -308,7 +308,7 @@ private function getCustomFunction(\PhpParser\Node\Name $nameNode, ?Scope $scope $isDeprecated, $isInternal, $isFinal, - $reflectionFunction->getFileName(), + $reflectionFunction->getFileName() !== false ? $reflectionFunction->getFileName() : null, $isPure ); $this->customFunctionReflections[$lowerCasedFunctionName] = $functionReflection; diff --git a/src/Rules/Classes/ClassConstantRule.php b/src/Rules/Classes/ClassConstantRule.php index 4ab9d2e083..58d5b8e65e 100644 --- a/src/Rules/Classes/ClassConstantRule.php +++ b/src/Rules/Classes/ClassConstantRule.php @@ -78,7 +78,7 @@ public function processNode(Node $node, Scope $scope): array ]; } $currentClassReflection = $scope->getClassReflection(); - if ($currentClassReflection->getParentClass() === false) { + if ($currentClassReflection->getParentClass() === null) { return [ RuleErrorBuilder::message(sprintf( 'Access to parent::%s but %s does not extend any class.', diff --git a/src/Rules/Classes/InstantiationRule.php b/src/Rules/Classes/InstantiationRule.php index 71d4b62949..94237c0b6d 100644 --- a/src/Rules/Classes/InstantiationRule.php +++ b/src/Rules/Classes/InstantiationRule.php @@ -103,7 +103,7 @@ private function checkClassName(string $class, bool $isName, Node $node, Scope $ RuleErrorBuilder::message(sprintf('Using %s outside of class scope.', $class))->build(), ]; } - if ($scope->getClassReflection()->getParentClass() === false) { + if ($scope->getClassReflection()->getParentClass() === null) { return [ RuleErrorBuilder::message(sprintf( '%s::%s() calls new parent but %s does not extend any class.', diff --git a/src/Rules/Constants/OverridingConstantRule.php b/src/Rules/Constants/OverridingConstantRule.php index 336d42d69c..34f9bf424e 100644 --- a/src/Rules/Constants/OverridingConstantRule.php +++ b/src/Rules/Constants/OverridingConstantRule.php @@ -131,7 +131,7 @@ private function findPrototype(ClassReflection $classReflection, string $constan } $parentClass = $classReflection->getParentClass(); - if ($parentClass === false) { + if ($parentClass === null) { return null; } diff --git a/src/Rules/Generics/CrossCheckInterfacesHelper.php b/src/Rules/Generics/CrossCheckInterfacesHelper.php index 4bc11186c4..65f5e8d86f 100644 --- a/src/Rules/Generics/CrossCheckInterfacesHelper.php +++ b/src/Rules/Generics/CrossCheckInterfacesHelper.php @@ -52,7 +52,7 @@ public function check(ClassReflection $classReflection): array $parent = $classReflection->getParentClass(); $checkParents = true; - if ($first && $parent !== false) { + if ($first && $parent !== null) { $extendsTags = $classReflection->getExtendsTags(); if (!array_key_exists($parent->getName(), $extendsTags)) { $checkParents = false; @@ -60,7 +60,7 @@ public function check(ClassReflection $classReflection): array } if ($checkParents) { - while ($parent !== false) { + while ($parent !== null) { $check($parent, false); $parent = $parent->getParentClass(); } diff --git a/src/Rules/Methods/CallMethodsRule.php b/src/Rules/Methods/CallMethodsRule.php index 8007179b40..d04d600986 100644 --- a/src/Rules/Methods/CallMethodsRule.php +++ b/src/Rules/Methods/CallMethodsRule.php @@ -99,7 +99,7 @@ static function (Type $type) use ($name): bool { $referencedClass = $directClassNames[0]; $methodClassReflection = $this->reflectionProvider->getClass($referencedClass); $parentClassReflection = $methodClassReflection->getParentClass(); - while ($parentClassReflection !== false) { + while ($parentClassReflection !== null) { if ($parentClassReflection->hasMethod($name)) { return [ RuleErrorBuilder::message(sprintf( diff --git a/src/Rules/Methods/CallStaticMethodsRule.php b/src/Rules/Methods/CallStaticMethodsRule.php index cdef6c8fdf..87fda9718c 100644 --- a/src/Rules/Methods/CallStaticMethodsRule.php +++ b/src/Rules/Methods/CallStaticMethodsRule.php @@ -103,7 +103,7 @@ public function processNode(Node $node, Scope $scope): array ]; } $currentClassReflection = $scope->getClassReflection(); - if ($currentClassReflection->getParentClass() === false) { + if ($currentClassReflection->getParentClass() === null) { return [ RuleErrorBuilder::message(sprintf( '%s::%s() calls parent::%s() but %s does not extend any class.', diff --git a/src/Rules/Methods/MethodSignatureRule.php b/src/Rules/Methods/MethodSignatureRule.php index 2599c46ea2..080aa2d58d 100644 --- a/src/Rules/Methods/MethodSignatureRule.php +++ b/src/Rules/Methods/MethodSignatureRule.php @@ -129,7 +129,7 @@ private function collectParentMethods(string $methodName, ClassReflection $class $parentMethods = []; $parentClass = $class->getParentClass(); - if ($parentClass !== false && $parentClass->hasNativeMethod($methodName)) { + if ($parentClass !== null && $parentClass->hasNativeMethod($methodName)) { $parentMethod = $parentClass->getNativeMethod($methodName); if (!$parentMethod->isPrivate()) { $parentMethods[] = $parentMethod; diff --git a/src/Rules/Methods/OverridingMethodRule.php b/src/Rules/Methods/OverridingMethodRule.php index 059c21df82..56cdd11762 100644 --- a/src/Rules/Methods/OverridingMethodRule.php +++ b/src/Rules/Methods/OverridingMethodRule.php @@ -62,7 +62,7 @@ public function processNode(Node $node, Scope $scope): array if ($prototype->getDeclaringClass()->getName() === $method->getDeclaringClass()->getName()) { if (strtolower($method->getName()) === '__construct') { $parent = $method->getDeclaringClass()->getParentClass(); - if ($parent !== false && $parent->hasConstructor()) { + if ($parent !== null && $parent->hasConstructor()) { $parentConstructor = $parent->getConstructor(); if ($parentConstructor->isFinal()->yes()) { return $this->addErrors([ diff --git a/src/Rules/Properties/AccessPropertiesRule.php b/src/Rules/Properties/AccessPropertiesRule.php index 6de912f2fe..c2d43fab36 100644 --- a/src/Rules/Properties/AccessPropertiesRule.php +++ b/src/Rules/Properties/AccessPropertiesRule.php @@ -123,7 +123,7 @@ static function (Type $type) use ($name): bool { $referencedClass = $typeResult->getReferencedClasses()[0]; $propertyClassReflection = $this->reflectionProvider->getClass($referencedClass); $parentClassReflection = $propertyClassReflection->getParentClass(); - while ($parentClassReflection !== false) { + while ($parentClassReflection !== null) { if ($parentClassReflection->hasProperty($name)) { return [ RuleErrorBuilder::message(sprintf( diff --git a/src/Rules/Properties/AccessStaticPropertiesRule.php b/src/Rules/Properties/AccessStaticPropertiesRule.php index b29beb813b..7d96f249f3 100644 --- a/src/Rules/Properties/AccessStaticPropertiesRule.php +++ b/src/Rules/Properties/AccessStaticPropertiesRule.php @@ -101,7 +101,7 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node, ))->build(), ]; } - if ($scope->getClassReflection()->getParentClass() === false) { + if ($scope->getClassReflection()->getParentClass() === null) { return [ RuleErrorBuilder::message(sprintf( '%s::%s() accesses parent::$%s but %s does not extend any class.', diff --git a/src/Rules/Properties/OverridingPropertyRule.php b/src/Rules/Properties/OverridingPropertyRule.php index 9f5bc22434..ad45a22d52 100644 --- a/src/Rules/Properties/OverridingPropertyRule.php +++ b/src/Rules/Properties/OverridingPropertyRule.php @@ -208,7 +208,7 @@ public function processNode(Node $node, Scope $scope): array private function findPrototype(ClassReflection $classReflection, string $propertyName): ?PhpPropertyReflection { $parentClass = $classReflection->getParentClass(); - if ($parentClass === false) { + if ($parentClass === null) { return null; } diff --git a/src/Type/FileTypeMapper.php b/src/Type/FileTypeMapper.php index d9b14ab7a3..85344d851f 100644 --- a/src/Type/FileTypeMapper.php +++ b/src/Type/FileTypeMapper.php @@ -462,7 +462,7 @@ function (\PhpParser\Node $node) use ($fileName, $lookForTrait, $traitMethodAlia if (!$traitReflection->isTrait()) { continue; } - if ($traitReflection->getFileName() === false) { + if ($traitReflection->getFileName() === null) { continue; } if (!file_exists($traitReflection->getFileName())) { diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 2214826e94..846cf7960a 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -527,7 +527,7 @@ public function toArray(): Type } $classReflection = $classReflection->getParentClass(); - } while ($classReflection !== false); + } while ($classReflection !== null); return new ConstantArrayType($arrayKeys, $arrayValues); } @@ -1092,7 +1092,7 @@ private function getParent(): ?ObjectType } $parentReflection = $thisReflection->getParentClass(); - if ($parentReflection === false) { + if ($parentReflection === null) { return null; } diff --git a/src/Type/ParserNodeTypeToPHPStanType.php b/src/Type/ParserNodeTypeToPHPStanType.php index c337f65221..4ed5f5b10a 100644 --- a/src/Type/ParserNodeTypeToPHPStanType.php +++ b/src/Type/ParserNodeTypeToPHPStanType.php @@ -31,7 +31,7 @@ public static function resolve($type, ?ClassReflection $classReflection): Type } elseif ( $lowercasedClassName === 'parent' && $classReflection !== null - && $classReflection->getParentClass() !== false + && $classReflection->getParentClass() !== null ) { $typeClassName = $classReflection->getParentClass()->getName(); } diff --git a/src/Type/Php/GetParentClassDynamicFunctionReturnTypeExtension.php b/src/Type/Php/GetParentClassDynamicFunctionReturnTypeExtension.php index 41caad5470..8dbcd52c3a 100644 --- a/src/Type/Php/GetParentClassDynamicFunctionReturnTypeExtension.php +++ b/src/Type/Php/GetParentClassDynamicFunctionReturnTypeExtension.php @@ -92,7 +92,7 @@ private function findParentClassType( ): Type { $parentClass = $classReflection->getParentClass(); - if ($parentClass === false) { + if ($parentClass === null) { return new ConstantBooleanType(false); } diff --git a/src/Type/TypehintHelper.php b/src/Type/TypehintHelper.php index 5fe0ed14fa..68b2a60e82 100644 --- a/src/Type/TypehintHelper.php +++ b/src/Type/TypehintHelper.php @@ -43,7 +43,7 @@ private static function getTypeObjectFromTypehint(string $typeString, ?string $s $broker = Broker::getInstance(); if ($selfClass !== null && $broker->hasClass($selfClass)) { $classReflection = $broker->getClass($selfClass); - if ($classReflection->getParentClass() !== false) { + if ($classReflection->getParentClass() !== null) { return new ObjectType($classReflection->getParentClass()->getName()); } } diff --git a/tests/PHPStan/Reflection/ClassReflectionTest.php b/tests/PHPStan/Reflection/ClassReflectionTest.php index 63d810a67a..fe893f3f61 100644 --- a/tests/PHPStan/Reflection/ClassReflectionTest.php +++ b/tests/PHPStan/Reflection/ClassReflectionTest.php @@ -141,7 +141,7 @@ public function testGenericInheritance(): void $this->assertSame('GenericInheritance\\C', $reflection->getDisplayName()); $parent = $reflection->getParentClass(); - $this->assertNotFalse($parent); + $this->assertNotNull($parent); $this->assertSame('GenericInheritance\\C0', $parent->getDisplayName()); From 10852a038d3ab28f271ae438346c5e55bc6fc1b2 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 23 Sep 2021 11:20:37 +0200 Subject: [PATCH 0388/1284] Use ReflectionProviderStaticAccessor instead of Broker::getInstance() --- src/DependencyInjection/ContainerFactory.php | 3 ++ src/PhpDoc/StubValidator.php | 3 ++ .../Dummy/DummyConstantReflection.php | 6 +-- .../Dummy/DummyMethodReflection.php | 6 +-- .../Dummy/DummyPropertyReflection.php | 6 +-- .../ReflectionProviderStaticAccessor.php | 27 +++++++++++ src/Type/ClassStringType.php | 10 ++--- src/Type/Constant/ConstantArrayType.php | 8 ++-- src/Type/Constant/ConstantStringType.php | 24 +++++----- src/Type/Generic/GenericClassStringType.php | 6 +-- src/Type/Generic/GenericObjectType.php | 8 ++-- src/Type/ObjectType.php | 45 ++++++++++--------- src/Type/StaticType.php | 8 ++-- ...gAlwaysAcceptingObjectWithToStringType.php | 8 ++-- src/Type/StringType.php | 8 ++-- src/Type/ThisType.php | 8 ++-- src/Type/TypehintHelper.php | 14 +++--- tests/PHPStan/Type/ArrayTypeTest.php | 4 +- 18 files changed, 118 insertions(+), 84 deletions(-) create mode 100644 src/Reflection/ReflectionProviderStaticAccessor.php diff --git a/src/DependencyInjection/ContainerFactory.php b/src/DependencyInjection/ContainerFactory.php index d1fa6f5746..2cf9f3fe48 100644 --- a/src/DependencyInjection/ContainerFactory.php +++ b/src/DependencyInjection/ContainerFactory.php @@ -10,6 +10,8 @@ use PHPStan\Command\CommandHelper; use PHPStan\File\FileHelper; use PHPStan\Php\PhpVersion; +use PHPStan\Reflection\ReflectionProvider; +use PHPStan\Reflection\ReflectionProviderStaticAccessor; use Symfony\Component\Finder\Finder; use function sys_get_temp_dir; @@ -120,6 +122,7 @@ public function create( /** @var Broker $broker */ $broker = $container->getByType(Broker::class); Broker::registerInstance($broker); + ReflectionProviderStaticAccessor::registerInstance($container->getByType(ReflectionProvider::class)); $container->getService('typeSpecifier'); BleedingEdgeToggle::setBleedingEdge($container->parameters['featureToggles']['bleedingEdge']); diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index 93ad07363f..3e7ad505af 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -12,6 +12,7 @@ use PHPStan\PhpDocParser\Lexer\Lexer; use PHPStan\PhpDocParser\Parser\PhpDocParser; use PHPStan\Reflection\ReflectionProvider; +use PHPStan\Reflection\ReflectionProviderStaticAccessor; use PHPStan\Rules\ClassCaseSensitivityCheck; use PHPStan\Rules\Classes\ExistingClassesInClassImplementsRule; use PHPStan\Rules\Classes\ExistingClassesInInterfaceExtendsRule; @@ -74,6 +75,7 @@ public function validate(array $stubFiles, bool $debug): array } $originalBroker = Broker::getInstance(); + $originalReflectionProvider = ReflectionProviderStaticAccessor::getInstance(); $container = $this->derivativeContainerFactory->create([ __DIR__ . '/../../conf/config.stubValidator.neon', ]); @@ -113,6 +115,7 @@ static function (): void { } Broker::registerInstance($originalBroker); + ReflectionProviderStaticAccessor::registerInstance($originalReflectionProvider); ObjectType::resetCaches(); return $errors; diff --git a/src/Reflection/Dummy/DummyConstantReflection.php b/src/Reflection/Dummy/DummyConstantReflection.php index 2137837e39..f45c4797b4 100644 --- a/src/Reflection/Dummy/DummyConstantReflection.php +++ b/src/Reflection/Dummy/DummyConstantReflection.php @@ -2,9 +2,9 @@ namespace PHPStan\Reflection\Dummy; -use PHPStan\Broker\Broker; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ConstantReflection; +use PHPStan\Reflection\ReflectionProviderStaticAccessor; use PHPStan\TrinaryLogic; use PHPStan\Type\MixedType; use PHPStan\Type\Type; @@ -21,9 +21,9 @@ public function __construct(string $name) public function getDeclaringClass(): ClassReflection { - $broker = Broker::getInstance(); + $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); - return $broker->getClass(\stdClass::class); + return $reflectionProvider->getClass(\stdClass::class); } public function getFileName(): ?string diff --git a/src/Reflection/Dummy/DummyMethodReflection.php b/src/Reflection/Dummy/DummyMethodReflection.php index bf12ffb38b..29e32c76e0 100644 --- a/src/Reflection/Dummy/DummyMethodReflection.php +++ b/src/Reflection/Dummy/DummyMethodReflection.php @@ -2,10 +2,10 @@ namespace PHPStan\Reflection\Dummy; -use PHPStan\Broker\Broker; use PHPStan\Reflection\ClassMemberReflection; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\MethodReflection; +use PHPStan\Reflection\ReflectionProviderStaticAccessor; use PHPStan\Reflection\TrivialParametersAcceptor; use PHPStan\TrinaryLogic; use PHPStan\Type\Type; @@ -22,9 +22,9 @@ public function __construct(string $name) public function getDeclaringClass(): ClassReflection { - $broker = Broker::getInstance(); + $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); - return $broker->getClass(\stdClass::class); + return $reflectionProvider->getClass(\stdClass::class); } public function isStatic(): bool diff --git a/src/Reflection/Dummy/DummyPropertyReflection.php b/src/Reflection/Dummy/DummyPropertyReflection.php index 1f5b97379e..9ae5f17a5e 100644 --- a/src/Reflection/Dummy/DummyPropertyReflection.php +++ b/src/Reflection/Dummy/DummyPropertyReflection.php @@ -2,9 +2,9 @@ namespace PHPStan\Reflection\Dummy; -use PHPStan\Broker\Broker; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\PropertyReflection; +use PHPStan\Reflection\ReflectionProviderStaticAccessor; use PHPStan\TrinaryLogic; use PHPStan\Type\MixedType; use PHPStan\Type\Type; @@ -14,9 +14,9 @@ class DummyPropertyReflection implements PropertyReflection public function getDeclaringClass(): ClassReflection { - $broker = Broker::getInstance(); + $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); - return $broker->getClass(\stdClass::class); + return $reflectionProvider->getClass(\stdClass::class); } public function isStatic(): bool diff --git a/src/Reflection/ReflectionProviderStaticAccessor.php b/src/Reflection/ReflectionProviderStaticAccessor.php new file mode 100644 index 0000000000..b38c0ca69b --- /dev/null +++ b/src/Reflection/ReflectionProviderStaticAccessor.php @@ -0,0 +1,27 @@ +hasClass($type->getValue())); + $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); + return TrinaryLogic::createFromBoolean($reflectionProvider->hasClass($type->getValue())); } if ($type instanceof self) { @@ -46,8 +46,8 @@ public function accepts(Type $type, bool $strictTypes): TrinaryLogic public function isSuperTypeOf(Type $type): TrinaryLogic { if ($type instanceof ConstantStringType) { - $broker = Broker::getInstance(); - return TrinaryLogic::createFromBoolean($broker->hasClass($type->getValue())); + $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); + return TrinaryLogic::createFromBoolean($reflectionProvider->hasClass($type->getValue())); } if ($type instanceof self) { diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 306510e5a5..952fba62e6 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -2,9 +2,9 @@ namespace PHPStan\Type\Constant; -use PHPStan\Broker\Broker; use PHPStan\Reflection\ClassMemberAccessAnswerer; use PHPStan\Reflection\InaccessibleMethod; +use PHPStan\Reflection\ReflectionProviderStaticAccessor; use PHPStan\Reflection\TrivialParametersAcceptor; use PHPStan\TrinaryLogic; use PHPStan\Type\Accessory\NonEmptyArrayType; @@ -368,11 +368,11 @@ public function findTypeAndMethodName(): ?ConstantArrayTypeAndMethod } if ($classOrObject instanceof ConstantStringType) { - $broker = Broker::getInstance(); - if (!$broker->hasClass($classOrObject->getValue())) { + $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); + if (!$reflectionProvider->hasClass($classOrObject->getValue())) { return ConstantArrayTypeAndMethod::createUnknown(); } - $type = new ObjectType($broker->getClass($classOrObject->getValue())->getName()); + $type = new ObjectType($reflectionProvider->getClass($classOrObject->getValue())->getName()); } elseif ($classOrObject instanceof GenericClassStringType) { $type = $classOrObject->getGenericType(); } elseif ((new \PHPStan\Type\ObjectWithoutClassType())->isSuperTypeOf($classOrObject)->yes()) { diff --git a/src/Type/Constant/ConstantStringType.php b/src/Type/Constant/ConstantStringType.php index b261e9a815..a41b2b9dfe 100644 --- a/src/Type/Constant/ConstantStringType.php +++ b/src/Type/Constant/ConstantStringType.php @@ -3,9 +3,9 @@ namespace PHPStan\Type\Constant; use PhpParser\Node\Name; -use PHPStan\Broker\Broker; use PHPStan\Reflection\ClassMemberAccessAnswerer; use PHPStan\Reflection\InaccessibleMethod; +use PHPStan\Reflection\ReflectionProviderStaticAccessor; use PHPStan\Reflection\TrivialParametersAcceptor; use PHPStan\TrinaryLogic; use PHPStan\Type\Accessory\AccessoryLiteralStringType; @@ -118,9 +118,9 @@ public function isSuperTypeOf(Type $type): TrinaryLogic return TrinaryLogic::createNo(); } if ($type instanceof ClassStringType) { - $broker = Broker::getInstance(); + $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); - return $broker->hasClass($this->getValue()) ? TrinaryLogic::createMaybe() : TrinaryLogic::createNo(); + return $reflectionProvider->hasClass($this->getValue()) ? TrinaryLogic::createMaybe() : TrinaryLogic::createNo(); } if ($type instanceof self) { @@ -144,21 +144,21 @@ public function isCallable(): TrinaryLogic return TrinaryLogic::createNo(); } - $broker = Broker::getInstance(); + $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); // 'my_function' - if ($broker->hasFunction(new Name($this->value), null)) { + if ($reflectionProvider->hasFunction(new Name($this->value), null)) { return TrinaryLogic::createYes(); } // 'MyClass::myStaticFunction' $matches = \Nette\Utils\Strings::match($this->value, '#^([a-zA-Z_\\x7f-\\xff\\\\][a-zA-Z0-9_\\x7f-\\xff\\\\]*)::([a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*)\z#'); if ($matches !== null) { - if (!$broker->hasClass($matches[1])) { + if (!$reflectionProvider->hasClass($matches[1])) { return TrinaryLogic::createMaybe(); } - $classRef = $broker->getClass($matches[1]); + $classRef = $reflectionProvider->getClass($matches[1]); if ($classRef->hasMethod($matches[2])) { return TrinaryLogic::createYes(); } @@ -179,22 +179,22 @@ public function isCallable(): TrinaryLogic */ public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array { - $broker = Broker::getInstance(); + $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); // 'my_function' $functionName = new Name($this->value); - if ($broker->hasFunction($functionName, null)) { - return $broker->getFunction($functionName, null)->getVariants(); + if ($reflectionProvider->hasFunction($functionName, null)) { + return $reflectionProvider->getFunction($functionName, null)->getVariants(); } // 'MyClass::myStaticFunction' $matches = \Nette\Utils\Strings::match($this->value, '#^([a-zA-Z_\\x7f-\\xff\\\\][a-zA-Z0-9_\\x7f-\\xff\\\\]*)::([a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*)\z#'); if ($matches !== null) { - if (!$broker->hasClass($matches[1])) { + if (!$reflectionProvider->hasClass($matches[1])) { return [new TrivialParametersAcceptor()]; } - $classReflection = $broker->getClass($matches[1]); + $classReflection = $reflectionProvider->getClass($matches[1]); if ($classReflection->hasMethod($matches[2])) { $method = $classReflection->getMethod($matches[2], $scope); if (!$scope->canCallMethod($method)) { diff --git a/src/Type/Generic/GenericClassStringType.php b/src/Type/Generic/GenericClassStringType.php index e91dfb3b95..bcb5e67e56 100644 --- a/src/Type/Generic/GenericClassStringType.php +++ b/src/Type/Generic/GenericClassStringType.php @@ -2,7 +2,7 @@ namespace PHPStan\Type\Generic; -use PHPStan\Broker\Broker; +use PHPStan\Reflection\ReflectionProviderStaticAccessor; use PHPStan\TrinaryLogic; use PHPStan\Type\ClassStringType; use PHPStan\Type\CompoundType; @@ -53,8 +53,8 @@ public function accepts(Type $type, bool $strictTypes): TrinaryLogic } if ($type instanceof ConstantStringType) { - $broker = Broker::getInstance(); - if (!$broker->hasClass($type->getValue())) { + $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); + if (!$reflectionProvider->hasClass($type->getValue())) { return TrinaryLogic::createNo(); } diff --git a/src/Type/Generic/GenericObjectType.php b/src/Type/Generic/GenericObjectType.php index 8ee2c8a5f1..e0230b9fa9 100644 --- a/src/Type/Generic/GenericObjectType.php +++ b/src/Type/Generic/GenericObjectType.php @@ -2,11 +2,11 @@ namespace PHPStan\Type\Generic; -use PHPStan\Broker\Broker; use PHPStan\Reflection\ClassMemberAccessAnswerer; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\PropertyReflection; +use PHPStan\Reflection\ReflectionProviderStaticAccessor; use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection; use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection; use PHPStan\TrinaryLogic; @@ -182,12 +182,12 @@ public function getClassReflection(): ?ClassReflection return $this->classReflection; } - $broker = Broker::getInstance(); - if (!$broker->hasClass($this->getClassName())) { + $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); + if (!$reflectionProvider->hasClass($this->getClassName())) { return null; } - return $this->classReflection = $broker->getClass($this->getClassName())->withTypes($this->types); + return $this->classReflection = $reflectionProvider->getClass($this->getClassName())->withTypes($this->types); } public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 846cf7960a..adc64f9d43 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -11,6 +11,7 @@ use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Reflection\Php\UniversalObjectCratesClassReflectionExtension; use PHPStan\Reflection\PropertyReflection; +use PHPStan\Reflection\ReflectionProviderStaticAccessor; use PHPStan\Reflection\TrivialParametersAcceptor; use PHPStan\Reflection\Type\CalledOnTypeUnresolvedMethodPrototypeReflection; use PHPStan\Reflection\Type\CalledOnTypeUnresolvedPropertyPrototypeReflection; @@ -278,14 +279,14 @@ public function isSuperTypeOf(Type $type): TrinaryLogic return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createYes(); } - $broker = Broker::getInstance(); + $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); - if ($this->getClassReflection() === null || !$broker->hasClass($thatClassName)) { + if ($this->getClassReflection() === null || !$reflectionProvider->hasClass($thatClassName)) { return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createMaybe(); } $thisClassReflection = $this->getClassReflection(); - $thatClassReflection = $broker->getClass($thatClassName); + $thatClassReflection = $reflectionProvider->getClass($thatClassName); if ($thisClassReflection->getName() === $thatClassReflection->getName()) { return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createYes(); @@ -341,14 +342,14 @@ private function checkSubclassAcceptability(string $thatClass): TrinaryLogic return TrinaryLogic::createYes(); } - $broker = Broker::getInstance(); + $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); - if ($this->getClassReflection() === null || !$broker->hasClass($thatClass)) { + if ($this->getClassReflection() === null || !$reflectionProvider->hasClass($thatClass)) { return TrinaryLogic::createNo(); } $thisReflection = $this->getClassReflection(); - $thatReflection = $broker->getClass($thatClass); + $thatReflection = $reflectionProvider->getClass($thatClass); if ($thisReflection->getName() === $thatReflection->getName()) { // class alias @@ -369,12 +370,12 @@ private function checkSubclassAcceptability(string $thatClass): TrinaryLogic public function describe(VerbosityLevel $level): string { $preciseNameCallback = function (): string { - $broker = Broker::getInstance(); - if (!$broker->hasClass($this->className)) { + $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); + if (!$reflectionProvider->hasClass($this->className)) { return $this->className; } - return $broker->getClassName($this->className); + return $reflectionProvider->getClassName($this->className); }; $preciseWithSubtracted = function () use ($level): string { @@ -484,13 +485,13 @@ public function toArray(): Type return new ArrayType(new MixedType(), new MixedType()); } - $broker = Broker::getInstance(); + $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); if ( !$classReflection->getNativeReflection()->isUserDefined() || UniversalObjectCratesClassReflectionExtension::isUniversalObjectCrate( - $broker, - $broker->getUniversalObjectCratesClasses(), + $reflectionProvider, + Broker::getInstance()->getUniversalObjectCratesClasses(), $classReflection ) ) { @@ -505,7 +506,7 @@ public function toArray(): Type continue; } - $declaringClass = $broker->getClass($nativeProperty->getDeclaringClass()->getName()); + $declaringClass = $reflectionProvider->getClass($nativeProperty->getDeclaringClass()->getName()); $property = $declaringClass->getNativeProperty($nativeProperty->getName()); $keyName = $nativeProperty->getName(); @@ -1005,12 +1006,12 @@ public function getNakedClassReflection(): ?ClassReflection if ($this->classReflection !== null) { return $this->classReflection; } - $broker = Broker::getInstance(); - if (!$broker->hasClass($this->className)) { + $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); + if (!$reflectionProvider->hasClass($this->className)) { return null; } - $this->classReflection = $broker->getClass($this->className); + $this->classReflection = $reflectionProvider->getClass($this->className); return $this->classReflection; } @@ -1020,12 +1021,12 @@ public function getClassReflection(): ?ClassReflection if ($this->classReflection !== null) { return $this->classReflection; } - $broker = Broker::getInstance(); - if (!$broker->hasClass($this->className)) { + $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); + if (!$reflectionProvider->hasClass($this->className)) { return null; } - $classReflection = $broker->getClass($this->className); + $classReflection = $reflectionProvider->getClass($this->className); if ($classReflection->isGeneric()) { return $this->classReflection = $classReflection->withTypes(array_values($classReflection->getTemplateTypeMap()->resolveToBounds()->getTypes())); } @@ -1053,11 +1054,11 @@ public function getAncestorWithClassName(string $className): ?TypeWithClassName return self::$ancestors[$description][$className]; } - $broker = Broker::getInstance(); - if (!$broker->hasClass($className)) { + $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); + if (!$reflectionProvider->hasClass($className)) { return null; } - $theirReflection = $broker->getClass($className); + $theirReflection = $reflectionProvider->getClass($className); if ($theirReflection->getName() === $thisReflection->getName()) { return self::$ancestors[$description][$className] = $this->currentAncestors[$className] = $this; diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php index e97f20fe1f..444e73fc37 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -2,12 +2,12 @@ namespace PHPStan\Type; -use PHPStan\Broker\Broker; use PHPStan\Reflection\ClassMemberAccessAnswerer; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ConstantReflection; use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\PropertyReflection; +use PHPStan\Reflection\ReflectionProviderStaticAccessor; use PHPStan\Reflection\Type\CallbackUnresolvedMethodPrototypeReflection; use PHPStan\Reflection\Type\CallbackUnresolvedPropertyPrototypeReflection; use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection; @@ -388,9 +388,9 @@ public function traverse(callable $cb): Type */ public static function __set_state(array $properties): Type { - $broker = Broker::getInstance(); - if ($broker->hasClass($properties['baseClass'])) { - return new self($broker->getClass($properties['baseClass'])); + $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); + if ($reflectionProvider->hasClass($properties['baseClass'])) { + return new self($reflectionProvider->getClass($properties['baseClass'])); } return new ErrorType(); diff --git a/src/Type/StringAlwaysAcceptingObjectWithToStringType.php b/src/Type/StringAlwaysAcceptingObjectWithToStringType.php index 1be5164838..8f08f6e9be 100644 --- a/src/Type/StringAlwaysAcceptingObjectWithToStringType.php +++ b/src/Type/StringAlwaysAcceptingObjectWithToStringType.php @@ -2,7 +2,7 @@ namespace PHPStan\Type; -use PHPStan\Broker\Broker; +use PHPStan\Reflection\ReflectionProviderStaticAccessor; use PHPStan\TrinaryLogic; class StringAlwaysAcceptingObjectWithToStringType extends StringType @@ -11,12 +11,12 @@ class StringAlwaysAcceptingObjectWithToStringType extends StringType public function accepts(Type $type, bool $strictTypes): TrinaryLogic { if ($type instanceof TypeWithClassName) { - $broker = Broker::getInstance(); - if (!$broker->hasClass($type->getClassName())) { + $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); + if (!$reflectionProvider->hasClass($type->getClassName())) { return TrinaryLogic::createNo(); } - $typeClass = $broker->getClass($type->getClassName()); + $typeClass = $reflectionProvider->getClass($type->getClassName()); return TrinaryLogic::createFromBoolean( $typeClass->hasNativeMethod('__toString') ); diff --git a/src/Type/StringType.php b/src/Type/StringType.php index f94ca86351..52ebc07712 100644 --- a/src/Type/StringType.php +++ b/src/Type/StringType.php @@ -2,7 +2,7 @@ namespace PHPStan\Type; -use PHPStan\Broker\Broker; +use PHPStan\Reflection\ReflectionProviderStaticAccessor; use PHPStan\TrinaryLogic; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantIntegerType; @@ -83,12 +83,12 @@ public function accepts(Type $type, bool $strictTypes): TrinaryLogic } if ($type instanceof TypeWithClassName && !$strictTypes) { - $broker = Broker::getInstance(); - if (!$broker->hasClass($type->getClassName())) { + $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); + if (!$reflectionProvider->hasClass($type->getClassName())) { return TrinaryLogic::createNo(); } - $typeClass = $broker->getClass($type->getClassName()); + $typeClass = $reflectionProvider->getClass($type->getClassName()); return TrinaryLogic::createFromBoolean( $typeClass->hasNativeMethod('__toString') ); diff --git a/src/Type/ThisType.php b/src/Type/ThisType.php index 2d6d1e5adc..ff5dcef89d 100644 --- a/src/Type/ThisType.php +++ b/src/Type/ThisType.php @@ -2,8 +2,8 @@ namespace PHPStan\Type; -use PHPStan\Broker\Broker; use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ReflectionProviderStaticAccessor; /** @api */ class ThisType extends StaticType @@ -33,9 +33,9 @@ public function describe(VerbosityLevel $level): string */ public static function __set_state(array $properties): Type { - $broker = Broker::getInstance(); - if ($broker->hasClass($properties['baseClass'])) { - return new self($broker->getClass($properties['baseClass'])); + $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); + if ($reflectionProvider->hasClass($properties['baseClass'])) { + return new self($reflectionProvider->getClass($properties['baseClass'])); } return new ErrorType(); diff --git a/src/Type/TypehintHelper.php b/src/Type/TypehintHelper.php index 68b2a60e82..61fc587a6a 100644 --- a/src/Type/TypehintHelper.php +++ b/src/Type/TypehintHelper.php @@ -2,7 +2,7 @@ namespace PHPStan\Type; -use PHPStan\Broker\Broker; +use PHPStan\Reflection\ReflectionProviderStaticAccessor; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Generic\TemplateTypeHelper; use ReflectionNamedType; @@ -40,18 +40,18 @@ private static function getTypeObjectFromTypehint(string $typeString, ?string $s case 'self': return $selfClass !== null ? new ObjectType($selfClass) : new ErrorType(); case 'parent': - $broker = Broker::getInstance(); - if ($selfClass !== null && $broker->hasClass($selfClass)) { - $classReflection = $broker->getClass($selfClass); + $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); + if ($selfClass !== null && $reflectionProvider->hasClass($selfClass)) { + $classReflection = $reflectionProvider->getClass($selfClass); if ($classReflection->getParentClass() !== null) { return new ObjectType($classReflection->getParentClass()->getName()); } } return new NonexistentParentClassType(); case 'static': - $broker = Broker::getInstance(); - if ($selfClass !== null && $broker->hasClass($selfClass)) { - return new StaticType($broker->getClass($selfClass)); + $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); + if ($selfClass !== null && $reflectionProvider->hasClass($selfClass)) { + return new StaticType($reflectionProvider->getClass($selfClass)); } return new ErrorType(); diff --git a/tests/PHPStan/Type/ArrayTypeTest.php b/tests/PHPStan/Type/ArrayTypeTest.php index df1211e805..559e4da6d9 100644 --- a/tests/PHPStan/Type/ArrayTypeTest.php +++ b/tests/PHPStan/Type/ArrayTypeTest.php @@ -2,7 +2,7 @@ namespace PHPStan\Type; -use PHPStan\Broker\Broker; +use PHPStan\Reflection\ReflectionProviderStaticAccessor; use PHPStan\TrinaryLogic; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantIntegerType; @@ -83,7 +83,7 @@ public function testIsSuperTypeOf(ArrayType $type, Type $otherType, TrinaryLogic public function dataAccepts(): array { - $reflectionProvider = Broker::getInstance(); + $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); return [ [ From 38a1d5628abe118250eb56fa8bedb64a22085c68 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 23 Sep 2021 11:31:03 +0200 Subject: [PATCH 0389/1284] Use createReflectionProvider() instead of createBroker() in tests --- src/Testing/RuleTestCase.php | 6 ++--- .../Analyser/AnalyserIntegrationTest.php | 9 ++++---- tests/PHPStan/Analyser/AnalyserTest.php | 8 +++---- .../Analyser/AnonymousClassNameRuleTest.php | 4 ++-- tests/PHPStan/Analyser/TypeSpecifierTest.php | 6 ++--- ...onsMethodsClassReflectionExtensionTest.php | 10 ++++----- ...PropertiesClassReflectionExtensionTest.php | 10 ++++----- .../Annotations/DeprecatedAnnotationsTest.php | 22 ++++++++----------- .../Annotations/FinalAnnotationsTest.php | 13 +++++------ .../Annotations/InternalAnnotationsTest.php | 13 +++++------ .../Annotations/ThrowsAnnotationsTest.php | 13 +++++------ .../Reflection/ClassReflectionTest.php | 21 ++++++++---------- tests/PHPStan/Reflection/MixedTypeTest.php | 2 +- .../ParametersAcceptorSelectorTest.php | 12 +++++----- .../Reflection/ReflectionProviderTest.php | 2 +- tests/PHPStan/Reflection/UnionTypesTest.php | 2 +- .../DefaultExceptionTypeResolverTest.php | 2 +- ...stractMethodInNonAbstractClassRuleTest.php | 1 - .../InvalidThrowsPhpDocValueRuleTest.php | 2 +- .../AccessPropertiesInAssignRuleTest.php | 4 ++-- .../Properties/AccessPropertiesRuleTest.php | 4 ++-- ...AccessStaticPropertiesInAssignRuleTest.php | 4 ++-- .../AccessStaticPropertiesRuleTest.php | 8 +++---- 23 files changed, 78 insertions(+), 100 deletions(-) diff --git a/src/Testing/RuleTestCase.php b/src/Testing/RuleTestCase.php index 059f409081..e3c4a67e4c 100644 --- a/src/Testing/RuleTestCase.php +++ b/src/Testing/RuleTestCase.php @@ -44,10 +44,10 @@ private function getAnalyser(): Analyser $this->getRule(), ]); - $broker = $this->createBroker(); + $reflectionProvider = $this->createReflectionProvider(); $typeSpecifier = $this->getTypeSpecifier(); $nodeScopeResolver = new NodeScopeResolver( - $broker, + $reflectionProvider, self::getReflectors()[0], $this->getClassReflectionExtensionRegistryProvider(), $this->getParser(), @@ -65,7 +65,7 @@ private function getAnalyser(): Analyser true ); $fileAnalyser = new FileAnalyser( - $this->createScopeFactory($broker, $typeSpecifier), + $this->createScopeFactory($reflectionProvider, $typeSpecifier), $nodeScopeResolver, $this->getParser(), self::getContainer()->getByType(DependencyResolver::class), diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 3a88d32854..1ab24aa64d 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -4,7 +4,6 @@ use Bug4288\MyClass; use Bug4713\Service; -use PHPStan\Broker\Broker; use PHPStan\File\FileHelper; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Reflection\SignatureMap\SignatureMapProvider; @@ -83,8 +82,8 @@ public function testExtendingKnownClassWithCheck(): void $errors = $this->runAnalyse(__DIR__ . '/data/extending-known-class-with-check.php'); $this->assertCount(0, $errors); - $broker = self::getContainer()->getByType(Broker::class); - $this->assertTrue($broker->hasClass(\ExtendingKnownClassWithCheck\Foo::class)); + $reflectionProvider = $this->createReflectionProvider(); + $this->assertTrue($reflectionProvider->hasClass(\ExtendingKnownClassWithCheck\Foo::class)); } public function testInfiniteRecursionWithCallable(): void @@ -353,7 +352,7 @@ public function testBug4713(): void $this->assertCount(1, $errors); $this->assertSame('Method Bug4713\Service::createInstance() should return Bug4713\Service but returns object.', $errors[0]->getMessage()); - $reflectionProvider = $this->createBroker(); + $reflectionProvider = $this->createReflectionProvider(); $class = $reflectionProvider->getClass(Service::class); $parameter = ParametersAcceptorSelector::selectSingle($class->getNativeMethod('createInstance')->getVariants())->getParameters()[0]; $defaultValue = $parameter->getDefaultValue(); @@ -366,7 +365,7 @@ public function testBug4288(): void $errors = $this->runAnalyse(__DIR__ . '/data/bug-4288.php'); $this->assertCount(0, $errors); - $reflectionProvider = $this->createBroker(); + $reflectionProvider = $this->createReflectionProvider(); $class = $reflectionProvider->getClass(MyClass::class); $parameter = ParametersAcceptorSelector::selectSingle($class->getNativeMethod('paginate')->getVariants())->getParameters()[0]; $defaultValue = $parameter->getDefaultValue(); diff --git a/tests/PHPStan/Analyser/AnalyserTest.php b/tests/PHPStan/Analyser/AnalyserTest.php index 22e953852b..0de0a0f1a3 100644 --- a/tests/PHPStan/Analyser/AnalyserTest.php +++ b/tests/PHPStan/Analyser/AnalyserTest.php @@ -482,7 +482,7 @@ private function createAnalyser(bool $reportUnmatchedIgnoredErrors): \PHPStan\An new AlwaysFailRule(), ]); - $broker = $this->createBroker(); + $reflectionProvider = $this->createReflectionProvider(); $printer = new \PhpParser\PrettyPrinter\Standard(); $fileHelper = $this->getFileHelper(); @@ -491,7 +491,7 @@ private function createAnalyser(bool $reportUnmatchedIgnoredErrors): \PHPStan\An $phpDocInheritanceResolver = new PhpDocInheritanceResolver($fileTypeMapper); $nodeScopeResolver = new NodeScopeResolver( - $broker, + $reflectionProvider, self::getReflectors()[0], $this->getClassReflectionExtensionRegistryProvider(), $this->getParser(), @@ -510,7 +510,7 @@ private function createAnalyser(bool $reportUnmatchedIgnoredErrors): \PHPStan\An ); $lexer = new \PhpParser\Lexer(['usedAttributes' => ['comments', 'startLine', 'endLine', 'startTokenPos', 'endTokenPos']]); $fileAnalyser = new FileAnalyser( - $this->createScopeFactory($broker, $typeSpecifier), + $this->createScopeFactory($reflectionProvider, $typeSpecifier), $nodeScopeResolver, new RichParser( new \PhpParser\Parser\Php7($lexer), @@ -518,7 +518,7 @@ private function createAnalyser(bool $reportUnmatchedIgnoredErrors): \PHPStan\An new NodeConnectingVisitor(), new StatementOrderVisitor() ), - new DependencyResolver($fileHelper, $broker, new ExportedNodeResolver($fileTypeMapper, $printer)), + new DependencyResolver($fileHelper, $reflectionProvider, new ExportedNodeResolver($fileTypeMapper, $printer)), $reportUnmatchedIgnoredErrors ); diff --git a/tests/PHPStan/Analyser/AnonymousClassNameRuleTest.php b/tests/PHPStan/Analyser/AnonymousClassNameRuleTest.php index 2ea1a331d7..c5f5a5aab1 100644 --- a/tests/PHPStan/Analyser/AnonymousClassNameRuleTest.php +++ b/tests/PHPStan/Analyser/AnonymousClassNameRuleTest.php @@ -10,8 +10,8 @@ class AnonymousClassNameRuleTest extends RuleTestCase protected function getRule(): Rule { - $broker = $this->createReflectionProvider(); - return new AnonymousClassNameRule($broker); + $reflectionProvider = $this->createReflectionProvider(); + return new AnonymousClassNameRule($reflectionProvider); } public function testRule(): void diff --git a/tests/PHPStan/Analyser/TypeSpecifierTest.php b/tests/PHPStan/Analyser/TypeSpecifierTest.php index cf8c1bd687..b6b46a0ba3 100644 --- a/tests/PHPStan/Analyser/TypeSpecifierTest.php +++ b/tests/PHPStan/Analyser/TypeSpecifierTest.php @@ -46,11 +46,11 @@ class TypeSpecifierTest extends \PHPStan\Testing\PHPStanTestCase protected function setUp(): void { - $broker = $this->createBroker(); + $reflectionProvider = $this->createReflectionProvider(); $this->printer = new \PhpParser\PrettyPrinter\Standard(); $this->typeSpecifier = self::getContainer()->getService('typeSpecifier'); - $this->scope = $this->createScopeFactory($broker, $this->typeSpecifier)->create(ScopeContext::create('')); - $this->scope = $this->scope->enterClass($broker->getClass('DateTime')); + $this->scope = $this->createScopeFactory($reflectionProvider, $this->typeSpecifier)->create(ScopeContext::create('')); + $this->scope = $this->scope->enterClass($reflectionProvider->getClass('DateTime')); $this->scope = $this->scope->assignVariable('bar', new ObjectType('Bar')); $this->scope = $this->scope->assignVariable('stringOrNull', new UnionType([new StringType(), new NullType()])); $this->scope = $this->scope->assignVariable('string', new StringType()); diff --git a/tests/PHPStan/Reflection/Annotations/AnnotationsMethodsClassReflectionExtensionTest.php b/tests/PHPStan/Reflection/Annotations/AnnotationsMethodsClassReflectionExtensionTest.php index ddda1e195d..345712db1e 100644 --- a/tests/PHPStan/Reflection/Annotations/AnnotationsMethodsClassReflectionExtensionTest.php +++ b/tests/PHPStan/Reflection/Annotations/AnnotationsMethodsClassReflectionExtensionTest.php @@ -3,7 +3,6 @@ namespace PHPStan\Reflection\Annotations; use PHPStan\Analyser\Scope; -use PHPStan\Broker\Broker; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Reflection\PassedByReference; use PHPStan\Reflection\Php\PhpMethodReflection; @@ -957,9 +956,8 @@ public function dataMethods(): array */ public function testMethods(string $className, array $methods): void { - /** @var Broker $broker */ - $broker = self::getContainer()->getByType(Broker::class); - $class = $broker->getClass($className); + $reflectionProvider = $this->createReflectionProvider(); + $class = $reflectionProvider->getClass($className); $scope = $this->createMock(Scope::class); $scope->method('isInClass')->willReturn(true); $scope->method('getClassReflection')->willReturn($class); @@ -1020,8 +1018,8 @@ public function testMethods(string $className, array $methods): void public function testOverridingNativeMethodsWithAnnotationsDoesNotBreakGetNativeMethod(): void { - $broker = self::getContainer()->getByType(Broker::class); - $class = $broker->getClass(\AnnotationsMethods\Bar::class); + $reflectionProvider = $this->createReflectionProvider(); + $class = $reflectionProvider->getClass(\AnnotationsMethods\Bar::class); $this->assertTrue($class->hasNativeMethod('overridenMethodWithAnnotation')); $this->assertInstanceOf(PhpMethodReflection::class, $class->getNativeMethod('overridenMethodWithAnnotation')); } diff --git a/tests/PHPStan/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtensionTest.php b/tests/PHPStan/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtensionTest.php index 67a9fc47d8..17e6ffd5e2 100644 --- a/tests/PHPStan/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtensionTest.php +++ b/tests/PHPStan/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtensionTest.php @@ -3,7 +3,6 @@ namespace PHPStan\Reflection\Annotations; use PHPStan\Analyser\Scope; -use PHPStan\Broker\Broker; use PHPStan\Type\VerbosityLevel; class AnnotationsPropertiesClassReflectionExtensionTest extends \PHPStan\Testing\PHPStanTestCase @@ -216,9 +215,8 @@ public function dataProperties(): array */ public function testProperties(string $className, array $properties): void { - /** @var Broker $broker */ - $broker = self::getContainer()->getByType(Broker::class); - $class = $broker->getClass($className); + $reflectionProvider = $this->createReflectionProvider(); + $class = $reflectionProvider->getClass($className); $scope = $this->createMock(Scope::class); $scope->method('isInClass')->willReturn(true); $scope->method('getClassReflection')->willReturn($class); @@ -255,8 +253,8 @@ public function testProperties(string $className, array $properties): void public function testOverridingNativePropertiesWithAnnotationsDoesNotBreakGetNativeProperty(): void { - $broker = self::getContainer()->getByType(Broker::class); - $class = $broker->getClass(\AnnotationsProperties\Bar::class); + $reflectionProvider = $this->createReflectionProvider(); + $class = $reflectionProvider->getClass(\AnnotationsProperties\Bar::class); $this->assertTrue($class->hasNativeProperty('overridenPropertyWithAnnotation')); $this->assertSame('AnnotationsProperties\Foo', $class->getNativeProperty('overridenPropertyWithAnnotation')->getReadableType()->describe(VerbosityLevel::precise())); } diff --git a/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php b/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php index 2da095b58e..ea25a9d06f 100644 --- a/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php +++ b/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php @@ -4,7 +4,6 @@ use PhpParser\Node\Name; use PHPStan\Analyser\Scope; -use PHPStan\Broker\Broker; class DeprecatedAnnotationsTest extends \PHPStan\Testing\PHPStanTestCase { @@ -84,9 +83,8 @@ public function dataDeprecatedAnnotations(): array */ public function testDeprecatedAnnotations(bool $deprecated, string $className, ?string $classDeprecation, array $deprecatedAnnotations): void { - /** @var Broker $broker */ - $broker = self::getContainer()->getByType(Broker::class); - $class = $broker->getClass($className); + $reflectionProvider = $this->createReflectionProvider(); + $class = $reflectionProvider->getClass($className); $scope = $this->createMock(Scope::class); $scope->method('isInClass')->willReturn(true); $scope->method('getClassReflection')->willReturn($class); @@ -118,21 +116,19 @@ public function testDeprecatedUserFunctions(): void { require_once __DIR__ . '/data/annotations-deprecated.php'; - /** @var Broker $broker */ - $broker = self::getContainer()->getByType(Broker::class); + $reflectionProvider = $this->createReflectionProvider(); - $this->assertFalse($broker->getFunction(new Name\FullyQualified('DeprecatedAnnotations\foo'), null)->isDeprecated()->yes()); - $this->assertTrue($broker->getFunction(new Name\FullyQualified('DeprecatedAnnotations\deprecatedFoo'), null)->isDeprecated()->yes()); + $this->assertFalse($reflectionProvider->getFunction(new Name\FullyQualified('DeprecatedAnnotations\foo'), null)->isDeprecated()->yes()); + $this->assertTrue($reflectionProvider->getFunction(new Name\FullyQualified('DeprecatedAnnotations\deprecatedFoo'), null)->isDeprecated()->yes()); } public function testNonDeprecatedNativeFunctions(): void { - /** @var Broker $broker */ - $broker = self::getContainer()->getByType(Broker::class); + $reflectionProvider = $this->createReflectionProvider(); - $this->assertFalse($broker->getFunction(new Name('str_replace'), null)->isDeprecated()->yes()); - $this->assertFalse($broker->getFunction(new Name('get_class'), null)->isDeprecated()->yes()); - $this->assertFalse($broker->getFunction(new Name('function_exists'), null)->isDeprecated()->yes()); + $this->assertFalse($reflectionProvider->getFunction(new Name('str_replace'), null)->isDeprecated()->yes()); + $this->assertFalse($reflectionProvider->getFunction(new Name('get_class'), null)->isDeprecated()->yes()); + $this->assertFalse($reflectionProvider->getFunction(new Name('function_exists'), null)->isDeprecated()->yes()); } } diff --git a/tests/PHPStan/Reflection/Annotations/FinalAnnotationsTest.php b/tests/PHPStan/Reflection/Annotations/FinalAnnotationsTest.php index c14a273b2d..8ac8a769b7 100644 --- a/tests/PHPStan/Reflection/Annotations/FinalAnnotationsTest.php +++ b/tests/PHPStan/Reflection/Annotations/FinalAnnotationsTest.php @@ -4,7 +4,6 @@ use PhpParser\Node\Name; use PHPStan\Analyser\Scope; -use PHPStan\Broker\Broker; class FinalAnnotationsTest extends \PHPStan\Testing\PHPStanTestCase { @@ -43,9 +42,8 @@ public function dataFinalAnnotations(): array */ public function testFinalAnnotations(bool $final, string $className, array $finalAnnotations): void { - /** @var Broker $broker */ - $broker = self::getContainer()->getByType(Broker::class); - $class = $broker->getClass($className); + $reflectionProvider = $this->createReflectionProvider(); + $class = $reflectionProvider->getClass($className); $scope = $this->createMock(Scope::class); $scope->method('isInClass')->willReturn(true); $scope->method('getClassReflection')->willReturn($class); @@ -63,11 +61,10 @@ public function testFinalUserFunctions(): void { require_once __DIR__ . '/data/annotations-final.php'; - /** @var Broker $broker */ - $broker = self::getContainer()->getByType(Broker::class); + $reflectionProvider = $this->createReflectionProvider(); - $this->assertFalse($broker->getFunction(new Name\FullyQualified('FinalAnnotations\foo'), null)->isFinal()->yes()); - $this->assertTrue($broker->getFunction(new Name\FullyQualified('FinalAnnotations\finalFoo'), null)->isFinal()->yes()); + $this->assertFalse($reflectionProvider->getFunction(new Name\FullyQualified('FinalAnnotations\foo'), null)->isFinal()->yes()); + $this->assertTrue($reflectionProvider->getFunction(new Name\FullyQualified('FinalAnnotations\finalFoo'), null)->isFinal()->yes()); } } diff --git a/tests/PHPStan/Reflection/Annotations/InternalAnnotationsTest.php b/tests/PHPStan/Reflection/Annotations/InternalAnnotationsTest.php index 24eac4c81d..be4c411a54 100644 --- a/tests/PHPStan/Reflection/Annotations/InternalAnnotationsTest.php +++ b/tests/PHPStan/Reflection/Annotations/InternalAnnotationsTest.php @@ -4,7 +4,6 @@ use PhpParser\Node\Name; use PHPStan\Analyser\Scope; -use PHPStan\Broker\Broker; class InternalAnnotationsTest extends \PHPStan\Testing\PHPStanTestCase { @@ -111,9 +110,8 @@ public function dataInternalAnnotations(): array */ public function testInternalAnnotations(bool $internal, string $className, array $internalAnnotations): void { - /** @var Broker $broker */ - $broker = self::getContainer()->getByType(Broker::class); - $class = $broker->getClass($className); + $reflectionProvider = $this->createReflectionProvider(); + $class = $reflectionProvider->getClass($className); $scope = $this->createMock(Scope::class); $scope->method('isInClass')->willReturn(true); $scope->method('getClassReflection')->willReturn($class); @@ -141,11 +139,10 @@ public function testInternalUserFunctions(): void { require_once __DIR__ . '/data/annotations-internal.php'; - /** @var Broker $broker */ - $broker = self::getContainer()->getByType(Broker::class); + $reflectionProvider = $this->createReflectionProvider(); - $this->assertFalse($broker->getFunction(new Name\FullyQualified('InternalAnnotations\foo'), null)->isInternal()->yes()); - $this->assertTrue($broker->getFunction(new Name\FullyQualified('InternalAnnotations\internalFoo'), null)->isInternal()->yes()); + $this->assertFalse($reflectionProvider->getFunction(new Name\FullyQualified('InternalAnnotations\foo'), null)->isInternal()->yes()); + $this->assertTrue($reflectionProvider->getFunction(new Name\FullyQualified('InternalAnnotations\internalFoo'), null)->isInternal()->yes()); } } diff --git a/tests/PHPStan/Reflection/Annotations/ThrowsAnnotationsTest.php b/tests/PHPStan/Reflection/Annotations/ThrowsAnnotationsTest.php index 5cc633832c..4dc0fc8aa3 100644 --- a/tests/PHPStan/Reflection/Annotations/ThrowsAnnotationsTest.php +++ b/tests/PHPStan/Reflection/Annotations/ThrowsAnnotationsTest.php @@ -4,7 +4,6 @@ use PhpParser\Node\Name; use PHPStan\Analyser\Scope; -use PHPStan\Broker\Broker; use PHPStan\Type\VerbosityLevel; class ThrowsAnnotationsTest extends \PHPStan\Testing\PHPStanTestCase @@ -68,9 +67,8 @@ public function dataThrowsAnnotations(): array */ public function testThrowsAnnotations(string $className, array $throwsAnnotations): void { - /** @var Broker $broker */ - $broker = self::getContainer()->getByType(Broker::class); - $class = $broker->getClass($className); + $reflectionProvider = $this->createReflectionProvider(); + $class = $reflectionProvider->getClass($className); $scope = $this->createMock(Scope::class); foreach ($throwsAnnotations as $methodName => $type) { @@ -84,12 +82,11 @@ public function testThrowsOnUserFunctions(): void { require_once __DIR__ . '/data/annotations-throws.php'; - /** @var Broker $broker */ - $broker = self::getContainer()->getByType(Broker::class); + $reflectionProvider = $this->createReflectionProvider(); - $this->assertNull($broker->getFunction(new Name\FullyQualified('ThrowsAnnotations\withoutThrows'), null)->getThrowType()); + $this->assertNull($reflectionProvider->getFunction(new Name\FullyQualified('ThrowsAnnotations\withoutThrows'), null)->getThrowType()); - $throwType = $broker->getFunction(new Name\FullyQualified('ThrowsAnnotations\throwsRuntime'), null)->getThrowType(); + $throwType = $reflectionProvider->getFunction(new Name\FullyQualified('ThrowsAnnotations\throwsRuntime'), null)->getThrowType(); $this->assertNotNull($throwType); $this->assertSame(\RuntimeException::class, $throwType->describe(VerbosityLevel::typeOnly())); } diff --git a/tests/PHPStan/Reflection/ClassReflectionTest.php b/tests/PHPStan/Reflection/ClassReflectionTest.php index fe893f3f61..c900dc65f1 100644 --- a/tests/PHPStan/Reflection/ClassReflectionTest.php +++ b/tests/PHPStan/Reflection/ClassReflectionTest.php @@ -124,9 +124,8 @@ public function testClassHierarchyDistances( public function testVariadicTraitMethod(): void { - /** @var Broker $broker */ - $broker = self::getContainer()->getService('broker'); - $fooReflection = $broker->getClass(\HasTraitUse\Foo::class); + $reflectionProvider = $this->createReflectionProvider(); + $fooReflection = $reflectionProvider->getClass(\HasTraitUse\Foo::class); $variadicMethod = $fooReflection->getNativeMethod('variadicMethod'); $methodVariant = ParametersAcceptorSelector::selectSingle($variadicMethod->getVariants()); $this->assertTrue($methodVariant->isVariadic()); @@ -134,9 +133,8 @@ public function testVariadicTraitMethod(): void public function testGenericInheritance(): void { - /** @var Broker $broker */ - $broker = self::getContainer()->getService('broker'); - $reflection = $broker->getClass(\GenericInheritance\C::class); + $reflectionProvider = $this->createReflectionProvider(); + $reflection = $reflectionProvider->getClass(\GenericInheritance\C::class); $this->assertSame('GenericInheritance\\C', $reflection->getDisplayName()); @@ -156,9 +154,8 @@ public function testGenericInheritance(): void public function testIsGenericWithStubPhpDoc(): void { - /** @var Broker $broker */ - $broker = self::getContainer()->getService('broker'); - $reflection = $broker->getClass(\ReflectionClass::class); + $reflectionProvider = $this->createReflectionProvider(); + $reflection = $reflectionProvider->getClass(\ReflectionClass::class); $this->assertTrue($reflection->isGeneric()); } @@ -196,7 +193,7 @@ public function testIsAttributeClass(string $className, bool $expected, int $exp if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 80000) { $this->markTestSkipped('Test requires PHP 8.0.'); } - $reflectionProvider = $this->createBroker(); + $reflectionProvider = $this->createReflectionProvider(); $reflection = $reflectionProvider->getClass($className); $this->assertSame($expected, $reflection->isAttributeClass()); if (!$expected) { @@ -207,7 +204,7 @@ public function testIsAttributeClass(string $className, bool $expected, int $exp public function testDeprecatedConstantFromAnotherFile(): void { - $reflectionProvider = $this->createBroker(); + $reflectionProvider = $this->createReflectionProvider(); $reflection = $reflectionProvider->getClass(SecuredRouter::class); $constant = $reflection->getConstant('SECURED'); $this->assertTrue($constant->isDeprecated()->yes()); @@ -221,7 +218,7 @@ public function testDeprecatedConstantFromAnotherFile(): void */ public function testGetTraits(string $className, array $expected, bool $recursive): void { - $reflectionProvider = $this->createBroker(); + $reflectionProvider = $this->createReflectionProvider(); $this->assertSame( array_map( diff --git a/tests/PHPStan/Reflection/MixedTypeTest.php b/tests/PHPStan/Reflection/MixedTypeTest.php index c9b5b7eb66..ff7aecd0f2 100644 --- a/tests/PHPStan/Reflection/MixedTypeTest.php +++ b/tests/PHPStan/Reflection/MixedTypeTest.php @@ -16,7 +16,7 @@ public function testMixedType(): void $this->markTestSkipped('Test requires PHP 8.0'); } - $reflectionProvider = $this->createBroker(); + $reflectionProvider = $this->createReflectionProvider(); $class = $reflectionProvider->getClass(Foo::class); $propertyType = $class->getNativeProperty('fooProp')->getNativeType(); $this->assertInstanceOf(MixedType::class, $propertyType); diff --git a/tests/PHPStan/Reflection/ParametersAcceptorSelectorTest.php b/tests/PHPStan/Reflection/ParametersAcceptorSelectorTest.php index 60f2746c0a..7b09586bf2 100644 --- a/tests/PHPStan/Reflection/ParametersAcceptorSelectorTest.php +++ b/tests/PHPStan/Reflection/ParametersAcceptorSelectorTest.php @@ -28,9 +28,9 @@ class ParametersAcceptorSelectorTest extends \PHPStan\Testing\PHPStanTestCase public function dataSelectFromTypes(): \Generator { require_once __DIR__ . '/data/function-definitions.php'; - $broker = $this->createBroker(); + $reflectionProvider = $this->createReflectionProvider(); - $arrayRandVariants = $broker->getFunction(new Name('array_rand'), null)->getVariants(); + $arrayRandVariants = $reflectionProvider->getFunction(new Name('array_rand'), null)->getVariants(); yield [ [ new ArrayType(new MixedType(), new MixedType()), @@ -50,7 +50,7 @@ public function dataSelectFromTypes(): \Generator $arrayRandVariants[1], ]; - $datePeriodConstructorVariants = $broker->getClass('DatePeriod')->getNativeMethod('__construct')->getVariants(); + $datePeriodConstructorVariants = $reflectionProvider->getClass('DatePeriod')->getNativeMethod('__construct')->getVariants(); yield [ [ new ObjectType(\DateTimeInterface::class), @@ -83,7 +83,7 @@ public function dataSelectFromTypes(): \Generator $datePeriodConstructorVariants[2], ]; - $ibaseWaitEventVariants = $broker->getFunction(new Name('ibase_wait_event'), null)->getVariants(); + $ibaseWaitEventVariants = $reflectionProvider->getFunction(new Name('ibase_wait_event'), null)->getVariants(); yield [ [ new ResourceType(), @@ -136,7 +136,7 @@ public function dataSelectFromTypes(): \Generator ), ]; - $absVariants = $broker->getFunction(new Name('abs'), null)->getVariants(); + $absVariants = $reflectionProvider->getFunction(new Name('abs'), null)->getVariants(); yield [ [ new FloatType(), @@ -165,7 +165,7 @@ public function dataSelectFromTypes(): \Generator $absVariants[2], ]; - $strtokVariants = $broker->getFunction(new Name('strtok'), null)->getVariants(); + $strtokVariants = $reflectionProvider->getFunction(new Name('strtok'), null)->getVariants(); yield [ [], $strtokVariants, diff --git a/tests/PHPStan/Reflection/ReflectionProviderTest.php b/tests/PHPStan/Reflection/ReflectionProviderTest.php index 8690fd0160..7af3d45fc7 100644 --- a/tests/PHPStan/Reflection/ReflectionProviderTest.php +++ b/tests/PHPStan/Reflection/ReflectionProviderTest.php @@ -86,7 +86,7 @@ public function dataMethodThrowType(): array */ public function testMethodThrowType(string $className, string $methodName, ?Type $expectedThrowType): void { - $reflectionProvider = $this->createBroker(); + $reflectionProvider = $this->createReflectionProvider(); $class = $reflectionProvider->getClass($className); $method = $class->getNativeMethod($methodName); $throwType = $method->getThrowType(); diff --git a/tests/PHPStan/Reflection/UnionTypesTest.php b/tests/PHPStan/Reflection/UnionTypesTest.php index 8ef3e7ef6a..5bab2ee37d 100644 --- a/tests/PHPStan/Reflection/UnionTypesTest.php +++ b/tests/PHPStan/Reflection/UnionTypesTest.php @@ -19,7 +19,7 @@ public function testUnionTypes(): void require_once __DIR__ . '/../../../stubs/runtime/ReflectionUnionType.php'; - $reflectionProvider = $this->createBroker(); + $reflectionProvider = $this->createReflectionProvider(); $class = $reflectionProvider->getClass(Foo::class); $propertyType = $class->getNativeProperty('fooProp')->getNativeType(); $this->assertInstanceOf(UnionType::class, $propertyType); diff --git a/tests/PHPStan/Rules/Exceptions/DefaultExceptionTypeResolverTest.php b/tests/PHPStan/Rules/Exceptions/DefaultExceptionTypeResolverTest.php index 4b56182cb1..b9a70b2ac9 100644 --- a/tests/PHPStan/Rules/Exceptions/DefaultExceptionTypeResolverTest.php +++ b/tests/PHPStan/Rules/Exceptions/DefaultExceptionTypeResolverTest.php @@ -141,7 +141,7 @@ public function testIsCheckedException( bool $expectedResult ): void { - $resolver = new DefaultExceptionTypeResolver($this->createBroker(), $uncheckedExceptionRegexes, $uncheckedExceptionClasses, $checkedExceptionRegexes, $checkedExceptionClasses); + $resolver = new DefaultExceptionTypeResolver($this->createReflectionProvider(), $uncheckedExceptionRegexes, $uncheckedExceptionClasses, $checkedExceptionRegexes, $checkedExceptionClasses); $this->assertSame($expectedResult, $resolver->isCheckedException($className, self::getContainer()->getByType(ScopeFactory::class)->create(ScopeContext::create(__DIR__)))); } diff --git a/tests/PHPStan/Rules/Methods/AbstractMethodInNonAbstractClassRuleTest.php b/tests/PHPStan/Rules/Methods/AbstractMethodInNonAbstractClassRuleTest.php index 0717ed9914..36911a51ff 100644 --- a/tests/PHPStan/Rules/Methods/AbstractMethodInNonAbstractClassRuleTest.php +++ b/tests/PHPStan/Rules/Methods/AbstractMethodInNonAbstractClassRuleTest.php @@ -47,7 +47,6 @@ public function testBug3406(): void public function testBug3406ReflectionCheck(): void { - $this->createBroker(); $reflectionProvider = $this->createReflectionProvider(); $reflection = $reflectionProvider->getClass(ClassFoo::class); $this->assertSame(AbstractFoo::class, $reflection->getNativeMethod('myFoo')->getDeclaringClass()->getName()); diff --git a/tests/PHPStan/Rules/PhpDoc/InvalidThrowsPhpDocValueRuleTest.php b/tests/PHPStan/Rules/PhpDoc/InvalidThrowsPhpDocValueRuleTest.php index 5d3b9b1e46..081fa77651 100644 --- a/tests/PHPStan/Rules/PhpDoc/InvalidThrowsPhpDocValueRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/InvalidThrowsPhpDocValueRuleTest.php @@ -105,7 +105,7 @@ public function testMergeInheritedPhpDocs( string $expectedType ): void { - $reflectionProvider = $this->createBroker(); + $reflectionProvider = $this->createReflectionProvider(); $reflection = $reflectionProvider->getClass($className); $method = $reflection->getNativeMethod($method); $throwsType = $method->getThrowType(); diff --git a/tests/PHPStan/Rules/Properties/AccessPropertiesInAssignRuleTest.php b/tests/PHPStan/Rules/Properties/AccessPropertiesInAssignRuleTest.php index 6554f967ed..973185edef 100644 --- a/tests/PHPStan/Rules/Properties/AccessPropertiesInAssignRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessPropertiesInAssignRuleTest.php @@ -14,9 +14,9 @@ class AccessPropertiesInAssignRuleTest extends RuleTestCase protected function getRule(): Rule { - $broker = $this->createReflectionProvider(); + $reflectionProvider = $this->createReflectionProvider(); return new AccessPropertiesInAssignRule( - new AccessPropertiesRule($broker, new RuleLevelHelper($broker, true, false, true, false), true) + new AccessPropertiesRule($reflectionProvider, new RuleLevelHelper($reflectionProvider, true, false, true, false), true) ); } diff --git a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php index cb843caafc..877e8e1fb0 100644 --- a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php @@ -18,8 +18,8 @@ class AccessPropertiesRuleTest extends \PHPStan\Testing\RuleTestCase protected function getRule(): \PHPStan\Rules\Rule { - $broker = $this->createReflectionProvider(); - return new AccessPropertiesRule($broker, new RuleLevelHelper($broker, true, $this->checkThisOnly, $this->checkUnionTypes, false), true); + $reflectionProvider = $this->createReflectionProvider(); + return new AccessPropertiesRule($reflectionProvider, new RuleLevelHelper($reflectionProvider, true, $this->checkThisOnly, $this->checkUnionTypes, false), true); } public function testAccessProperties(): void diff --git a/tests/PHPStan/Rules/Properties/AccessStaticPropertiesInAssignRuleTest.php b/tests/PHPStan/Rules/Properties/AccessStaticPropertiesInAssignRuleTest.php index 214fa443d7..2aa987e50e 100644 --- a/tests/PHPStan/Rules/Properties/AccessStaticPropertiesInAssignRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessStaticPropertiesInAssignRuleTest.php @@ -15,9 +15,9 @@ class AccessStaticPropertiesInAssignRuleTest extends RuleTestCase protected function getRule(): Rule { - $broker = $this->createReflectionProvider(); + $reflectionProvider = $this->createReflectionProvider(); return new AccessStaticPropertiesInAssignRule( - new AccessStaticPropertiesRule($broker, new RuleLevelHelper($broker, true, false, true, false), new ClassCaseSensitivityCheck($broker)) + new AccessStaticPropertiesRule($reflectionProvider, new RuleLevelHelper($reflectionProvider, true, false, true, false), new ClassCaseSensitivityCheck($reflectionProvider)) ); } diff --git a/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php index ac320be48d..9409c97014 100644 --- a/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php @@ -13,11 +13,11 @@ class AccessStaticPropertiesRuleTest extends \PHPStan\Testing\RuleTestCase protected function getRule(): \PHPStan\Rules\Rule { - $broker = $this->createReflectionProvider(); + $reflectionProvider = $this->createReflectionProvider(); return new AccessStaticPropertiesRule( - $broker, - new RuleLevelHelper($broker, true, false, true, false), - new ClassCaseSensitivityCheck($broker) + $reflectionProvider, + new RuleLevelHelper($reflectionProvider, true, false, true, false), + new ClassCaseSensitivityCheck($reflectionProvider) ); } From 1e5cf58e07446e634803c3be87e7c41fdd903c7c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 23 Sep 2021 10:53:34 +0200 Subject: [PATCH 0390/1284] Deprecated PHPStanTestCase::createBroker(). Use createReflectionProvider instead(). --- src/Testing/PHPStanTestCase.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Testing/PHPStanTestCase.php b/src/Testing/PHPStanTestCase.php index 042baa80bd..6bda3dd2bf 100644 --- a/src/Testing/PHPStanTestCase.php +++ b/src/Testing/PHPStanTestCase.php @@ -86,6 +86,7 @@ public function getParser(): \PHPStan\Parser\Parser /** * @api + * @deprecated Use createReflectionProvider() instead */ public function createBroker(): Broker { From c7755948c124cfbcb1220ab912a6eaa863cdf5fb Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 23 Sep 2021 10:55:37 +0200 Subject: [PATCH 0391/1284] Deprecated PHPStan\Broker\Broker. Use PHPStan\Reflection\ReflectionProvider instead. --- phpstan-baseline.neon | 36 ++++++++++++++++++++++++++++++++++++ src/Broker/Broker.php | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index f2cb4a97f4..449d9103f9 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -245,6 +245,15 @@ parameters: count: 1 path: src/Testing/PHPStanTestCase.php + - + message: + """ + #^Call to deprecated method getUniversalObjectCratesClasses\\(\\) of class PHPStan\\\\Broker\\\\Broker\\: + Inject %%universalObjectCratesClasses%% parameter instead\\.$# + """ + count: 1 + path: src/Type/ObjectType.php + - message: "#^Unreachable statement \\- code above always terminates\\.$#" count: 1 @@ -280,6 +289,33 @@ parameters: count: 1 path: tests/PHPStan/Analyser/EvaluationOrderTest.php + - + message: + """ + #^Call to deprecated method getClass\\(\\) of class PHPStan\\\\Broker\\\\Broker\\: + Use PHPStan\\\\Reflection\\\\ReflectionProvider instead$# + """ + count: 1 + path: tests/PHPStan/Broker/BrokerTest.php + + - + message: + """ + #^Call to deprecated method getFunction\\(\\) of class PHPStan\\\\Broker\\\\Broker\\: + Use PHPStan\\\\Reflection\\\\ReflectionProvider instead$# + """ + count: 1 + path: tests/PHPStan/Broker/BrokerTest.php + + - + message: + """ + #^Call to deprecated method hasClass\\(\\) of class PHPStan\\\\Broker\\\\Broker\\: + Use PHPStan\\\\Reflection\\\\ReflectionProvider instead$# + """ + count: 1 + path: tests/PHPStan/Broker/BrokerTest.php + - message: "#^Constant SOME_CONSTANT_IN_AUTOLOAD_FILE not found\\.$#" count: 1 diff --git a/src/Broker/Broker.php b/src/Broker/Broker.php index 800414eabc..e5c520f521 100644 --- a/src/Broker/Broker.php +++ b/src/Broker/Broker.php @@ -45,62 +45,97 @@ public static function getInstance(): Broker return self::$instance; } + /** + * @deprecated Use PHPStan\Reflection\ReflectionProvider instead + */ public function hasClass(string $className): bool { return $this->reflectionProvider->hasClass($className); } + /** + * @deprecated Use PHPStan\Reflection\ReflectionProvider instead + */ public function getClass(string $className): ClassReflection { return $this->reflectionProvider->getClass($className); } + /** + * @deprecated Use PHPStan\Reflection\ReflectionProvider instead + */ public function getClassName(string $className): string { return $this->reflectionProvider->getClassName($className); } + /** + * @deprecated Use PHPStan\Reflection\ReflectionProvider instead + */ public function supportsAnonymousClasses(): bool { return $this->reflectionProvider->supportsAnonymousClasses(); } + /** + * @deprecated Use PHPStan\Reflection\ReflectionProvider instead + */ public function getAnonymousClassReflection(\PhpParser\Node\Stmt\Class_ $classNode, Scope $scope): ClassReflection { return $this->reflectionProvider->getAnonymousClassReflection($classNode, $scope); } + /** + * @deprecated Use PHPStan\Reflection\ReflectionProvider instead + */ public function hasFunction(\PhpParser\Node\Name $nameNode, ?Scope $scope): bool { return $this->reflectionProvider->hasFunction($nameNode, $scope); } + /** + * @deprecated Use PHPStan\Reflection\ReflectionProvider instead + */ public function getFunction(\PhpParser\Node\Name $nameNode, ?Scope $scope): FunctionReflection { return $this->reflectionProvider->getFunction($nameNode, $scope); } + /** + * @deprecated Use PHPStan\Reflection\ReflectionProvider instead + */ public function resolveFunctionName(\PhpParser\Node\Name $nameNode, ?Scope $scope): ?string { return $this->reflectionProvider->resolveFunctionName($nameNode, $scope); } + /** + * @deprecated Use PHPStan\Reflection\ReflectionProvider instead + */ public function hasConstant(\PhpParser\Node\Name $nameNode, ?Scope $scope): bool { return $this->reflectionProvider->hasConstant($nameNode, $scope); } + /** + * @deprecated Use PHPStan\Reflection\ReflectionProvider instead + */ public function getConstant(\PhpParser\Node\Name $nameNode, ?Scope $scope): GlobalConstantReflection { return $this->reflectionProvider->getConstant($nameNode, $scope); } + /** + * @deprecated Use PHPStan\Reflection\ReflectionProvider instead + */ public function resolveConstantName(\PhpParser\Node\Name $nameNode, ?Scope $scope): ?string { return $this->reflectionProvider->resolveConstantName($nameNode, $scope); } /** + * @deprecated Inject %universalObjectCratesClasses% parameter instead. + * * @return string[] */ public function getUniversalObjectCratesClasses(): array From 4e7d60d74b3fda7dc4d3e0c0798b7fd55a7b32f2 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 23 Sep 2021 11:19:51 +0200 Subject: [PATCH 0392/1284] Deprecated PHPStan\Broker\Broker::getInstance(). Use PHPStan\Reflection\ReflectionProviderStaticAccessor instead. --- phpstan-baseline.neon | 18 ++++++++++++++++++ src/Broker/Broker.php | 3 +++ 2 files changed, 21 insertions(+) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 449d9103f9..ac6123fc51 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -90,6 +90,15 @@ parameters: count: 1 path: src/PhpDoc/ResolvedPhpDocBlock.php + - + message: + """ + #^Call to deprecated method getInstance\\(\\) of class PHPStan\\\\Broker\\\\Broker\\: + Use PHPStan\\\\Reflection\\\\ReflectionProviderStaticAccessor instead$# + """ + count: 1 + path: src/PhpDoc/StubValidator.php + - message: "#^Return type \\(PHPStan\\\\PhpDoc\\\\Tag\\\\ParamTag\\) of method PHPStan\\\\PhpDoc\\\\Tag\\\\ParamTag\\:\\:withType\\(\\) should be covariant with return type \\(static\\(PHPStan\\\\PhpDoc\\\\Tag\\\\TypedTag\\)\\) of method PHPStan\\\\PhpDoc\\\\Tag\\\\TypedTag\\:\\:withType\\(\\)$#" count: 1 @@ -245,6 +254,15 @@ parameters: count: 1 path: src/Testing/PHPStanTestCase.php + - + message: + """ + #^Call to deprecated method getInstance\\(\\) of class PHPStan\\\\Broker\\\\Broker\\: + Use PHPStan\\\\Reflection\\\\ReflectionProviderStaticAccessor instead$# + """ + count: 1 + path: src/Type/ObjectType.php + - message: """ diff --git a/src/Broker/Broker.php b/src/Broker/Broker.php index e5c520f521..d46f30576b 100644 --- a/src/Broker/Broker.php +++ b/src/Broker/Broker.php @@ -37,6 +37,9 @@ public static function registerInstance(Broker $broker): void self::$instance = $broker; } + /** + * @deprecated Use PHPStan\Reflection\ReflectionProviderStaticAccessor instead + */ public static function getInstance(): Broker { if (self::$instance === null) { From 65681033ff625c67a478212a0cd6da29cd5ab5fa Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 23 Sep 2021 12:43:16 +0200 Subject: [PATCH 0393/1284] [BCB] Type generalization precision is now a required argument --- composer.lock | 16 ++++++++-------- src/Analyser/MutatingScope.php | 2 +- src/Type/Constant/ConstantArrayType.php | 2 +- src/Type/Constant/ConstantStringType.php | 8 ++++---- src/Type/ConstantType.php | 2 +- src/Type/IntegerRangeType.php | 2 +- src/Type/NullType.php | 2 +- src/Type/Traits/ConstantScalarTypeTrait.php | 2 +- src/Type/TypeUtils.php | 2 +- 9 files changed, 19 insertions(+), 19 deletions(-) diff --git a/composer.lock b/composer.lock index 2c36f66299..5e67ba1ccf 100644 --- a/composer.lock +++ b/composer.lock @@ -4418,12 +4418,12 @@ "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-phpunit.git", - "reference": "234cb5563905c448083dcd9d315ed882494798ab" + "reference": "5d501422a47e99f288a1eb07e1de141df0b1eab4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/234cb5563905c448083dcd9d315ed882494798ab", - "reference": "234cb5563905c448083dcd9d315ed882494798ab", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/5d501422a47e99f288a1eb07e1de141df0b1eab4", + "reference": "5d501422a47e99f288a1eb07e1de141df0b1eab4", "shasum": "" }, "require": { @@ -4466,7 +4466,7 @@ "issues": "https://github.com/phpstan/phpstan-phpunit/issues", "source": "https://github.com/phpstan/phpstan-phpunit/tree/master" }, - "time": "2021-09-20T15:59:00+00:00" + "time": "2021-09-23T08:37:07+00:00" }, { "name": "phpstan/phpstan-strict-rules", @@ -4474,12 +4474,12 @@ "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "a9b2cb1ee2668ac3a66b5f76f37122e64299c2ca" + "reference": "b49efedea9da854d6c6d0cd6e7802ec8d76e63b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/a9b2cb1ee2668ac3a66b5f76f37122e64299c2ca", - "reference": "a9b2cb1ee2668ac3a66b5f76f37122e64299c2ca", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/b49efedea9da854d6c6d0cd6e7802ec8d76e63b4", + "reference": "b49efedea9da854d6c6d0cd6e7802ec8d76e63b4", "shasum": "" }, "require": { @@ -4518,7 +4518,7 @@ "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", "source": "https://github.com/phpstan/phpstan-strict-rules/tree/master" }, - "time": "2021-09-23T08:29:12+00:00" + "time": "2021-09-23T10:45:37+00:00" }, { "name": "phpunit/php-code-coverage", diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 395b8126dd..e031880a6c 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -1118,7 +1118,7 @@ private function resolveType(Expr $node): Type foreach ($rightTypes as $rightType) { $resultType = $this->calculateFromScalars($node, $leftType, $rightType); if ($generalize) { - $resultType = TypeUtils::generalizeType($resultType); + $resultType = TypeUtils::generalizeType($resultType, GeneralizePrecision::lessSpecific()); } $resultTypes[] = $resultType; } diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 952fba62e6..7a800fc291 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -619,7 +619,7 @@ public function toFloat(): Type return $this->toBoolean()->toFloat(); } - public function generalize(?GeneralizePrecision $precision = null): Type + public function generalize(GeneralizePrecision $precision): Type { if (count($this->keyTypes) === 0) { return $this; diff --git a/src/Type/Constant/ConstantStringType.php b/src/Type/Constant/ConstantStringType.php index a41b2b9dfe..b57e467e25 100644 --- a/src/Type/Constant/ConstantStringType.php +++ b/src/Type/Constant/ConstantStringType.php @@ -301,17 +301,17 @@ public function append(self $otherString): self return new self($this->getValue() . $otherString->getValue()); } - public function generalize(?GeneralizePrecision $precision = null): Type + public function generalize(GeneralizePrecision $precision): Type { if ($this->isClassString) { - if ($precision !== null && $precision->isMoreSpecific()) { + if ($precision->isMoreSpecific()) { return new ClassStringType(); } return new StringType(); } - if ($this->getValue() !== '' && $precision !== null && $precision->isMoreSpecific()) { + if ($this->getValue() !== '' && $precision->isMoreSpecific()) { return new IntersectionType([ new StringType(), new AccessoryNonEmptyStringType(), @@ -319,7 +319,7 @@ public function generalize(?GeneralizePrecision $precision = null): Type ]); } - if ($precision !== null && $precision->isMoreSpecific()) { + if ($precision->isMoreSpecific()) { return new IntersectionType([ new StringType(), new AccessoryLiteralStringType(), diff --git a/src/Type/ConstantType.php b/src/Type/ConstantType.php index cb0d7fe026..0f08d47c81 100644 --- a/src/Type/ConstantType.php +++ b/src/Type/ConstantType.php @@ -6,6 +6,6 @@ interface ConstantType extends Type { - public function generalize(?GeneralizePrecision $precision = null): Type; + public function generalize(GeneralizePrecision $precision): Type; } diff --git a/src/Type/IntegerRangeType.php b/src/Type/IntegerRangeType.php index f06302e833..ab643d34da 100644 --- a/src/Type/IntegerRangeType.php +++ b/src/Type/IntegerRangeType.php @@ -269,7 +269,7 @@ public function equals(Type $type): bool } - public function generalize(?GeneralizePrecision $precision = null): Type + public function generalize(GeneralizePrecision $precision): Type { return new parent(); } diff --git a/src/Type/NullType.php b/src/Type/NullType.php index a4ca1e92c7..b6326e3d7d 100644 --- a/src/Type/NullType.php +++ b/src/Type/NullType.php @@ -45,7 +45,7 @@ public function getValue() return null; } - public function generalize(?GeneralizePrecision $precision = null): Type + public function generalize(GeneralizePrecision $precision): Type { return $this; } diff --git a/src/Type/Traits/ConstantScalarTypeTrait.php b/src/Type/Traits/ConstantScalarTypeTrait.php index d21d4882cc..44e8674c39 100644 --- a/src/Type/Traits/ConstantScalarTypeTrait.php +++ b/src/Type/Traits/ConstantScalarTypeTrait.php @@ -72,7 +72,7 @@ public function isSmallerThanOrEqual(Type $otherType): TrinaryLogic return TrinaryLogic::createMaybe(); } - public function generalize(?GeneralizePrecision $precision = null): Type + public function generalize(GeneralizePrecision $precision): Type { return new parent(); } diff --git a/src/Type/TypeUtils.php b/src/Type/TypeUtils.php index 6437a16585..1d0dc83c16 100644 --- a/src/Type/TypeUtils.php +++ b/src/Type/TypeUtils.php @@ -119,7 +119,7 @@ public static function getAnyArrays(Type $type): array return self::map(ArrayType::class, $type, true, false); } - public static function generalizeType(Type $type, ?GeneralizePrecision $precision = null): Type + public static function generalizeType(Type $type, GeneralizePrecision $precision): Type { return TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($precision): Type { if ($type instanceof ConstantType) { From 1f4062fe2d0d5406e9b8a782f99b6da61eceafde Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 24 Sep 2021 16:19:30 +0200 Subject: [PATCH 0394/1284] [BCB] Some constructor parameters are now required --- src/Command/ErrorFormatter/TableErrorFormatter.php | 2 +- .../Php/PhpFunctionFromParserNodeReflection.php | 14 +++++++------- src/Reflection/Php/PhpFunctionReflection.php | 2 +- .../Php/PhpMethodFromParserNodeReflection.php | 2 +- src/Reflection/Php/PhpMethodReflection.php | 2 +- src/Rules/ClassCaseSensitivityCheck.php | 2 +- src/Rules/Generics/GenericAncestorsCheck.php | 2 +- src/Rules/MissingTypehintCheck.php | 2 +- src/Rules/RuleLevelHelper.php | 2 +- .../Command/AnalyseApplicationIntegrationTest.php | 2 +- .../ErrorFormatter/GithubErrorFormatterTest.php | 2 +- .../ErrorFormatter/TableErrorFormatterTest.php | 2 +- .../Rules/Classes/ClassAttributesRuleTest.php | 2 +- .../Classes/ClassConstantAttributesRuleTest.php | 2 +- .../Rules/Classes/ClassConstantRuleTest.php | 2 +- .../ExistingClassInClassExtendsRuleTest.php | 2 +- .../Classes/ExistingClassInInstanceOfRuleTest.php | 2 +- .../Classes/ExistingClassInTraitUseRuleTest.php | 2 +- .../ExistingClassesInClassImplementsRuleTest.php | 2 +- .../ExistingClassesInInterfaceExtendsRuleTest.php | 2 +- .../Rules/Classes/InstantiationRuleTest.php | 2 +- tests/PHPStan/Rules/Classes/MixinRuleTest.php | 4 ++-- .../MissingClassConstantTypehintRuleTest.php | 2 +- .../CaughtExceptionExistenceRuleTest.php | 2 +- .../Functions/ArrowFunctionAttributesRuleTest.php | 2 +- .../Rules/Functions/ClosureAttributesRuleTest.php | 2 +- ...tingClassesInArrowFunctionTypehintsRuleTest.php | 2 +- .../ExistingClassesInClosureTypehintsRuleTest.php | 2 +- .../ExistingClassesInTypehintsRuleTest.php | 2 +- .../Rules/Functions/FunctionAttributesRuleTest.php | 2 +- .../MissingFunctionParameterTypehintRuleTest.php | 2 +- .../MissingFunctionReturnTypehintRuleTest.php | 2 +- .../Rules/Functions/ParamAttributesRuleTest.php | 2 +- .../Rules/Generics/ClassAncestorsRuleTest.php | 3 ++- .../Rules/Generics/ClassTemplateTypeRuleTest.php | 2 +- .../Generics/FunctionTemplateTypeRuleTest.php | 2 +- .../Rules/Generics/InterfaceAncestorsRuleTest.php | 3 ++- .../Generics/InterfaceTemplateTypeRuleTest.php | 2 +- .../Rules/Generics/MethodTemplateTypeRuleTest.php | 2 +- .../Rules/Generics/TraitTemplateTypeRuleTest.php | 2 +- .../PHPStan/Rules/Generics/UsedTraitsRuleTest.php | 3 ++- .../Rules/Methods/CallStaticMethodsRuleTest.php | 2 +- .../Methods/ExistingClassesInTypehintsRuleTest.php | 2 +- .../Rules/Methods/MethodAttributesRuleTest.php | 2 +- .../Namespaces/ExistingNamesInGroupUseRuleTest.php | 2 +- .../PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php | 4 ++-- .../AccessStaticPropertiesInAssignRuleTest.php | 2 +- .../Properties/AccessStaticPropertiesRuleTest.php | 2 +- .../ExistingClassesInPropertiesRuleTest.php | 6 +++++- .../Properties/MissingPropertyTypehintRuleTest.php | 2 +- .../Properties/PropertyAttributesRuleTest.php | 2 +- 51 files changed, 66 insertions(+), 59 deletions(-) diff --git a/src/Command/ErrorFormatter/TableErrorFormatter.php b/src/Command/ErrorFormatter/TableErrorFormatter.php index 1ef40d97d1..dfb4be797a 100644 --- a/src/Command/ErrorFormatter/TableErrorFormatter.php +++ b/src/Command/ErrorFormatter/TableErrorFormatter.php @@ -19,7 +19,7 @@ class TableErrorFormatter implements ErrorFormatter public function __construct( RelativePathHelper $relativePathHelper, bool $showTipsOfTheDay, - ?string $editorUrl = null + ?string $editorUrl ) { $this->relativePathHelper = $relativePathHelper; diff --git a/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php b/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php index d1c109af91..a41d6eb572 100644 --- a/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php +++ b/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php @@ -71,13 +71,13 @@ public function __construct( array $phpDocParameterTypes, array $realParameterDefaultValues, Type $realReturnType, - ?Type $phpDocReturnType = null, - ?Type $throwType = null, - ?string $deprecatedDescription = null, - bool $isDeprecated = false, - bool $isInternal = false, - bool $isFinal = false, - ?bool $isPure = null + ?Type $phpDocReturnType, + ?Type $throwType, + ?string $deprecatedDescription, + bool $isDeprecated, + bool $isInternal, + bool $isFinal, + ?bool $isPure ) { $this->functionLike = $functionLike; diff --git a/src/Reflection/Php/PhpFunctionReflection.php b/src/Reflection/Php/PhpFunctionReflection.php index 3193522627..aa5710cfd3 100644 --- a/src/Reflection/Php/PhpFunctionReflection.php +++ b/src/Reflection/Php/PhpFunctionReflection.php @@ -86,7 +86,7 @@ public function __construct( bool $isInternal, bool $isFinal, ?string $filename, - ?bool $isPure = null + ?bool $isPure ) { $this->reflection = $reflection; diff --git a/src/Reflection/Php/PhpMethodFromParserNodeReflection.php b/src/Reflection/Php/PhpMethodFromParserNodeReflection.php index ee5f5d6dd8..a0610af795 100644 --- a/src/Reflection/Php/PhpMethodFromParserNodeReflection.php +++ b/src/Reflection/Php/PhpMethodFromParserNodeReflection.php @@ -51,7 +51,7 @@ public function __construct( bool $isDeprecated, bool $isInternal, bool $isFinal, - ?bool $isPure = null + ?bool $isPure ) { $name = strtolower($classMethod->name->name); diff --git a/src/Reflection/Php/PhpMethodReflection.php b/src/Reflection/Php/PhpMethodReflection.php index 50cbd8d81a..5ca8163673 100644 --- a/src/Reflection/Php/PhpMethodReflection.php +++ b/src/Reflection/Php/PhpMethodReflection.php @@ -112,7 +112,7 @@ public function __construct( bool $isInternal, bool $isFinal, ?string $stubPhpDocString, - ?bool $isPure = null + ?bool $isPure ) { $this->declaringClass = $declaringClass; diff --git a/src/Rules/ClassCaseSensitivityCheck.php b/src/Rules/ClassCaseSensitivityCheck.php index e3e1e67cda..91efda4469 100644 --- a/src/Rules/ClassCaseSensitivityCheck.php +++ b/src/Rules/ClassCaseSensitivityCheck.php @@ -12,7 +12,7 @@ class ClassCaseSensitivityCheck private bool $checkInternalClassCaseSensitivity; - public function __construct(ReflectionProvider $reflectionProvider, bool $checkInternalClassCaseSensitivity = false) + public function __construct(ReflectionProvider $reflectionProvider, bool $checkInternalClassCaseSensitivity) { $this->reflectionProvider = $reflectionProvider; $this->checkInternalClassCaseSensitivity = $checkInternalClassCaseSensitivity; diff --git a/src/Rules/Generics/GenericAncestorsCheck.php b/src/Rules/Generics/GenericAncestorsCheck.php index c97a8043cc..a93fa76eeb 100644 --- a/src/Rules/Generics/GenericAncestorsCheck.php +++ b/src/Rules/Generics/GenericAncestorsCheck.php @@ -32,7 +32,7 @@ public function __construct( GenericObjectTypeCheck $genericObjectTypeCheck, VarianceCheck $varianceCheck, bool $checkGenericClassInNonGenericObjectType, - array $skipCheckGenericClasses = [] + array $skipCheckGenericClasses ) { $this->reflectionProvider = $reflectionProvider; diff --git a/src/Rules/MissingTypehintCheck.php b/src/Rules/MissingTypehintCheck.php index 9d8ac31dd9..70244b9663 100644 --- a/src/Rules/MissingTypehintCheck.php +++ b/src/Rules/MissingTypehintCheck.php @@ -48,7 +48,7 @@ public function __construct( bool $checkMissingIterableValueType, bool $checkGenericClassInNonGenericObjectType, bool $checkMissingCallableSignature, - array $skipCheckGenericClasses = [] + array $skipCheckGenericClasses ) { $this->reflectionProvider = $reflectionProvider; diff --git a/src/Rules/RuleLevelHelper.php b/src/Rules/RuleLevelHelper.php index 4a119f416a..5ace8c6903 100644 --- a/src/Rules/RuleLevelHelper.php +++ b/src/Rules/RuleLevelHelper.php @@ -40,7 +40,7 @@ public function __construct( bool $checkNullables, bool $checkThisOnly, bool $checkUnionTypes, - bool $checkExplicitMixed = false + bool $checkExplicitMixed ) { $this->reflectionProvider = $reflectionProvider; diff --git a/tests/PHPStan/Command/AnalyseApplicationIntegrationTest.php b/tests/PHPStan/Command/AnalyseApplicationIntegrationTest.php index c4605b5bcb..96a2cfe99f 100644 --- a/tests/PHPStan/Command/AnalyseApplicationIntegrationTest.php +++ b/tests/PHPStan/Command/AnalyseApplicationIntegrationTest.php @@ -58,7 +58,7 @@ private function runPath(string $path, int $expectedStatusCode): string $memoryLimitFile = self::getContainer()->getParameter('memoryLimitFile'); $relativePathHelper = new FuzzyRelativePathHelper(new NullRelativePathHelper(), __DIR__, [], DIRECTORY_SEPARATOR); - $errorFormatter = new TableErrorFormatter($relativePathHelper, false); + $errorFormatter = new TableErrorFormatter($relativePathHelper, false, null); $analysisResult = $analyserApplication->analyse( [$path], true, diff --git a/tests/PHPStan/Command/ErrorFormatter/GithubErrorFormatterTest.php b/tests/PHPStan/Command/ErrorFormatter/GithubErrorFormatterTest.php index 1b52ff5515..ba5c3b4674 100644 --- a/tests/PHPStan/Command/ErrorFormatter/GithubErrorFormatterTest.php +++ b/tests/PHPStan/Command/ErrorFormatter/GithubErrorFormatterTest.php @@ -165,7 +165,7 @@ public function testFormatErrors( $relativePathHelper = new FuzzyRelativePathHelper(new NullRelativePathHelper(), self::DIRECTORY_PATH, [], '/'); $formatter = new GithubErrorFormatter( $relativePathHelper, - new TableErrorFormatter($relativePathHelper, false) + new TableErrorFormatter($relativePathHelper, false, null) ); $this->assertSame($exitCode, $formatter->formatErrors( diff --git a/tests/PHPStan/Command/ErrorFormatter/TableErrorFormatterTest.php b/tests/PHPStan/Command/ErrorFormatter/TableErrorFormatterTest.php index e36f959856..2079333ba5 100644 --- a/tests/PHPStan/Command/ErrorFormatter/TableErrorFormatterTest.php +++ b/tests/PHPStan/Command/ErrorFormatter/TableErrorFormatterTest.php @@ -150,7 +150,7 @@ public function testFormatErrors( string $expected ): void { - $formatter = new TableErrorFormatter(new FuzzyRelativePathHelper(new NullRelativePathHelper(), self::DIRECTORY_PATH, [], '/'), false); + $formatter = new TableErrorFormatter(new FuzzyRelativePathHelper(new NullRelativePathHelper(), self::DIRECTORY_PATH, [], '/'), false, null); $this->assertSame($exitCode, $formatter->formatErrors( $this->getAnalysisResult($numFileErrors, $numGenericErrors), diff --git a/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php b/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php index 02894dedbd..ff1428002e 100644 --- a/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php @@ -25,7 +25,7 @@ protected function getRule(): Rule new AttributesCheck( $reflectionProvider, new FunctionCallParametersCheck( - new RuleLevelHelper($reflectionProvider, true, false, true), + new RuleLevelHelper($reflectionProvider, true, false, true, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), diff --git a/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php b/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php index b233afdb49..1f333145e6 100644 --- a/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php @@ -25,7 +25,7 @@ protected function getRule(): Rule new AttributesCheck( $reflectionProvider, new FunctionCallParametersCheck( - new RuleLevelHelper($reflectionProvider, true, false, true), + new RuleLevelHelper($reflectionProvider, true, false, true, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), diff --git a/tests/PHPStan/Rules/Classes/ClassConstantRuleTest.php b/tests/PHPStan/Rules/Classes/ClassConstantRuleTest.php index 53a1da938e..19b0edef06 100644 --- a/tests/PHPStan/Rules/Classes/ClassConstantRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ClassConstantRuleTest.php @@ -19,7 +19,7 @@ class ClassConstantRuleTest extends \PHPStan\Testing\RuleTestCase protected function getRule(): Rule { $broker = $this->createReflectionProvider(); - return new ClassConstantRule($broker, new RuleLevelHelper($broker, true, false, true, false), new ClassCaseSensitivityCheck($broker), new PhpVersion($this->phpVersion)); + return new ClassConstantRule($broker, new RuleLevelHelper($broker, true, false, true, false), new ClassCaseSensitivityCheck($broker, true), new PhpVersion($this->phpVersion)); } public function testClassConstant(): void diff --git a/tests/PHPStan/Rules/Classes/ExistingClassInClassExtendsRuleTest.php b/tests/PHPStan/Rules/Classes/ExistingClassInClassExtendsRuleTest.php index f429d082dd..9ae0fab7a7 100644 --- a/tests/PHPStan/Rules/Classes/ExistingClassInClassExtendsRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ExistingClassInClassExtendsRuleTest.php @@ -15,7 +15,7 @@ protected function getRule(): Rule { $broker = $this->createReflectionProvider(); return new ExistingClassInClassExtendsRule( - new ClassCaseSensitivityCheck($broker), + new ClassCaseSensitivityCheck($broker, true), $broker ); } diff --git a/tests/PHPStan/Rules/Classes/ExistingClassInInstanceOfRuleTest.php b/tests/PHPStan/Rules/Classes/ExistingClassInInstanceOfRuleTest.php index 18d571f3e8..c0ce2a610b 100644 --- a/tests/PHPStan/Rules/Classes/ExistingClassInInstanceOfRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ExistingClassInInstanceOfRuleTest.php @@ -16,7 +16,7 @@ protected function getRule(): Rule $broker = $this->createReflectionProvider(); return new ExistingClassInInstanceOfRule( $broker, - new ClassCaseSensitivityCheck($broker), + new ClassCaseSensitivityCheck($broker, true), true ); } diff --git a/tests/PHPStan/Rules/Classes/ExistingClassInTraitUseRuleTest.php b/tests/PHPStan/Rules/Classes/ExistingClassInTraitUseRuleTest.php index 826094c52a..bb898044e9 100644 --- a/tests/PHPStan/Rules/Classes/ExistingClassInTraitUseRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ExistingClassInTraitUseRuleTest.php @@ -15,7 +15,7 @@ protected function getRule(): Rule { $broker = $this->createReflectionProvider(); return new ExistingClassInTraitUseRule( - new ClassCaseSensitivityCheck($broker), + new ClassCaseSensitivityCheck($broker, true), $broker ); } diff --git a/tests/PHPStan/Rules/Classes/ExistingClassesInClassImplementsRuleTest.php b/tests/PHPStan/Rules/Classes/ExistingClassesInClassImplementsRuleTest.php index 72f58b2369..d328cf159d 100644 --- a/tests/PHPStan/Rules/Classes/ExistingClassesInClassImplementsRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ExistingClassesInClassImplementsRuleTest.php @@ -15,7 +15,7 @@ protected function getRule(): Rule { $broker = $this->createReflectionProvider(); return new ExistingClassesInClassImplementsRule( - new ClassCaseSensitivityCheck($broker), + new ClassCaseSensitivityCheck($broker, true), $broker ); } diff --git a/tests/PHPStan/Rules/Classes/ExistingClassesInInterfaceExtendsRuleTest.php b/tests/PHPStan/Rules/Classes/ExistingClassesInInterfaceExtendsRuleTest.php index 196edc0421..8b9bca5876 100644 --- a/tests/PHPStan/Rules/Classes/ExistingClassesInInterfaceExtendsRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ExistingClassesInInterfaceExtendsRuleTest.php @@ -15,7 +15,7 @@ protected function getRule(): Rule { $broker = $this->createReflectionProvider(); return new ExistingClassesInInterfaceExtendsRule( - new ClassCaseSensitivityCheck($broker), + new ClassCaseSensitivityCheck($broker, true), $broker ); } diff --git a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php index c9f7a241b6..9d499b64ff 100644 --- a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php +++ b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php @@ -21,7 +21,7 @@ protected function getRule(): \PHPStan\Rules\Rule return new InstantiationRule( $broker, new FunctionCallParametersCheck(new RuleLevelHelper($broker, true, false, true, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), true, true, true, true), - new ClassCaseSensitivityCheck($broker) + new ClassCaseSensitivityCheck($broker, true) ); } diff --git a/tests/PHPStan/Rules/Classes/MixinRuleTest.php b/tests/PHPStan/Rules/Classes/MixinRuleTest.php index c94714b072..d680e953b0 100644 --- a/tests/PHPStan/Rules/Classes/MixinRuleTest.php +++ b/tests/PHPStan/Rules/Classes/MixinRuleTest.php @@ -23,9 +23,9 @@ protected function getRule(): Rule return new MixinRule( self::getContainer()->getByType(FileTypeMapper::class), $reflectionProvider, - new ClassCaseSensitivityCheck($reflectionProvider), + new ClassCaseSensitivityCheck($reflectionProvider, true), new GenericObjectTypeCheck(), - new MissingTypehintCheck($reflectionProvider, true, true, true), + new MissingTypehintCheck($reflectionProvider, true, true, true, []), new UnresolvableTypeHelper(), true ); diff --git a/tests/PHPStan/Rules/Constants/MissingClassConstantTypehintRuleTest.php b/tests/PHPStan/Rules/Constants/MissingClassConstantTypehintRuleTest.php index f084566139..0d5834b82c 100644 --- a/tests/PHPStan/Rules/Constants/MissingClassConstantTypehintRuleTest.php +++ b/tests/PHPStan/Rules/Constants/MissingClassConstantTypehintRuleTest.php @@ -14,7 +14,7 @@ class MissingClassConstantTypehintRuleTest extends RuleTestCase protected function getRule(): \PHPStan\Rules\Rule { $reflectionProvider = $this->createReflectionProvider(); - return new MissingClassConstantTypehintRule(new MissingTypehintCheck($reflectionProvider, true, true, true)); + return new MissingClassConstantTypehintRule(new MissingTypehintCheck($reflectionProvider, true, true, true, [])); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Exceptions/CaughtExceptionExistenceRuleTest.php b/tests/PHPStan/Rules/Exceptions/CaughtExceptionExistenceRuleTest.php index 9875365e55..8eec73ae88 100644 --- a/tests/PHPStan/Rules/Exceptions/CaughtExceptionExistenceRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/CaughtExceptionExistenceRuleTest.php @@ -15,7 +15,7 @@ protected function getRule(): \PHPStan\Rules\Rule $broker = $this->createReflectionProvider(); return new CaughtExceptionExistenceRule( $broker, - new ClassCaseSensitivityCheck($broker), + new ClassCaseSensitivityCheck($broker, true), true ); } diff --git a/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php index 64fcae7e6a..ede0ebe859 100644 --- a/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ArrowFunctionAttributesRuleTest.php @@ -25,7 +25,7 @@ protected function getRule(): Rule new AttributesCheck( $reflectionProvider, new FunctionCallParametersCheck( - new RuleLevelHelper($reflectionProvider, true, false, true), + new RuleLevelHelper($reflectionProvider, true, false, true, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), diff --git a/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php index ba897cdaa6..7ca7f49d5b 100644 --- a/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ClosureAttributesRuleTest.php @@ -25,7 +25,7 @@ protected function getRule(): Rule new AttributesCheck( $reflectionProvider, new FunctionCallParametersCheck( - new RuleLevelHelper($reflectionProvider, true, false, true), + new RuleLevelHelper($reflectionProvider, true, false, true, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), diff --git a/tests/PHPStan/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRuleTest.php b/tests/PHPStan/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRuleTest.php index 4c01c5a4fe..20fac6acad 100644 --- a/tests/PHPStan/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRuleTest.php @@ -18,7 +18,7 @@ class ExistingClassesInArrowFunctionTypehintsRuleTest extends \PHPStan\Testing\R protected function getRule(): \PHPStan\Rules\Rule { $broker = $this->createReflectionProvider(); - return new ExistingClassesInArrowFunctionTypehintsRule(new FunctionDefinitionCheck($broker, new ClassCaseSensitivityCheck($broker), new PhpVersion($this->phpVersionId), true, false)); + return new ExistingClassesInArrowFunctionTypehintsRule(new FunctionDefinitionCheck($broker, new ClassCaseSensitivityCheck($broker, true), new PhpVersion($this->phpVersionId), true, false)); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Functions/ExistingClassesInClosureTypehintsRuleTest.php b/tests/PHPStan/Rules/Functions/ExistingClassesInClosureTypehintsRuleTest.php index d7252b4ca4..9f4d2fb267 100644 --- a/tests/PHPStan/Rules/Functions/ExistingClassesInClosureTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ExistingClassesInClosureTypehintsRuleTest.php @@ -18,7 +18,7 @@ class ExistingClassesInClosureTypehintsRuleTest extends \PHPStan\Testing\RuleTes protected function getRule(): \PHPStan\Rules\Rule { $broker = $this->createReflectionProvider(); - return new ExistingClassesInClosureTypehintsRule(new FunctionDefinitionCheck($broker, new ClassCaseSensitivityCheck($broker), new PhpVersion($this->phpVersionId), true, false)); + return new ExistingClassesInClosureTypehintsRule(new FunctionDefinitionCheck($broker, new ClassCaseSensitivityCheck($broker, true), new PhpVersion($this->phpVersionId), true, false)); } public function testExistingClassInTypehint(): void diff --git a/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php b/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php index 859b7a2e12..efe0695a38 100644 --- a/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php @@ -19,7 +19,7 @@ class ExistingClassesInTypehintsRuleTest extends \PHPStan\Testing\RuleTestCase protected function getRule(): \PHPStan\Rules\Rule { $broker = $this->createReflectionProvider(); - return new ExistingClassesInTypehintsRule(new FunctionDefinitionCheck($broker, new ClassCaseSensitivityCheck($broker), new PhpVersion($this->phpVersionId), true, false)); + return new ExistingClassesInTypehintsRule(new FunctionDefinitionCheck($broker, new ClassCaseSensitivityCheck($broker, true), new PhpVersion($this->phpVersionId), true, false)); } public function testExistingClassInTypehint(): void diff --git a/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php index 799e1a0b90..8200d32431 100644 --- a/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/FunctionAttributesRuleTest.php @@ -25,7 +25,7 @@ protected function getRule(): Rule new AttributesCheck( $reflectionProvider, new FunctionCallParametersCheck( - new RuleLevelHelper($reflectionProvider, true, false, true), + new RuleLevelHelper($reflectionProvider, true, false, true, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), diff --git a/tests/PHPStan/Rules/Functions/MissingFunctionParameterTypehintRuleTest.php b/tests/PHPStan/Rules/Functions/MissingFunctionParameterTypehintRuleTest.php index 5e17b2b675..c66c93ba26 100644 --- a/tests/PHPStan/Rules/Functions/MissingFunctionParameterTypehintRuleTest.php +++ b/tests/PHPStan/Rules/Functions/MissingFunctionParameterTypehintRuleTest.php @@ -13,7 +13,7 @@ class MissingFunctionParameterTypehintRuleTest extends \PHPStan\Testing\RuleTest protected function getRule(): \PHPStan\Rules\Rule { $broker = $this->createReflectionProvider(); - return new MissingFunctionParameterTypehintRule(new MissingTypehintCheck($broker, true, true, true)); + return new MissingFunctionParameterTypehintRule(new MissingTypehintCheck($broker, true, true, true, [])); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Functions/MissingFunctionReturnTypehintRuleTest.php b/tests/PHPStan/Rules/Functions/MissingFunctionReturnTypehintRuleTest.php index eb26d2d90e..c1c8b04934 100644 --- a/tests/PHPStan/Rules/Functions/MissingFunctionReturnTypehintRuleTest.php +++ b/tests/PHPStan/Rules/Functions/MissingFunctionReturnTypehintRuleTest.php @@ -13,7 +13,7 @@ class MissingFunctionReturnTypehintRuleTest extends \PHPStan\Testing\RuleTestCas protected function getRule(): \PHPStan\Rules\Rule { $broker = $this->createReflectionProvider(); - return new MissingFunctionReturnTypehintRule(new MissingTypehintCheck($broker, true, true, true)); + return new MissingFunctionReturnTypehintRule(new MissingTypehintCheck($broker, true, true, true, [])); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php b/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php index e64960fdbe..3bf4b1227a 100644 --- a/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ParamAttributesRuleTest.php @@ -25,7 +25,7 @@ protected function getRule(): Rule new AttributesCheck( $reflectionProvider, new FunctionCallParametersCheck( - new RuleLevelHelper($reflectionProvider, true, false, true), + new RuleLevelHelper($reflectionProvider, true, false, true, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), diff --git a/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php b/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php index b31aaa8278..9deeb6a6ec 100644 --- a/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php +++ b/tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php @@ -20,7 +20,8 @@ protected function getRule(): Rule $this->createReflectionProvider(), new GenericObjectTypeCheck(), new VarianceCheck(), - true + true, + [] ), new CrossCheckInterfacesHelper() ); diff --git a/tests/PHPStan/Rules/Generics/ClassTemplateTypeRuleTest.php b/tests/PHPStan/Rules/Generics/ClassTemplateTypeRuleTest.php index e7954fbaa4..d415f5a17c 100644 --- a/tests/PHPStan/Rules/Generics/ClassTemplateTypeRuleTest.php +++ b/tests/PHPStan/Rules/Generics/ClassTemplateTypeRuleTest.php @@ -20,7 +20,7 @@ protected function getRule(): Rule return new ClassTemplateTypeRule( new TemplateTypeCheck( $broker, - new ClassCaseSensitivityCheck($broker), + new ClassCaseSensitivityCheck($broker, true), new GenericObjectTypeCheck(), $typeAliasResolver, true diff --git a/tests/PHPStan/Rules/Generics/FunctionTemplateTypeRuleTest.php b/tests/PHPStan/Rules/Generics/FunctionTemplateTypeRuleTest.php index 77b758c6bc..2c3d78a560 100644 --- a/tests/PHPStan/Rules/Generics/FunctionTemplateTypeRuleTest.php +++ b/tests/PHPStan/Rules/Generics/FunctionTemplateTypeRuleTest.php @@ -20,7 +20,7 @@ protected function getRule(): Rule return new FunctionTemplateTypeRule( self::getContainer()->getByType(FileTypeMapper::class), - new TemplateTypeCheck($broker, new ClassCaseSensitivityCheck($broker), new GenericObjectTypeCheck(), $typeAliasResolver, true) + new TemplateTypeCheck($broker, new ClassCaseSensitivityCheck($broker, true), new GenericObjectTypeCheck(), $typeAliasResolver, true) ); } diff --git a/tests/PHPStan/Rules/Generics/InterfaceAncestorsRuleTest.php b/tests/PHPStan/Rules/Generics/InterfaceAncestorsRuleTest.php index 0207076df9..907a9f7f68 100644 --- a/tests/PHPStan/Rules/Generics/InterfaceAncestorsRuleTest.php +++ b/tests/PHPStan/Rules/Generics/InterfaceAncestorsRuleTest.php @@ -20,7 +20,8 @@ protected function getRule(): Rule $this->createReflectionProvider(), new GenericObjectTypeCheck(), new VarianceCheck(), - true + true, + [] ), new CrossCheckInterfacesHelper() ); diff --git a/tests/PHPStan/Rules/Generics/InterfaceTemplateTypeRuleTest.php b/tests/PHPStan/Rules/Generics/InterfaceTemplateTypeRuleTest.php index e8272578f0..119116a965 100644 --- a/tests/PHPStan/Rules/Generics/InterfaceTemplateTypeRuleTest.php +++ b/tests/PHPStan/Rules/Generics/InterfaceTemplateTypeRuleTest.php @@ -20,7 +20,7 @@ protected function getRule(): Rule return new InterfaceTemplateTypeRule( self::getContainer()->getByType(FileTypeMapper::class), - new TemplateTypeCheck($broker, new ClassCaseSensitivityCheck($broker), new GenericObjectTypeCheck(), $typeAliasResolver, true) + new TemplateTypeCheck($broker, new ClassCaseSensitivityCheck($broker, true), new GenericObjectTypeCheck(), $typeAliasResolver, true) ); } diff --git a/tests/PHPStan/Rules/Generics/MethodTemplateTypeRuleTest.php b/tests/PHPStan/Rules/Generics/MethodTemplateTypeRuleTest.php index d953a401c8..e3c61ec085 100644 --- a/tests/PHPStan/Rules/Generics/MethodTemplateTypeRuleTest.php +++ b/tests/PHPStan/Rules/Generics/MethodTemplateTypeRuleTest.php @@ -20,7 +20,7 @@ protected function getRule(): Rule return new MethodTemplateTypeRule( self::getContainer()->getByType(FileTypeMapper::class), - new TemplateTypeCheck($broker, new ClassCaseSensitivityCheck($broker), new GenericObjectTypeCheck(), $typeAliasResolver, true) + new TemplateTypeCheck($broker, new ClassCaseSensitivityCheck($broker, true), new GenericObjectTypeCheck(), $typeAliasResolver, true) ); } diff --git a/tests/PHPStan/Rules/Generics/TraitTemplateTypeRuleTest.php b/tests/PHPStan/Rules/Generics/TraitTemplateTypeRuleTest.php index 1b90f838f6..4b40725040 100644 --- a/tests/PHPStan/Rules/Generics/TraitTemplateTypeRuleTest.php +++ b/tests/PHPStan/Rules/Generics/TraitTemplateTypeRuleTest.php @@ -20,7 +20,7 @@ protected function getRule(): Rule return new TraitTemplateTypeRule( self::getContainer()->getByType(FileTypeMapper::class), - new TemplateTypeCheck($broker, new ClassCaseSensitivityCheck($broker), new GenericObjectTypeCheck(), $typeAliasResolver, true) + new TemplateTypeCheck($broker, new ClassCaseSensitivityCheck($broker, true), new GenericObjectTypeCheck(), $typeAliasResolver, true) ); } diff --git a/tests/PHPStan/Rules/Generics/UsedTraitsRuleTest.php b/tests/PHPStan/Rules/Generics/UsedTraitsRuleTest.php index 340d663e34..38fda5ace9 100644 --- a/tests/PHPStan/Rules/Generics/UsedTraitsRuleTest.php +++ b/tests/PHPStan/Rules/Generics/UsedTraitsRuleTest.php @@ -20,7 +20,8 @@ protected function getRule(): Rule $this->createReflectionProvider(), new GenericObjectTypeCheck(), new VarianceCheck(), - true + true, + [] ) ); } diff --git a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php index d0c05ddd38..b667ebcd78 100644 --- a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php @@ -26,7 +26,7 @@ protected function getRule(): \PHPStan\Rules\Rule $broker, new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), true, true, true, true), $ruleLevelHelper, - new ClassCaseSensitivityCheck($broker), + new ClassCaseSensitivityCheck($broker, true), true, true ); diff --git a/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php b/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php index 70be8fb673..f4d2a80db7 100644 --- a/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php @@ -19,7 +19,7 @@ class ExistingClassesInTypehintsRuleTest extends \PHPStan\Testing\RuleTestCase protected function getRule(): \PHPStan\Rules\Rule { $broker = $this->createReflectionProvider(); - return new ExistingClassesInTypehintsRule(new FunctionDefinitionCheck($broker, new ClassCaseSensitivityCheck($broker), new PhpVersion($this->phpVersionId), true, false)); + return new ExistingClassesInTypehintsRule(new FunctionDefinitionCheck($broker, new ClassCaseSensitivityCheck($broker, true), new PhpVersion($this->phpVersionId), true, false)); } public function testExistingClassInTypehint(): void diff --git a/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php b/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php index 7bae918088..badcc06643 100644 --- a/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php @@ -25,7 +25,7 @@ protected function getRule(): Rule new AttributesCheck( $reflectionProvider, new FunctionCallParametersCheck( - new RuleLevelHelper($reflectionProvider, true, false, true), + new RuleLevelHelper($reflectionProvider, true, false, true, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), diff --git a/tests/PHPStan/Rules/Namespaces/ExistingNamesInGroupUseRuleTest.php b/tests/PHPStan/Rules/Namespaces/ExistingNamesInGroupUseRuleTest.php index 442d127776..8c9d0b0ce5 100644 --- a/tests/PHPStan/Rules/Namespaces/ExistingNamesInGroupUseRuleTest.php +++ b/tests/PHPStan/Rules/Namespaces/ExistingNamesInGroupUseRuleTest.php @@ -13,7 +13,7 @@ class ExistingNamesInGroupUseRuleTest extends \PHPStan\Testing\RuleTestCase protected function getRule(): \PHPStan\Rules\Rule { $broker = $this->createReflectionProvider(); - return new ExistingNamesInGroupUseRule($broker, new ClassCaseSensitivityCheck($broker), true); + return new ExistingNamesInGroupUseRule($broker, new ClassCaseSensitivityCheck($broker, true), true); } public function testRule(): void diff --git a/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php b/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php index cdeb22e447..8f4337db51 100644 --- a/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php @@ -21,9 +21,9 @@ protected function getRule(): Rule return new InvalidPhpDocVarTagTypeRule( self::getContainer()->getByType(FileTypeMapper::class), $broker, - new ClassCaseSensitivityCheck($broker), + new ClassCaseSensitivityCheck($broker, true), new GenericObjectTypeCheck(), - new MissingTypehintCheck($broker, true, true, true), + new MissingTypehintCheck($broker, true, true, true, []), new UnresolvableTypeHelper(), true, true diff --git a/tests/PHPStan/Rules/Properties/AccessStaticPropertiesInAssignRuleTest.php b/tests/PHPStan/Rules/Properties/AccessStaticPropertiesInAssignRuleTest.php index 2aa987e50e..72f1864b88 100644 --- a/tests/PHPStan/Rules/Properties/AccessStaticPropertiesInAssignRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessStaticPropertiesInAssignRuleTest.php @@ -17,7 +17,7 @@ protected function getRule(): Rule { $reflectionProvider = $this->createReflectionProvider(); return new AccessStaticPropertiesInAssignRule( - new AccessStaticPropertiesRule($reflectionProvider, new RuleLevelHelper($reflectionProvider, true, false, true, false), new ClassCaseSensitivityCheck($reflectionProvider)) + new AccessStaticPropertiesRule($reflectionProvider, new RuleLevelHelper($reflectionProvider, true, false, true, false), new ClassCaseSensitivityCheck($reflectionProvider, true)) ); } diff --git a/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php index 9409c97014..1d412e439a 100644 --- a/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php @@ -17,7 +17,7 @@ protected function getRule(): \PHPStan\Rules\Rule return new AccessStaticPropertiesRule( $reflectionProvider, new RuleLevelHelper($reflectionProvider, true, false, true, false), - new ClassCaseSensitivityCheck($reflectionProvider) + new ClassCaseSensitivityCheck($reflectionProvider, true) ); } diff --git a/tests/PHPStan/Rules/Properties/ExistingClassesInPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/ExistingClassesInPropertiesRuleTest.php index 99dea64d96..86d1bee508 100644 --- a/tests/PHPStan/Rules/Properties/ExistingClassesInPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/ExistingClassesInPropertiesRuleTest.php @@ -16,7 +16,7 @@ protected function getRule(): Rule $broker = $this->createReflectionProvider(); return new ExistingClassesInPropertiesRule( $broker, - new ClassCaseSensitivityCheck($broker), + new ClassCaseSensitivityCheck($broker, true), true, false ); @@ -67,6 +67,10 @@ public function testNonexistentClass(): void 'Property PropertiesTypes\Foo::$withTrait has invalid type PropertiesTypes\SomeTrait.', 27, ], + [ + 'Class DateTime referenced with incorrect case: Datetime.', + 30, + ], [ 'Property PropertiesTypes\Foo::$nonexistentClassInGenericObjectType has unknown class PropertiesTypes\Foooo as its type.', 33, diff --git a/tests/PHPStan/Rules/Properties/MissingPropertyTypehintRuleTest.php b/tests/PHPStan/Rules/Properties/MissingPropertyTypehintRuleTest.php index 46fbc40179..d937b2fb05 100644 --- a/tests/PHPStan/Rules/Properties/MissingPropertyTypehintRuleTest.php +++ b/tests/PHPStan/Rules/Properties/MissingPropertyTypehintRuleTest.php @@ -13,7 +13,7 @@ class MissingPropertyTypehintRuleTest extends \PHPStan\Testing\RuleTestCase protected function getRule(): \PHPStan\Rules\Rule { $broker = $this->createReflectionProvider(); - return new MissingPropertyTypehintRule(new MissingTypehintCheck($broker, true, true, true)); + return new MissingPropertyTypehintRule(new MissingTypehintCheck($broker, true, true, true, [])); } public function testRule(): void diff --git a/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php b/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php index 92b41031aa..b166c6c024 100644 --- a/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/PropertyAttributesRuleTest.php @@ -25,7 +25,7 @@ protected function getRule(): Rule new AttributesCheck( $reflectionProvider, new FunctionCallParametersCheck( - new RuleLevelHelper($reflectionProvider, true, false, true), + new RuleLevelHelper($reflectionProvider, true, false, true, false), new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), From 2d2e7fdaec3bd50453e1a203b6f4353737821c1a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 30 Sep 2021 16:43:55 +0200 Subject: [PATCH 0395/1284] Fix --- .../Rules/Methods/ExistingClassesInTypehintsRuleTest.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php b/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php index f4d2a80db7..f4256db031 100644 --- a/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php @@ -84,10 +84,18 @@ public function testExistingClassInTypehint(): void 'Class TestMethodTypehints\FooMethodTypehints referenced with incorrect case: TestMethodTypehints\fOOMethodTypehintS.', 67, ], + [ + 'Class stdClass referenced with incorrect case: STDClass.', + 76, + ], [ 'Class TestMethodTypehints\FooMethodTypehints referenced with incorrect case: TestMethodTypehints\fOOMethodTypehints.', 76, ], + [ + 'Class stdClass referenced with incorrect case: stdclass.', + 76, + ], [ 'Class TestMethodTypehints\FooMethodTypehints referenced with incorrect case: TestMethodTypehints\fOOMethodTypehintS.', 76, From 646a5ff1c713bb890363842ff2bf7996953d9b82 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 1 Oct 2021 09:32:15 +0200 Subject: [PATCH 0396/1284] GitHub Actions PHAR compilation job concurrency --- .github/workflows/phar-old.yml | 2 ++ .github/workflows/phar.yml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/phar-old.yml b/.github/workflows/phar-old.yml index eb91eb3d60..3548fe8548 100644 --- a/.github/workflows/phar-old.yml +++ b/.github/workflows/phar-old.yml @@ -7,6 +7,8 @@ on: tags: - '0.12.*' +concurrency: phar + jobs: compile: name: "Compile PHAR" diff --git a/.github/workflows/phar.yml b/.github/workflows/phar.yml index a0de139f4d..87facea311 100644 --- a/.github/workflows/phar.yml +++ b/.github/workflows/phar.yml @@ -9,6 +9,8 @@ on: tags: - '1.*' +concurrency: phar + jobs: compile: name: "Compile PHAR" From 5314a6b20503e8e1ccaa74e8dfb1e3c30c5667ed Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 1 Oct 2021 11:46:14 +0200 Subject: [PATCH 0397/1284] Tip about template bound type when combined with a native type --- .../PhpDoc/IncompatiblePhpDocTypeRule.php | 24 +++++++++----- .../IncompatiblePropertyPhpDocTypeRule.php | 11 +++++-- .../PhpDoc/IncompatiblePhpDocTypeRuleTest.php | 24 +++++++++++++- ...IncompatiblePropertyPhpDocTypeRuleTest.php | 5 +++ .../incompatible-property-native-types.php | 9 ++++++ .../Rules/PhpDoc/data/incompatible-types.php | 9 ++++++ .../data/template-type-native-type-object.php | 31 +++++++++++++++++++ 7 files changed, 103 insertions(+), 10 deletions(-) create mode 100644 tests/PHPStan/Rules/PhpDoc/data/template-type-native-type-object.php diff --git a/src/Rules/PhpDoc/IncompatiblePhpDocTypeRule.php b/src/Rules/PhpDoc/IncompatiblePhpDocTypeRule.php index 5da471cdb6..e15c2af08c 100644 --- a/src/Rules/PhpDoc/IncompatiblePhpDocTypeRule.php +++ b/src/Rules/PhpDoc/IncompatiblePhpDocTypeRule.php @@ -10,7 +10,7 @@ use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\ArrayType; use PHPStan\Type\FileTypeMapper; -use PHPStan\Type\Generic\TemplateTypeHelper; +use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\Type; use PHPStan\Type\VerbosityLevel; @@ -95,7 +95,7 @@ public function processNode(Node $node, Scope $scope): array ) { $phpDocParamType = $phpDocParamType->getItemType(); } - $isParamSuperType = $nativeParamType->isSuperTypeOf(TemplateTypeHelper::resolveToBounds($phpDocParamType)); + $isParamSuperType = $nativeParamType->isSuperTypeOf($phpDocParamType); $escapedParameterName = SprintfHelper::escapeFormatString($parameterName); @@ -128,18 +128,23 @@ public function processNode(Node $node, Scope $scope): array ))->build(); } elseif ($isParamSuperType->maybe()) { - $errors[] = RuleErrorBuilder::message(sprintf( + $errorBuilder = RuleErrorBuilder::message(sprintf( 'PHPDoc tag @param for parameter $%s with type %s is not subtype of native type %s.', $parameterName, $phpDocParamType->describe(VerbosityLevel::typeOnly()), $nativeParamType->describe(VerbosityLevel::typeOnly()) - ))->build(); + )); + if ($phpDocParamType instanceof TemplateType) { + $errorBuilder->tip(sprintf('Write @template %s of %s to fix this.', $phpDocParamType->getName(), $nativeParamType->describe(VerbosityLevel::typeOnly()))); + } + + $errors[] = $errorBuilder->build(); } } } if ($resolvedPhpDoc->getReturnTag() !== null) { - $phpDocReturnType = TemplateTypeHelper::resolveToBounds($resolvedPhpDoc->getReturnTag()->getType()); + $phpDocReturnType = $resolvedPhpDoc->getReturnTag()->getType(); if ( $this->unresolvableTypeHelper->containsUnresolvableType($phpDocReturnType) @@ -163,11 +168,16 @@ public function processNode(Node $node, Scope $scope): array ))->build(); } elseif ($isReturnSuperType->maybe()) { - $errors[] = RuleErrorBuilder::message(sprintf( + $errorBuilder = RuleErrorBuilder::message(sprintf( 'PHPDoc tag @return with type %s is not subtype of native type %s.', $phpDocReturnType->describe(VerbosityLevel::typeOnly()), $nativeReturnType->describe(VerbosityLevel::typeOnly()) - ))->build(); + )); + if ($phpDocReturnType instanceof TemplateType) { + $errorBuilder->tip(sprintf('Write @template %s of %s to fix this.', $phpDocReturnType->getName(), $nativeReturnType->describe(VerbosityLevel::typeOnly()))); + } + + $errors[] = $errorBuilder->build(); } } } diff --git a/src/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRule.php b/src/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRule.php index 0f2ab5119d..9755e30d20 100644 --- a/src/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRule.php +++ b/src/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRule.php @@ -9,6 +9,7 @@ use PHPStan\Rules\Generics\GenericObjectTypeCheck; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; +use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\VerbosityLevel; /** @@ -79,14 +80,20 @@ public function processNode(Node $node, Scope $scope): array ))->build(); } elseif ($isSuperType->maybe()) { - $messages[] = RuleErrorBuilder::message(sprintf( + $errorBuilder = RuleErrorBuilder::message(sprintf( '%s for property %s::$%s with type %s is not subtype of native type %s.', $description, $propertyReflection->getDeclaringClass()->getDisplayName(), $propertyName, $phpDocType->describe(VerbosityLevel::typeOnly()), $nativeType->describe(VerbosityLevel::typeOnly()) - ))->build(); + )); + + if ($phpDocType instanceof TemplateType) { + $errorBuilder->tip(sprintf('Write @template %s of %s to fix this.', $phpDocType->getName(), $nativeType->describe(VerbosityLevel::typeOnly()))); + } + + $messages[] = $errorBuilder->build(); } $className = SprintfHelper::escapeFormatString($propertyReflection->getDeclaringClass()->getDisplayName()); diff --git a/tests/PHPStan/Rules/PhpDoc/IncompatiblePhpDocTypeRuleTest.php b/tests/PHPStan/Rules/PhpDoc/IncompatiblePhpDocTypeRuleTest.php index 163f826ac9..9a73c50d67 100644 --- a/tests/PHPStan/Rules/PhpDoc/IncompatiblePhpDocTypeRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/IncompatiblePhpDocTypeRuleTest.php @@ -70,14 +70,17 @@ public function testRule(): void [ 'PHPDoc tag @param for parameter $a with type T is not subtype of native type int.', 154, + 'Write @template T of int to fix this.', ], [ 'PHPDoc tag @param for parameter $b with type U of DateTimeInterface is not subtype of native type DateTime.', 154, + 'Write @template U of DateTime to fix this.', ], [ - 'PHPDoc tag @return with type DateTimeInterface is not subtype of native type DateTime.', + 'PHPDoc tag @return with type U of DateTimeInterface is not subtype of native type DateTime.', 154, + 'Write @template U of DateTime to fix this.', ], [ 'PHPDoc tag @param for parameter $foo contains generic type InvalidPhpDocDefinitions\Foo but class InvalidPhpDocDefinitions\Foo is not generic.', @@ -143,6 +146,11 @@ public function testRule(): void 'PHPDoc tag @return contains generic type InvalidPhpDocDefinitions\Foo but class InvalidPhpDocDefinitions\Foo is not generic.', 274, ], + [ + 'PHPDoc tag @param for parameter $i with type TFoo is not subtype of native type int.', + 283, + 'Write @template TFoo of int to fix this.', + ], ]); } @@ -165,4 +173,18 @@ public function testBug3753(): void ]); } + public function testTemplateTypeNativeTypeObject(): void + { + if (PHP_VERSION_ID < 70400 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 7.4.'); + } + $this->analyse([__DIR__ . '/data/template-type-native-type-object.php'], [ + [ + 'PHPDoc tag @return with type T is not subtype of native type object.', + 23, + 'Write @template T of object to fix this.', + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRuleTest.php b/tests/PHPStan/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRuleTest.php index 164c03193e..7938a2401c 100644 --- a/tests/PHPStan/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRuleTest.php @@ -76,6 +76,11 @@ public function testNativeTypes(): void 'PHPDoc tag @var for property IncompatiblePhpDocPropertyNativeType\Foo::$stringOrInt with type int|string is not subtype of native type string.', 21, ], + [ + 'PHPDoc tag @var for property IncompatiblePhpDocPropertyNativeType\Lorem::$string with type T is not subtype of native type string.', + 45, + 'Write @template T of string to fix this.', + ], ]); } diff --git a/tests/PHPStan/Rules/PhpDoc/data/incompatible-property-native-types.php b/tests/PHPStan/Rules/PhpDoc/data/incompatible-property-native-types.php index 93a4a38a91..56c5f7bc60 100644 --- a/tests/PHPStan/Rules/PhpDoc/data/incompatible-property-native-types.php +++ b/tests/PHPStan/Rules/PhpDoc/data/incompatible-property-native-types.php @@ -36,3 +36,12 @@ class Baz private string $stringProp; } + +/** @template T */ +class Lorem +{ + + /** @var T */ + private string $string; + +} diff --git a/tests/PHPStan/Rules/PhpDoc/data/incompatible-types.php b/tests/PHPStan/Rules/PhpDoc/data/incompatible-types.php index 517a3e0ac3..5f7fd45b73 100644 --- a/tests/PHPStan/Rules/PhpDoc/data/incompatible-types.php +++ b/tests/PHPStan/Rules/PhpDoc/data/incompatible-types.php @@ -275,3 +275,12 @@ function genericNestedNonTemplateArgs() { } + +/** + * @template TFoo + * @param TFoo $i + */ +function genericWrongBound(int $i) +{ + +} diff --git a/tests/PHPStan/Rules/PhpDoc/data/template-type-native-type-object.php b/tests/PHPStan/Rules/PhpDoc/data/template-type-native-type-object.php new file mode 100644 index 0000000000..823fb21600 --- /dev/null +++ b/tests/PHPStan/Rules/PhpDoc/data/template-type-native-type-object.php @@ -0,0 +1,31 @@ += 7.4 + +namespace TemplateTypeNativeTypeObject; + +class HelloWorld +{ + /** + * @var array + */ + private array $instances; + + public function __construct() + { + $this->instances = []; + } + + /** + * @phpstan-template T + * @phpstan-param class-string $className + * + * @phpstan-return T + */ + public function getInstanceByName(string $className, string $name): object + { + $instance = $this->instances["[{$className}]{$name}"]; + + \assert($instance instanceof $className); + + return $instance; + } +} From f3c054b19975d570991b2ea99c1ca1c42d42161c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 2 Oct 2021 16:03:22 +0200 Subject: [PATCH 0398/1284] TypeCombinator - call count() less often --- src/Type/TypeCombinator.php | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index e3a300cfe9..6dd1fd1bf0 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -272,14 +272,16 @@ public static function union(Type ...$types): Type ); // simplify string[] | int[] to (string|int)[] - for ($i = 0; $i < count($types); $i++) { - for ($j = $i + 1; $j < count($types); $j++) { + $typesCount = count($types); + for ($i = 0; $i < $typesCount; $i++) { + for ($j = $i + 1; $j < $typesCount; $j++) { if ($types[$i] instanceof IterableType && $types[$j] instanceof IterableType) { $types[$i] = new IterableType( self::union($types[$i]->getIterableKeyType(), $types[$j]->getIterableKeyType()), self::union($types[$i]->getIterableValueType(), $types[$j]->getIterableValueType()) ); array_splice($types, $j, 1); + $typesCount--; continue 2; } } @@ -296,8 +298,10 @@ public static function union(Type ...$types): Type continue; } - for ($i = 0; $i < count($types); $i++) { - for ($j = 0; $j < count($scalarTypeItems); $j++) { + $typesCount = count($types); + $scalarTypeItemsCount = count($scalarTypeItems); + for ($i = 0; $i < $typesCount; $i++) { + for ($j = 0; $j < $scalarTypeItemsCount; $j++) { $compareResult = self::compareTypesInUnion($types[$i], $scalarTypeItems[$j]); if ($compareResult === null) { continue; @@ -307,11 +311,13 @@ public static function union(Type ...$types): Type if ($a !== null) { $types[$i] = $a; array_splice($scalarTypeItems, $j--, 1); + $scalarTypeItemsCount--; continue 1; } if ($b !== null) { $scalarTypeItems[$j] = $b; array_splice($types, $i--, 1); + $typesCount--; continue 2; } } @@ -322,8 +328,9 @@ public static function union(Type ...$types): Type // transform A | A to A // transform A | never to A - for ($i = 0; $i < count($types); $i++) { - for ($j = $i + 1; $j < count($types); $j++) { + $typesCount = count($types); + for ($i = 0; $i < $typesCount; $i++) { + for ($j = $i + 1; $j < $typesCount; $j++) { $compareResult = self::compareTypesInUnion($types[$i], $types[$j]); if ($compareResult === null) { continue; @@ -333,11 +340,13 @@ public static function union(Type ...$types): Type if ($a !== null) { $types[$i] = $a; array_splice($types, $j--, 1); + $typesCount--; continue 1; } if ($b !== null) { $types[$j] = $b; array_splice($types, $i--, 1); + $typesCount--; continue 2; } } @@ -349,10 +358,11 @@ public static function union(Type ...$types): Type } } - if (count($types) === 0) { + $typesCount = count($types); + if ($typesCount === 0) { return new NeverType(); - - } elseif (count($types) === 1) { + } + if ($typesCount === 1) { return $types[0]; } From ac6a1022ff90851171f29cbceb45b1d639099255 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 2 Oct 2021 16:06:22 +0200 Subject: [PATCH 0399/1284] TypeCombinator - eliminate same types before normalization --- src/Type/TypeCombinator.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index 6dd1fd1bf0..fb6eba781a 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -326,6 +326,14 @@ public static function union(Type ...$types): Type $scalarTypes[$classType] = $scalarTypeItems; } + if (count($types) > 16) { + $newTypes = []; + foreach ($types as $type) { + $newTypes[$type->describe(VerbosityLevel::cache())] = $type; + } + $types = array_values($newTypes); + } + // transform A | A to A // transform A | never to A $typesCount = count($types); From ac5fe1bc92b0a91e7b164c1f43eabf134dcb15b8 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 4 Oct 2021 09:36:21 +0200 Subject: [PATCH 0400/1284] TypeCombinator - fix segfault --- src/Type/Generic/TemplateTypeTrait.php | 3 +- tests/PHPStan/Analyser/data/instanceof.php | 6 ++-- .../Rules/Methods/data/returnTypes.php | 2 +- tests/PHPStan/Type/TypeCombinatorTest.php | 33 +++++++++++++++++++ 4 files changed, 38 insertions(+), 6 deletions(-) diff --git a/src/Type/Generic/TemplateTypeTrait.php b/src/Type/Generic/TemplateTypeTrait.php index d62acb63a5..aa0b863685 100644 --- a/src/Type/Generic/TemplateTypeTrait.php +++ b/src/Type/Generic/TemplateTypeTrait.php @@ -3,7 +3,6 @@ namespace PHPStan\Type\Generic; use PHPStan\TrinaryLogic; -use PHPStan\Type\CompoundType; use PHPStan\Type\IntersectionType; use PHPStan\Type\MixedType; use PHPStan\Type\Type; @@ -123,7 +122,7 @@ public function accepts(Type $type, bool $strictTypes): TrinaryLogic public function isSuperTypeOf(Type $type): TrinaryLogic { - if ($type instanceof CompoundType) { + if ($type instanceof self) { return $type->isSubTypeOf($this); } diff --git a/tests/PHPStan/Analyser/data/instanceof.php b/tests/PHPStan/Analyser/data/instanceof.php index 21b871dd64..098b74cb47 100644 --- a/tests/PHPStan/Analyser/data/instanceof.php +++ b/tests/PHPStan/Analyser/data/instanceof.php @@ -133,7 +133,7 @@ public function testExprInstanceof($subject, string $classString, $union, $inter assertType('bool', $subject instanceof $objectT); } else { assertType('mixed~ObjectT of InstanceOfNamespace\BarInterface (method InstanceOfNamespace\Foo::testExprInstanceof(), argument)', $subject); - assertType('false', $subject instanceof $objectT); + assertType('bool', $subject instanceof $objectT); // can be false } if ($subject instanceof $objectTString) { @@ -141,7 +141,7 @@ public function testExprInstanceof($subject, string $classString, $union, $inter assertType('bool', $subject instanceof $objectTString); } else { assertType('mixed~ObjectT of InstanceOfNamespace\BarInterface (method InstanceOfNamespace\Foo::testExprInstanceof(), argument)', $subject); - assertType('false', $subject instanceof $objectTString); + assertType('bool', $subject instanceof $objectTString); // can be false } if ($subject instanceof $mixedTString) { @@ -149,7 +149,7 @@ public function testExprInstanceof($subject, string $classString, $union, $inter assertType('bool', $subject instanceof $mixedTString); } else { assertType('mixed~MixedT (method InstanceOfNamespace\Foo::testExprInstanceof(), argument)', $subject); - assertType('false', $subject instanceof $mixedTString); + assertType('bool', $subject instanceof $mixedTString); // can be false } if ($subject instanceof $string) { diff --git a/tests/PHPStan/Rules/Methods/data/returnTypes.php b/tests/PHPStan/Rules/Methods/data/returnTypes.php index bc5bfc2742..c5326ae13c 100644 --- a/tests/PHPStan/Rules/Methods/data/returnTypes.php +++ b/tests/PHPStan/Rules/Methods/data/returnTypes.php @@ -1184,7 +1184,7 @@ public function doBar(\DateTimeInterface $date): \DateTimeImmutable } /** - * @template CollectionKey + * @template CollectionKey of array-key * @template CollectionValue * @implements \Iterator */ diff --git a/tests/PHPStan/Type/TypeCombinatorTest.php b/tests/PHPStan/Type/TypeCombinatorTest.php index 61fb1db976..e5610edffb 100644 --- a/tests/PHPStan/Type/TypeCombinatorTest.php +++ b/tests/PHPStan/Type/TypeCombinatorTest.php @@ -1822,6 +1822,39 @@ public function dataUnion(): array UnionType::class, '(T of bool|float|int|string (function doFoo(), parameter))|null', ], + [ + [ + new UnionType([ + new IntersectionType([new ArrayType(new MixedType(), new MixedType())]), + IntegerRangeType::fromInterval(null, -1), + IntegerRangeType::fromInterval(1, null), + ]), + TemplateTypeFactory::create( + TemplateTypeScope::createWithClass('Foo'), + 'TCode', + new UnionType([new ArrayType(new IntegerType(), new IntegerType()), new IntegerType()]), + TemplateTypeVariance::createInvariant() + ), + ], + UnionType::class, + 'array|int|int<1, max>|(TCode of array|int (class Foo, parameter))', + ], + [ + [ + new UnionType([ + new IntersectionType([new ArrayType(new MixedType(), new MixedType())]), + new CallableType(), + ]), + TemplateTypeFactory::create( + TemplateTypeScope::createWithClass('Foo'), + 'TCode', + new UnionType([new ArrayType(new IntegerType(), new IntegerType()), new IntegerType()]), + TemplateTypeVariance::createInvariant() + ), + ], + UnionType::class, + 'array|(callable(): mixed)|(TCode of array|int (class Foo, parameter))', + ], ]; } From 884b9221bd9235ced7a2a0b0bb8508408682d963 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 4 Oct 2021 10:23:43 +0200 Subject: [PATCH 0401/1284] Update Nette dependencies --- composer.json | 10 +- composer.lock | 144 +++++++++--------- phpcs.xml | 1 + src/DependencyInjection/Configurator.php | 6 +- .../Nette/NetteContainer.php | 5 +- src/DependencyInjection/RulesExtension.php | 6 +- 6 files changed, 90 insertions(+), 82 deletions(-) diff --git a/composer.json b/composer.json index 88aaae4948..9b7ac41a53 100644 --- a/composer.json +++ b/composer.json @@ -15,12 +15,12 @@ "hoa/regex": "1.17.01.13", "jean85/pretty-package-versions": "^1.0.3", "jetbrains/phpstorm-stubs": "dev-master#82595d7a426c4b3d1e3a7d604ad3f99534784599", - "nette/bootstrap": "^3.0", - "nette/di": "^3.0.5", + "nette/bootstrap": "^3.1.1", + "nette/di": "^3.0.10", "nette/finder": "^2.5", - "nette/neon": "^3.0", - "nette/schema": "^1.0", - "nette/utils": "^3.1.3", + "nette/neon": "^3.2.2", + "nette/schema": "^1.2.1", + "nette/utils": "^3.2.5", "nikic/php-parser": "4.13.0", "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "4.3.70", diff --git a/composer.lock b/composer.lock index 5e67ba1ccf..9286e90391 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c1d91467e312e364a2d0be0d50163a50", + "content-hash": "37bbe438b63cc6628dbdf725d25a3640", "packages": [ { "name": "clue/block-react", @@ -1380,29 +1380,29 @@ }, { "name": "nette/bootstrap", - "version": "v3.0.2", + "version": "v3.1.1", "source": { "type": "git", "url": "https://github.com/nette/bootstrap.git", - "reference": "67830a65b42abfb906f8e371512d336ebfb5da93" + "reference": "efe6c30fc009451f59fe56f3b309eb85c48b2baf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/bootstrap/zipball/67830a65b42abfb906f8e371512d336ebfb5da93", - "reference": "67830a65b42abfb906f8e371512d336ebfb5da93", + "url": "https://api.github.com/repos/nette/bootstrap/zipball/efe6c30fc009451f59fe56f3b309eb85c48b2baf", + "reference": "efe6c30fc009451f59fe56f3b309eb85c48b2baf", "shasum": "" }, "require": { - "nette/di": "^3.0", - "nette/utils": "^3.0", - "php": ">=7.1" + "nette/di": "^3.0.5", + "nette/utils": "^3.2.1", + "php": ">=7.2 <8.1" }, "conflict": { "tracy/tracy": "<2.6" }, "require-dev": { "latte/latte": "^2.2", - "nette/application": "^3.0", + "nette/application": "^3.1", "nette/caching": "^3.0", "nette/database": "^3.0", "nette/forms": "^3.0", @@ -1422,7 +1422,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "3.1-dev" } }, "autoload": { @@ -1446,7 +1446,7 @@ "homepage": "https://nette.org/contributors" } ], - "description": "🅱 Nette Bootstrap: the simple way to configure and bootstrap your Nette application.", + "description": "🅱 Nette Bootstrap: the simple way to configure and bootstrap your Nette application.", "homepage": "https://nette.org", "keywords": [ "bootstrapping", @@ -1455,22 +1455,22 @@ ], "support": { "issues": "https://github.com/nette/bootstrap/issues", - "source": "https://github.com/nette/bootstrap/tree/master" + "source": "https://github.com/nette/bootstrap/tree/v3.1.1" }, - "time": "2020-05-26T08:46:23+00:00" + "time": "2021-01-25T00:31:21+00:00" }, { "name": "nette/di", - "version": "v3.0.5", + "version": "v3.0.10", "source": { "type": "git", "url": "https://github.com/nette/di.git", - "reference": "766e8185196a97ded4f9128db6d79a3a124b7eb6" + "reference": "4f0cb0b3f032a9106aa3fb29e33da381564716c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/di/zipball/766e8185196a97ded4f9128db6d79a3a124b7eb6", - "reference": "766e8185196a97ded4f9128db6d79a3a124b7eb6", + "url": "https://api.github.com/repos/nette/di/zipball/4f0cb0b3f032a9106aa3fb29e33da381564716c2", + "reference": "4f0cb0b3f032a9106aa3fb29e33da381564716c2", "shasum": "" }, "require": { @@ -1478,9 +1478,9 @@ "nette/neon": "^3.0", "nette/php-generator": "^3.3.3", "nette/robot-loader": "^3.2", - "nette/schema": "^1.0", - "nette/utils": "^3.1", - "php": ">=7.1" + "nette/schema": "^1.1", + "nette/utils": "^3.1.6", + "php": ">=7.1 <8.2" }, "conflict": { "nette/bootstrap": "<3.0" @@ -1517,7 +1517,7 @@ "homepage": "https://nette.org/contributors" } ], - "description": "💎 Nette Dependency Injection Container: Flexible, compiled and full-featured DIC with perfectly usable autowiring and support for all new PHP 7.1 features.", + "description": "💎 Nette Dependency Injection Container: Flexible, compiled and full-featured DIC with perfectly usable autowiring and support for all new PHP features.", "homepage": "https://nette.org", "keywords": [ "compiled", @@ -1530,9 +1530,9 @@ ], "support": { "issues": "https://github.com/nette/di/issues", - "source": "https://github.com/nette/di/tree/master" + "source": "https://github.com/nette/di/tree/v3.0.10" }, - "time": "2020-08-13T13:04:23+00:00" + "time": "2021-09-29T15:39:33+00:00" }, { "name": "nette/finder", @@ -1603,20 +1603,19 @@ }, { "name": "nette/neon", - "version": "v3.2.1", + "version": "v3.2.2", "source": { "type": "git", "url": "https://github.com/nette/neon.git", - "reference": "a5b3a60833d2ef55283a82d0c30b45d136b29e75" + "reference": "e4ca6f4669121ca6876b1d048c612480e39a28d5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/neon/zipball/a5b3a60833d2ef55283a82d0c30b45d136b29e75", - "reference": "a5b3a60833d2ef55283a82d0c30b45d136b29e75", + "url": "https://api.github.com/repos/nette/neon/zipball/e4ca6f4669121ca6876b1d048c612480e39a28d5", + "reference": "e4ca6f4669121ca6876b1d048c612480e39a28d5", "shasum": "" }, "require": { - "ext-iconv": "*", "ext-json": "*", "php": ">=7.1" }, @@ -1663,31 +1662,31 @@ ], "support": { "issues": "https://github.com/nette/neon/issues", - "source": "https://github.com/nette/neon/tree/master" + "source": "https://github.com/nette/neon/tree/v3.2.2" }, - "time": "2020-07-31T12:28:05+00:00" + "time": "2021-02-28T12:30:32+00:00" }, { "name": "nette/php-generator", - "version": "v3.5.0", + "version": "v3.6.2", "source": { "type": "git", "url": "https://github.com/nette/php-generator.git", - "reference": "9162f7455059755dcbece1b5570d1bbfc6f0ab0d" + "reference": "bce6abcd4090ab5eec24b78f26c753c6525a425c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/php-generator/zipball/9162f7455059755dcbece1b5570d1bbfc6f0ab0d", - "reference": "9162f7455059755dcbece1b5570d1bbfc6f0ab0d", + "url": "https://api.github.com/repos/nette/php-generator/zipball/bce6abcd4090ab5eec24b78f26c753c6525a425c", + "reference": "bce6abcd4090ab5eec24b78f26c753c6525a425c", "shasum": "" }, "require": { "nette/utils": "^3.1.2", - "php": ">=7.1" + "php": ">=7.2 <8.2" }, "require-dev": { - "nette/tester": "^2.0", - "nikic/php-parser": "^4.4", + "nette/tester": "^2.4", + "nikic/php-parser": "^4.11", "phpstan/phpstan": "^0.12", "tracy/tracy": "^2.3" }, @@ -1697,7 +1696,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.5-dev" + "dev-master": "3.6-dev" } }, "autoload": { @@ -1721,7 +1720,7 @@ "homepage": "https://nette.org/contributors" } ], - "description": "🐘 Nette PHP Generator: generates neat PHP code for you. Supports new PHP 7.4 features.", + "description": "🐘 Nette PHP Generator: generates neat PHP code for you. Supports new PHP 8.1 features.", "homepage": "https://nette.org", "keywords": [ "code", @@ -1731,22 +1730,22 @@ ], "support": { "issues": "https://github.com/nette/php-generator/issues", - "source": "https://github.com/nette/php-generator/tree/v3.5.0" + "source": "https://github.com/nette/php-generator/tree/v3.6.2" }, - "time": "2020-11-02T16:16:58+00:00" + "time": "2021-09-23T21:48:24+00:00" }, { "name": "nette/robot-loader", - "version": "v3.3.1", + "version": "v3.4.1", "source": { "type": "git", "url": "https://github.com/nette/robot-loader.git", - "reference": "15c1ecd0e6e69e8d908dfc4cca7b14f3b850a96b" + "reference": "e2adc334cb958164c050f485d99c44c430f51fe2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/robot-loader/zipball/15c1ecd0e6e69e8d908dfc4cca7b14f3b850a96b", - "reference": "15c1ecd0e6e69e8d908dfc4cca7b14f3b850a96b", + "url": "https://api.github.com/repos/nette/robot-loader/zipball/e2adc334cb958164c050f485d99c44c430f51fe2", + "reference": "e2adc334cb958164c050f485d99c44c430f51fe2", "shasum": "" }, "require": { @@ -1763,7 +1762,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "3.4-dev" } }, "autoload": { @@ -1798,36 +1797,38 @@ ], "support": { "issues": "https://github.com/nette/robot-loader/issues", - "source": "https://github.com/nette/robot-loader/tree/v3.3.1" + "source": "https://github.com/nette/robot-loader/tree/v3.4.1" }, - "time": "2020-09-15T15:14:17+00:00" + "time": "2021-08-25T15:53:54+00:00" }, { "name": "nette/schema", - "version": "v1.0.2", + "version": "v1.2.1", "source": { "type": "git", "url": "https://github.com/nette/schema.git", - "reference": "febf71fb4052c824046f5a33f4f769a6e7fa0cb4" + "reference": "f5ed39fc96358f922cedfd1e516f0dadf5d2be0d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/febf71fb4052c824046f5a33f4f769a6e7fa0cb4", - "reference": "febf71fb4052c824046f5a33f4f769a6e7fa0cb4", + "url": "https://api.github.com/repos/nette/schema/zipball/f5ed39fc96358f922cedfd1e516f0dadf5d2be0d", + "reference": "f5ed39fc96358f922cedfd1e516f0dadf5d2be0d", "shasum": "" }, "require": { - "nette/utils": "^3.1", - "php": ">=7.1" + "nette/utils": "^3.1.4 || ^4.0", + "php": ">=7.1 <8.1" }, "require-dev": { - "nette/tester": "^2.2", + "nette/tester": "^2.3 || ^2.4", "phpstan/phpstan-nette": "^0.12", - "tracy/tracy": "^2.3" + "tracy/tracy": "^2.7" }, "type": "library", "extra": { - "branch-alias": [] + "branch-alias": { + "dev-master": "1.2-dev" + } }, "autoload": { "classmap": [ @@ -1837,8 +1838,8 @@ "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause", - "GPL-2.0", - "GPL-3.0" + "GPL-2.0-only", + "GPL-3.0-only" ], "authors": [ { @@ -1858,26 +1859,29 @@ ], "support": { "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.0.2" + "source": "https://github.com/nette/schema/tree/v1.2.1" }, - "time": "2020-01-06T22:52:48+00:00" + "time": "2021-03-04T17:51:11+00:00" }, { "name": "nette/utils", - "version": "v3.1.3", + "version": "v3.2.5", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "c09937fbb24987b2a41c6022ebe84f4f1b8eec0f" + "reference": "9cd80396ca58d7969ab44fc7afcf03624dfa526e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/c09937fbb24987b2a41c6022ebe84f4f1b8eec0f", - "reference": "c09937fbb24987b2a41c6022ebe84f4f1b8eec0f", + "url": "https://api.github.com/repos/nette/utils/zipball/9cd80396ca58d7969ab44fc7afcf03624dfa526e", + "reference": "9cd80396ca58d7969ab44fc7afcf03624dfa526e", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2 <8.2" + }, + "conflict": { + "nette/di": "<3.0.6" }, "require-dev": { "nette/tester": "~2.0", @@ -1896,7 +1900,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-master": "3.2-dev" } }, "autoload": { @@ -1920,7 +1924,7 @@ "homepage": "https://nette.org/contributors" } ], - "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", "homepage": "https://nette.org", "keywords": [ "array", @@ -1940,9 +1944,9 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v3.1.3" + "source": "https://github.com/nette/utils/tree/v3.2.5" }, - "time": "2020-08-07T10:34:21+00:00" + "time": "2021-09-20T10:50:11+00:00" }, { "name": "nikic/php-parser", diff --git a/phpcs.xml b/phpcs.xml index b3b5bb5651..f03e0dfad3 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -49,6 +49,7 @@ + src/Command/CommandHelper.php diff --git a/src/DependencyInjection/Configurator.php b/src/DependencyInjection/Configurator.php index 1da912305f..9f3fd8d7f0 100644 --- a/src/DependencyInjection/Configurator.php +++ b/src/DependencyInjection/Configurator.php @@ -5,7 +5,7 @@ use Nette\DI\Config\Loader; use Nette\DI\ContainerLoader; -class Configurator extends \Nette\Configurator +class Configurator extends \Nette\Bootstrap\Configurator { private LoaderFactory $loaderFactory; @@ -39,12 +39,12 @@ public function loadContainer(): string { $loader = new ContainerLoader( $this->getContainerCacheDirectory(), - $this->parameters['debugMode'] + $this->staticParameters['debugMode'] ); return $loader->load( [$this, 'generateContainer'], - [$this->parameters, array_keys($this->dynamicParameters), $this->configs, PHP_VERSION_ID - PHP_RELEASE_VERSION, NeonAdapter::CACHE_KEY] + [$this->staticParameters, array_keys($this->dynamicParameters), $this->configs, PHP_VERSION_ID - PHP_RELEASE_VERSION, NeonAdapter::CACHE_KEY] ); } diff --git a/src/DependencyInjection/Nette/NetteContainer.php b/src/DependencyInjection/Nette/NetteContainer.php index 5e57025649..1e0632ca65 100644 --- a/src/DependencyInjection/Nette/NetteContainer.php +++ b/src/DependencyInjection/Nette/NetteContainer.php @@ -32,8 +32,9 @@ public function getService(string $serviceName) } /** - * @param string $className - * @return mixed + * @template T of object + * @param class-string $className + * @return T */ public function getByType(string $className) { diff --git a/src/DependencyInjection/RulesExtension.php b/src/DependencyInjection/RulesExtension.php index b72f286754..f672ecffbd 100644 --- a/src/DependencyInjection/RulesExtension.php +++ b/src/DependencyInjection/RulesExtension.php @@ -2,6 +2,7 @@ namespace PHPStan\DependencyInjection; +use Nette\DI\Definitions\ServiceDefinition; use Nette\Schema\Expect; use PHPStan\Rules\RegistryFactory; @@ -20,8 +21,9 @@ public function loadConfiguration(): void $builder = $this->getContainerBuilder(); foreach ($config as $key => $rule) { - $builder->addDefinition($this->prefix((string) $key)) - ->setFactory($rule) + /** @var ServiceDefinition $definition */ + $definition = $builder->addDefinition($this->prefix((string) $key)); + $definition->setFactory($rule) ->setAutowired(false) ->addTag(RegistryFactory::RULE_TAG); } From aee55441dc12dabacf4d9e20f6672d061ac0f98f Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 3 Oct 2021 20:22:11 +0200 Subject: [PATCH 0402/1284] more precise func_* types --- resources/functionMap.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index a0169e3562..0088e20ab5 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -3051,9 +3051,9 @@ 'ftp_ssl_connect' => ['resource|false', 'host'=>'string', 'port='=>'int', 'timeout='=>'int'], 'ftp_systype' => ['string|false', 'stream'=>'resource'], 'ftruncate' => ['bool', 'fp'=>'resource', 'size'=>'int'], -'func_get_arg' => ['mixed', 'arg_num'=>'int'], +'func_get_arg' => ['mixed', 'arg_num'=>'0|positive-int'], 'func_get_args' => ['array'], -'func_num_args' => ['int'], +'func_num_args' => ['0|positive-int'], 'function_exists' => ['bool', 'function_name'=>'string'], 'fwrite' => ['int|false', 'fp'=>'resource', 'str'=>'string', 'length='=>'int'], 'gc_collect_cycles' => ['int'], From 1f63ad0ec6ac7a3a9d493b730d7c0a49383a4a1c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 4 Oct 2021 10:51:46 +0200 Subject: [PATCH 0403/1284] Revert "Update Nette dependencies" This reverts commit 884b9221bd9235ced7a2a0b0bb8508408682d963. --- composer.json | 10 +- composer.lock | 144 +++++++++--------- phpcs.xml | 1 - src/DependencyInjection/Configurator.php | 6 +- .../Nette/NetteContainer.php | 5 +- src/DependencyInjection/RulesExtension.php | 6 +- 6 files changed, 82 insertions(+), 90 deletions(-) diff --git a/composer.json b/composer.json index 9b7ac41a53..88aaae4948 100644 --- a/composer.json +++ b/composer.json @@ -15,12 +15,12 @@ "hoa/regex": "1.17.01.13", "jean85/pretty-package-versions": "^1.0.3", "jetbrains/phpstorm-stubs": "dev-master#82595d7a426c4b3d1e3a7d604ad3f99534784599", - "nette/bootstrap": "^3.1.1", - "nette/di": "^3.0.10", + "nette/bootstrap": "^3.0", + "nette/di": "^3.0.5", "nette/finder": "^2.5", - "nette/neon": "^3.2.2", - "nette/schema": "^1.2.1", - "nette/utils": "^3.2.5", + "nette/neon": "^3.0", + "nette/schema": "^1.0", + "nette/utils": "^3.1.3", "nikic/php-parser": "4.13.0", "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "4.3.70", diff --git a/composer.lock b/composer.lock index 9286e90391..5e67ba1ccf 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "37bbe438b63cc6628dbdf725d25a3640", + "content-hash": "c1d91467e312e364a2d0be0d50163a50", "packages": [ { "name": "clue/block-react", @@ -1380,29 +1380,29 @@ }, { "name": "nette/bootstrap", - "version": "v3.1.1", + "version": "v3.0.2", "source": { "type": "git", "url": "https://github.com/nette/bootstrap.git", - "reference": "efe6c30fc009451f59fe56f3b309eb85c48b2baf" + "reference": "67830a65b42abfb906f8e371512d336ebfb5da93" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/bootstrap/zipball/efe6c30fc009451f59fe56f3b309eb85c48b2baf", - "reference": "efe6c30fc009451f59fe56f3b309eb85c48b2baf", + "url": "https://api.github.com/repos/nette/bootstrap/zipball/67830a65b42abfb906f8e371512d336ebfb5da93", + "reference": "67830a65b42abfb906f8e371512d336ebfb5da93", "shasum": "" }, "require": { - "nette/di": "^3.0.5", - "nette/utils": "^3.2.1", - "php": ">=7.2 <8.1" + "nette/di": "^3.0", + "nette/utils": "^3.0", + "php": ">=7.1" }, "conflict": { "tracy/tracy": "<2.6" }, "require-dev": { "latte/latte": "^2.2", - "nette/application": "^3.1", + "nette/application": "^3.0", "nette/caching": "^3.0", "nette/database": "^3.0", "nette/forms": "^3.0", @@ -1422,7 +1422,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -1446,7 +1446,7 @@ "homepage": "https://nette.org/contributors" } ], - "description": "🅱 Nette Bootstrap: the simple way to configure and bootstrap your Nette application.", + "description": "🅱 Nette Bootstrap: the simple way to configure and bootstrap your Nette application.", "homepage": "https://nette.org", "keywords": [ "bootstrapping", @@ -1455,22 +1455,22 @@ ], "support": { "issues": "https://github.com/nette/bootstrap/issues", - "source": "https://github.com/nette/bootstrap/tree/v3.1.1" + "source": "https://github.com/nette/bootstrap/tree/master" }, - "time": "2021-01-25T00:31:21+00:00" + "time": "2020-05-26T08:46:23+00:00" }, { "name": "nette/di", - "version": "v3.0.10", + "version": "v3.0.5", "source": { "type": "git", "url": "https://github.com/nette/di.git", - "reference": "4f0cb0b3f032a9106aa3fb29e33da381564716c2" + "reference": "766e8185196a97ded4f9128db6d79a3a124b7eb6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/di/zipball/4f0cb0b3f032a9106aa3fb29e33da381564716c2", - "reference": "4f0cb0b3f032a9106aa3fb29e33da381564716c2", + "url": "https://api.github.com/repos/nette/di/zipball/766e8185196a97ded4f9128db6d79a3a124b7eb6", + "reference": "766e8185196a97ded4f9128db6d79a3a124b7eb6", "shasum": "" }, "require": { @@ -1478,9 +1478,9 @@ "nette/neon": "^3.0", "nette/php-generator": "^3.3.3", "nette/robot-loader": "^3.2", - "nette/schema": "^1.1", - "nette/utils": "^3.1.6", - "php": ">=7.1 <8.2" + "nette/schema": "^1.0", + "nette/utils": "^3.1", + "php": ">=7.1" }, "conflict": { "nette/bootstrap": "<3.0" @@ -1517,7 +1517,7 @@ "homepage": "https://nette.org/contributors" } ], - "description": "💎 Nette Dependency Injection Container: Flexible, compiled and full-featured DIC with perfectly usable autowiring and support for all new PHP features.", + "description": "💎 Nette Dependency Injection Container: Flexible, compiled and full-featured DIC with perfectly usable autowiring and support for all new PHP 7.1 features.", "homepage": "https://nette.org", "keywords": [ "compiled", @@ -1530,9 +1530,9 @@ ], "support": { "issues": "https://github.com/nette/di/issues", - "source": "https://github.com/nette/di/tree/v3.0.10" + "source": "https://github.com/nette/di/tree/master" }, - "time": "2021-09-29T15:39:33+00:00" + "time": "2020-08-13T13:04:23+00:00" }, { "name": "nette/finder", @@ -1603,19 +1603,20 @@ }, { "name": "nette/neon", - "version": "v3.2.2", + "version": "v3.2.1", "source": { "type": "git", "url": "https://github.com/nette/neon.git", - "reference": "e4ca6f4669121ca6876b1d048c612480e39a28d5" + "reference": "a5b3a60833d2ef55283a82d0c30b45d136b29e75" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/neon/zipball/e4ca6f4669121ca6876b1d048c612480e39a28d5", - "reference": "e4ca6f4669121ca6876b1d048c612480e39a28d5", + "url": "https://api.github.com/repos/nette/neon/zipball/a5b3a60833d2ef55283a82d0c30b45d136b29e75", + "reference": "a5b3a60833d2ef55283a82d0c30b45d136b29e75", "shasum": "" }, "require": { + "ext-iconv": "*", "ext-json": "*", "php": ">=7.1" }, @@ -1662,31 +1663,31 @@ ], "support": { "issues": "https://github.com/nette/neon/issues", - "source": "https://github.com/nette/neon/tree/v3.2.2" + "source": "https://github.com/nette/neon/tree/master" }, - "time": "2021-02-28T12:30:32+00:00" + "time": "2020-07-31T12:28:05+00:00" }, { "name": "nette/php-generator", - "version": "v3.6.2", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/nette/php-generator.git", - "reference": "bce6abcd4090ab5eec24b78f26c753c6525a425c" + "reference": "9162f7455059755dcbece1b5570d1bbfc6f0ab0d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/php-generator/zipball/bce6abcd4090ab5eec24b78f26c753c6525a425c", - "reference": "bce6abcd4090ab5eec24b78f26c753c6525a425c", + "url": "https://api.github.com/repos/nette/php-generator/zipball/9162f7455059755dcbece1b5570d1bbfc6f0ab0d", + "reference": "9162f7455059755dcbece1b5570d1bbfc6f0ab0d", "shasum": "" }, "require": { "nette/utils": "^3.1.2", - "php": ">=7.2 <8.2" + "php": ">=7.1" }, "require-dev": { - "nette/tester": "^2.4", - "nikic/php-parser": "^4.11", + "nette/tester": "^2.0", + "nikic/php-parser": "^4.4", "phpstan/phpstan": "^0.12", "tracy/tracy": "^2.3" }, @@ -1696,7 +1697,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.6-dev" + "dev-master": "3.5-dev" } }, "autoload": { @@ -1720,7 +1721,7 @@ "homepage": "https://nette.org/contributors" } ], - "description": "🐘 Nette PHP Generator: generates neat PHP code for you. Supports new PHP 8.1 features.", + "description": "🐘 Nette PHP Generator: generates neat PHP code for you. Supports new PHP 7.4 features.", "homepage": "https://nette.org", "keywords": [ "code", @@ -1730,22 +1731,22 @@ ], "support": { "issues": "https://github.com/nette/php-generator/issues", - "source": "https://github.com/nette/php-generator/tree/v3.6.2" + "source": "https://github.com/nette/php-generator/tree/v3.5.0" }, - "time": "2021-09-23T21:48:24+00:00" + "time": "2020-11-02T16:16:58+00:00" }, { "name": "nette/robot-loader", - "version": "v3.4.1", + "version": "v3.3.1", "source": { "type": "git", "url": "https://github.com/nette/robot-loader.git", - "reference": "e2adc334cb958164c050f485d99c44c430f51fe2" + "reference": "15c1ecd0e6e69e8d908dfc4cca7b14f3b850a96b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/robot-loader/zipball/e2adc334cb958164c050f485d99c44c430f51fe2", - "reference": "e2adc334cb958164c050f485d99c44c430f51fe2", + "url": "https://api.github.com/repos/nette/robot-loader/zipball/15c1ecd0e6e69e8d908dfc4cca7b14f3b850a96b", + "reference": "15c1ecd0e6e69e8d908dfc4cca7b14f3b850a96b", "shasum": "" }, "require": { @@ -1762,7 +1763,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.4-dev" + "dev-master": "3.3-dev" } }, "autoload": { @@ -1797,38 +1798,36 @@ ], "support": { "issues": "https://github.com/nette/robot-loader/issues", - "source": "https://github.com/nette/robot-loader/tree/v3.4.1" + "source": "https://github.com/nette/robot-loader/tree/v3.3.1" }, - "time": "2021-08-25T15:53:54+00:00" + "time": "2020-09-15T15:14:17+00:00" }, { "name": "nette/schema", - "version": "v1.2.1", + "version": "v1.0.2", "source": { "type": "git", "url": "https://github.com/nette/schema.git", - "reference": "f5ed39fc96358f922cedfd1e516f0dadf5d2be0d" + "reference": "febf71fb4052c824046f5a33f4f769a6e7fa0cb4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/f5ed39fc96358f922cedfd1e516f0dadf5d2be0d", - "reference": "f5ed39fc96358f922cedfd1e516f0dadf5d2be0d", + "url": "https://api.github.com/repos/nette/schema/zipball/febf71fb4052c824046f5a33f4f769a6e7fa0cb4", + "reference": "febf71fb4052c824046f5a33f4f769a6e7fa0cb4", "shasum": "" }, "require": { - "nette/utils": "^3.1.4 || ^4.0", - "php": ">=7.1 <8.1" + "nette/utils": "^3.1", + "php": ">=7.1" }, "require-dev": { - "nette/tester": "^2.3 || ^2.4", + "nette/tester": "^2.2", "phpstan/phpstan-nette": "^0.12", - "tracy/tracy": "^2.7" + "tracy/tracy": "^2.3" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.2-dev" - } + "branch-alias": [] }, "autoload": { "classmap": [ @@ -1838,8 +1837,8 @@ "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause", - "GPL-2.0-only", - "GPL-3.0-only" + "GPL-2.0", + "GPL-3.0" ], "authors": [ { @@ -1859,29 +1858,26 @@ ], "support": { "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.2.1" + "source": "https://github.com/nette/schema/tree/v1.0.2" }, - "time": "2021-03-04T17:51:11+00:00" + "time": "2020-01-06T22:52:48+00:00" }, { "name": "nette/utils", - "version": "v3.2.5", + "version": "v3.1.3", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "9cd80396ca58d7969ab44fc7afcf03624dfa526e" + "reference": "c09937fbb24987b2a41c6022ebe84f4f1b8eec0f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/9cd80396ca58d7969ab44fc7afcf03624dfa526e", - "reference": "9cd80396ca58d7969ab44fc7afcf03624dfa526e", + "url": "https://api.github.com/repos/nette/utils/zipball/c09937fbb24987b2a41c6022ebe84f4f1b8eec0f", + "reference": "c09937fbb24987b2a41c6022ebe84f4f1b8eec0f", "shasum": "" }, "require": { - "php": ">=7.2 <8.2" - }, - "conflict": { - "nette/di": "<3.0.6" + "php": ">=7.1" }, "require-dev": { "nette/tester": "~2.0", @@ -1900,7 +1896,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-master": "3.1-dev" } }, "autoload": { @@ -1924,7 +1920,7 @@ "homepage": "https://nette.org/contributors" } ], - "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", "homepage": "https://nette.org", "keywords": [ "array", @@ -1944,9 +1940,9 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v3.2.5" + "source": "https://github.com/nette/utils/tree/v3.1.3" }, - "time": "2021-09-20T10:50:11+00:00" + "time": "2020-08-07T10:34:21+00:00" }, { "name": "nikic/php-parser", diff --git a/phpcs.xml b/phpcs.xml index f03e0dfad3..b3b5bb5651 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -49,7 +49,6 @@ - src/Command/CommandHelper.php diff --git a/src/DependencyInjection/Configurator.php b/src/DependencyInjection/Configurator.php index 9f3fd8d7f0..1da912305f 100644 --- a/src/DependencyInjection/Configurator.php +++ b/src/DependencyInjection/Configurator.php @@ -5,7 +5,7 @@ use Nette\DI\Config\Loader; use Nette\DI\ContainerLoader; -class Configurator extends \Nette\Bootstrap\Configurator +class Configurator extends \Nette\Configurator { private LoaderFactory $loaderFactory; @@ -39,12 +39,12 @@ public function loadContainer(): string { $loader = new ContainerLoader( $this->getContainerCacheDirectory(), - $this->staticParameters['debugMode'] + $this->parameters['debugMode'] ); return $loader->load( [$this, 'generateContainer'], - [$this->staticParameters, array_keys($this->dynamicParameters), $this->configs, PHP_VERSION_ID - PHP_RELEASE_VERSION, NeonAdapter::CACHE_KEY] + [$this->parameters, array_keys($this->dynamicParameters), $this->configs, PHP_VERSION_ID - PHP_RELEASE_VERSION, NeonAdapter::CACHE_KEY] ); } diff --git a/src/DependencyInjection/Nette/NetteContainer.php b/src/DependencyInjection/Nette/NetteContainer.php index 1e0632ca65..5e57025649 100644 --- a/src/DependencyInjection/Nette/NetteContainer.php +++ b/src/DependencyInjection/Nette/NetteContainer.php @@ -32,9 +32,8 @@ public function getService(string $serviceName) } /** - * @template T of object - * @param class-string $className - * @return T + * @param string $className + * @return mixed */ public function getByType(string $className) { diff --git a/src/DependencyInjection/RulesExtension.php b/src/DependencyInjection/RulesExtension.php index f672ecffbd..b72f286754 100644 --- a/src/DependencyInjection/RulesExtension.php +++ b/src/DependencyInjection/RulesExtension.php @@ -2,7 +2,6 @@ namespace PHPStan\DependencyInjection; -use Nette\DI\Definitions\ServiceDefinition; use Nette\Schema\Expect; use PHPStan\Rules\RegistryFactory; @@ -21,9 +20,8 @@ public function loadConfiguration(): void $builder = $this->getContainerBuilder(); foreach ($config as $key => $rule) { - /** @var ServiceDefinition $definition */ - $definition = $builder->addDefinition($this->prefix((string) $key)); - $definition->setFactory($rule) + $builder->addDefinition($this->prefix((string) $key)) + ->setFactory($rule) ->setAutowired(false) ->addTag(RegistryFactory::RULE_TAG); } From 286b2c7a08ae4cb9c05bfd6186b4fa1917a87dea Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 29 Sep 2021 14:24:54 +0200 Subject: [PATCH 0404/1284] use more precise types in file IO functions Update resources/functionMap.php --- resources/functionMap.php | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 0088e20ab5..7875a7423c 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -2937,13 +2937,13 @@ 'ffmpeg_movie::hasAudio' => ['bool'], 'ffmpeg_movie::hasVideo' => ['bool'], 'fgetc' => ['string|false', 'fp'=>'resource'], -'fgetcsv' => ['(?array)|(?false)', 'fp'=>'resource', 'length='=>'int', 'delimiter='=>'string', 'enclosure='=>'string', 'escape='=>'string'], -'fgets' => ['string|false', 'fp'=>'resource', 'length='=>'int'], -'fgetss' => ['string|false', 'fp'=>'resource', 'length='=>'int', 'allowable_tags='=>'string'], +'fgetcsv' => ['(?array)|(?false)', 'fp'=>'resource', 'length='=>'0|positive-int', 'delimiter='=>'string', 'enclosure='=>'string', 'escape='=>'string'], +'fgets' => ['string|false', 'fp'=>'resource', 'length='=>'0|positive-int'], +'fgetss' => ['string|false', 'fp'=>'resource', 'length='=>'0|positive-int', 'allowable_tags='=>'string'], 'file' => ['array|false', 'filename'=>'string', 'flags='=>'int', 'context='=>'resource'], 'file_exists' => ['bool', 'filename'=>'string'], -'file_get_contents' => ['string|false', 'filename'=>'string', 'use_include_path='=>'bool', 'context='=>'?resource', 'offset='=>'int', 'maxlen='=>'int'], -'file_put_contents' => ['int|false', 'file'=>'string', 'data'=>'mixed', 'flags='=>'int', 'context='=>'?resource'], +'file_get_contents' => ['string|false', 'filename'=>'string', 'use_include_path='=>'bool', 'context='=>'?resource', 'offset='=>'int', 'maxlen='=>'0|positive-int'], +'file_put_contents' => ['0|positive-int|false', 'file'=>'string', 'data'=>'mixed', 'flags='=>'int', 'context='=>'?resource'], 'fileatime' => ['int|false', 'filename'=>'string'], 'filectime' => ['int|false', 'filename'=>'string'], 'filegroup' => ['int|false', 'filename'=>'string'], @@ -2958,7 +2958,7 @@ 'filepro_fieldwidth' => ['int', 'field_number'=>'int'], 'filepro_retrieve' => ['string', 'row_number'=>'int', 'field_number'=>'int'], 'filepro_rowcount' => ['int'], -'filesize' => ['int|false', 'filename'=>'string'], +'filesize' => ['0|positive-int|false', 'filename'=>'string'], 'FilesystemIterator::__construct' => ['void', 'path'=>'string', 'flags='=>'int'], 'FilesystemIterator::current' => ['string|SplFileInfo'], 'FilesystemIterator::getFlags' => ['int'], @@ -3001,16 +3001,16 @@ 'fopen' => ['resource|false', 'filename'=>'string', 'mode'=>'string', 'use_include_path='=>'bool', 'context='=>'resource'], 'forward_static_call' => ['mixed', 'function'=>'callable', '...parameters='=>'mixed'], 'forward_static_call_array' => ['mixed', 'function'=>'callable', 'parameters'=>'array'], -'fpassthru' => ['int|false', 'fp'=>'resource'], +'fpassthru' => ['0|positive-int|false', 'fp'=>'resource'], 'fpm_get_status' => ['array|false'], 'fprintf' => ['int', 'stream'=>'resource', 'format'=>'string', '...values='=>'string|int|float'], -'fputcsv' => ['int|false', 'fp'=>'resource', 'fields'=>'array', 'delimiter='=>'string', 'enclosure='=>'string', 'escape_char='=>'string'], -'fputs' => ['int|false', 'fp'=>'resource', 'str'=>'string', 'length='=>'int'], -'fread' => ['string|false', 'fp'=>'resource', 'length'=>'int'], +'fputcsv' => ['0|positive-int|false', 'fp'=>'resource', 'fields'=>'array', 'delimiter='=>'string', 'enclosure='=>'string', 'escape_char='=>'string'], +'fputs' => ['0|positive-int|false', 'fp'=>'resource', 'str'=>'string', 'length='=>'0|positive-int'], +'fread' => ['string|false', 'fp'=>'resource', 'length'=>'0|positive-int'], 'frenchtojd' => ['int', 'month'=>'int', 'day'=>'int', 'year'=>'int'], 'fribidi_log2vis' => ['string', 'str'=>'string', 'direction'=>'string', 'charset'=>'int'], 'fscanf' => ['array|int|false', 'stream'=>'resource', 'format'=>'string', '&...w_vars='=>'string|int|float|null'], -'fseek' => ['int', 'fp'=>'resource', 'offset'=>'int', 'whence='=>'int'], +'fseek' => ['0|-1', 'fp'=>'resource', 'offset'=>'int', 'whence='=>'int'], 'fsockopen' => ['resource|false', 'hostname'=>'string', 'port='=>'int', '&w_errno='=>'int', '&w_errstr='=>'string', 'timeout='=>'float'], 'fstat' => ['array|false', 'fp'=>'resource'], 'ftell' => ['int|false', 'fp'=>'resource'], @@ -3050,12 +3050,12 @@ 'ftp_size' => ['int', 'stream'=>'resource', 'filename'=>'string'], 'ftp_ssl_connect' => ['resource|false', 'host'=>'string', 'port='=>'int', 'timeout='=>'int'], 'ftp_systype' => ['string|false', 'stream'=>'resource'], -'ftruncate' => ['bool', 'fp'=>'resource', 'size'=>'int'], +'ftruncate' => ['bool', 'fp'=>'resource', 'size'=>'0|positive-int'], 'func_get_arg' => ['mixed', 'arg_num'=>'0|positive-int'], 'func_get_args' => ['array'], 'func_num_args' => ['0|positive-int'], 'function_exists' => ['bool', 'function_name'=>'string'], -'fwrite' => ['int|false', 'fp'=>'resource', 'str'=>'string', 'length='=>'int'], +'fwrite' => ['0|positive-int|false', 'fp'=>'resource', 'str'=>'string', 'length='=>'0|positive-int'], 'gc_collect_cycles' => ['int'], 'gc_disable' => ['void'], 'gc_enable' => ['void'], @@ -9123,8 +9123,8 @@ 'rawurlencode' => ['string', 'str'=>'string'], 'read_exif_data' => ['array', 'filename'=>'string|resource', 'sections_needed='=>'string', 'sub_arrays='=>'bool', 'read_thumbnail='=>'bool'], 'readdir' => ['string|false', 'dir_handle='=>'resource'], -'readfile' => ['int|false', 'filename'=>'string', 'use_include_path='=>'bool', 'context='=>'resource'], -'readgzfile' => ['int|false', 'filename'=>'string', 'use_include_path='=>'int'], +'readfile' => ['0|positive-int|false', 'filename'=>'string', 'use_include_path='=>'bool', 'context='=>'resource'], +'readgzfile' => ['0|positive-int|false', 'filename'=>'string', 'use_include_path='=>'int'], 'readline' => ['string|false', 'prompt='=>'?string'], 'readline_add_history' => ['bool', 'prompt'=>'string'], 'readline_callback_handler_install' => ['bool', 'prompt'=>'string', 'callback'=>'callable'], From c1812e7e617aa9f228bf51b668e98591d2b1a1f4 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 4 Oct 2021 18:02:17 +0200 Subject: [PATCH 0405/1284] Skip processing inline `@var` casting for property --- src/Analyser/NodeScopeResolver.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index cbde3f80ee..2b3125ed6c 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -359,6 +359,8 @@ private function processStmtNode( !$stmt instanceof Static_ && !$stmt instanceof Foreach_ && !$stmt instanceof Node\Stmt\Global_ + && !$stmt instanceof Node\Stmt\Property + && !$stmt instanceof Node\Stmt\PropertyProperty ) { $scope = $this->processStmtVarAnnotation($scope, $stmt, null); } From 25cb765aca4c9a3ab05fee99ff3295b61ccaf7c0 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 4 Oct 2021 18:44:13 +0200 Subject: [PATCH 0406/1284] This caused bad memory leak --- .../Reflector/MemoizingClassReflector.php | 12 ++---------- .../Reflector/MemoizingConstantReflector.php | 12 ++---------- .../Reflector/MemoizingFunctionReflector.php | 12 ++---------- 3 files changed, 6 insertions(+), 30 deletions(-) diff --git a/src/Reflection/BetterReflection/Reflector/MemoizingClassReflector.php b/src/Reflection/BetterReflection/Reflector/MemoizingClassReflector.php index d34a77a86f..8aeddfbadb 100644 --- a/src/Reflection/BetterReflection/Reflector/MemoizingClassReflector.php +++ b/src/Reflection/BetterReflection/Reflector/MemoizingClassReflector.php @@ -8,7 +8,7 @@ final class MemoizingClassReflector extends ClassReflector { - /** @var array */ + /** @var array */ private array $reflections = []; /** @@ -22,18 +22,10 @@ public function reflect(string $className): Reflection { $lowerClassName = strtolower($className); if (isset($this->reflections[$lowerClassName])) { - if ($this->reflections[$lowerClassName] instanceof \Throwable) { - throw $this->reflections[$lowerClassName]; - } return $this->reflections[$lowerClassName]; } - try { - return $this->reflections[$lowerClassName] = parent::reflect($className); - } catch (\Throwable $e) { - $this->reflections[$lowerClassName] = $e; - throw $e; - } + return $this->reflections[$lowerClassName] = parent::reflect($className); } } diff --git a/src/Reflection/BetterReflection/Reflector/MemoizingConstantReflector.php b/src/Reflection/BetterReflection/Reflector/MemoizingConstantReflector.php index aca5db5d44..312f9a8af2 100644 --- a/src/Reflection/BetterReflection/Reflector/MemoizingConstantReflector.php +++ b/src/Reflection/BetterReflection/Reflector/MemoizingConstantReflector.php @@ -8,7 +8,7 @@ final class MemoizingConstantReflector extends ConstantReflector { - /** @var array */ + /** @var array */ private array $reflections = []; /** @@ -21,18 +21,10 @@ final class MemoizingConstantReflector extends ConstantReflector public function reflect(string $constantName): Reflection { if (isset($this->reflections[$constantName])) { - if ($this->reflections[$constantName] instanceof \Throwable) { - throw $this->reflections[$constantName]; - } return $this->reflections[$constantName]; } - try { - return $this->reflections[$constantName] = parent::reflect($constantName); - } catch (\Throwable $e) { - $this->reflections[$constantName] = $e; - throw $e; - } + return $this->reflections[$constantName] = parent::reflect($constantName); } } diff --git a/src/Reflection/BetterReflection/Reflector/MemoizingFunctionReflector.php b/src/Reflection/BetterReflection/Reflector/MemoizingFunctionReflector.php index 078392ef1b..7fa5e1ba7a 100644 --- a/src/Reflection/BetterReflection/Reflector/MemoizingFunctionReflector.php +++ b/src/Reflection/BetterReflection/Reflector/MemoizingFunctionReflector.php @@ -8,7 +8,7 @@ final class MemoizingFunctionReflector extends FunctionReflector { - /** @var array */ + /** @var array */ private array $reflections = []; /** @@ -22,18 +22,10 @@ public function reflect(string $functionName): Reflection { $lowerFunctionName = strtolower($functionName); if (isset($this->reflections[$lowerFunctionName])) { - if ($this->reflections[$lowerFunctionName] instanceof \Throwable) { - throw $this->reflections[$lowerFunctionName]; - } return $this->reflections[$lowerFunctionName]; } - try { - return $this->reflections[$lowerFunctionName] = parent::reflect($functionName); - } catch (\Throwable $e) { - $this->reflections[$lowerFunctionName] = $e; - throw $e; - } + return $this->reflections[$lowerFunctionName] = parent::reflect($functionName); } } From d781e34a35e1dd6d6918b3160edae8d93b9fae6b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 4 Oct 2021 21:12:29 +0200 Subject: [PATCH 0407/1284] Improve memory consumption --- conf/config.neon | 6 +++ src/Command/CommandHelper.php | 9 +++- src/Parser/CleaningParser.php | 42 +++++++++++++++++++ src/Parser/CleaningVisitor.php | 31 ++++++++++++++ src/Parser/PathRoutingParser.php | 15 +++++++ src/PhpDoc/StubValidator.php | 3 ++ .../Php/PhpClassReflectionExtension.php | 2 +- src/Reflection/Php/PhpFunctionReflection.php | 2 +- src/Reflection/Php/PhpMethodReflection.php | 2 +- src/Testing/TestCase.neon | 2 + tests/PHPStan/Parser/CachedParserTest.php | 10 ++++- 11 files changed, 118 insertions(+), 6 deletions(-) create mode 100644 src/Parser/CleaningParser.php create mode 100644 src/Parser/CleaningVisitor.php diff --git a/conf/config.neon b/conf/config.neon index f74919aafc..da89b51574 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1514,6 +1514,12 @@ services: autowired: no currentPhpVersionSimpleParser: + class: PHPStan\Parser\CleaningParser + arguments: + wrappedParser: @currentPhpVersionSimpleDirectParser + autowired: no + + currentPhpVersionSimpleDirectParser: class: PHPStan\Parser\SimpleParser arguments: parser: @currentPhpVersionPhpParser diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php index c21d8d1843..e784a0aef8 100644 --- a/src/Command/CommandHelper.php +++ b/src/Command/CommandHelper.php @@ -375,11 +375,16 @@ public static function begin( /** @var FileFinder $fileFinder */ $fileFinder = $container->getService('fileFinderAnalyse'); + $pathRoutingParser = $container->getService('pathRoutingParser'); + /** @var \Closure(): (array{string[], bool}) $filesCallback */ - $filesCallback = static function () use ($fileFinder, $paths): array { + $filesCallback = static function () use ($fileFinder, $pathRoutingParser, $paths): array { $fileFinderResult = $fileFinder->findFiles($paths); + $files = $fileFinderResult->getFiles(); + + $pathRoutingParser->setAnalysedFiles($files); - return [$fileFinderResult->getFiles(), $fileFinderResult->isOnlyFiles()]; + return [$files, $fileFinderResult->isOnlyFiles()]; }; return new InceptionResult( diff --git a/src/Parser/CleaningParser.php b/src/Parser/CleaningParser.php new file mode 100644 index 0000000000..844fdd71b1 --- /dev/null +++ b/src/Parser/CleaningParser.php @@ -0,0 +1,42 @@ +wrappedParser = $wrappedParser; + $this->traverser = new NodeTraverser(); + $this->traverser->addVisitor(new CleaningVisitor()); + } + + public function parseFile(string $file): array + { + return $this->clean($this->wrappedParser->parseFile($file)); + } + + public function parseString(string $sourceCode): array + { + return $this->clean($this->wrappedParser->parseString($sourceCode)); + } + + /** + * @param Stmt[] $ast + * @return Stmt[] + */ + private function clean(array $ast): array + { + /** @var Stmt[] */ + return $this->traverser->traverse($ast); + } + +} diff --git a/src/Parser/CleaningVisitor.php b/src/Parser/CleaningVisitor.php new file mode 100644 index 0000000000..e6f832e706 --- /dev/null +++ b/src/Parser/CleaningVisitor.php @@ -0,0 +1,31 @@ +stmts = []; + return $node; + } + + if ($node instanceof Node\Stmt\ClassMethod) { + $node->stmts = []; + return $node; + } + + if ($node instanceof Node\Expr\Closure) { + $node->stmts = []; + return $node; + } + + return null; + } + +} diff --git a/src/Parser/PathRoutingParser.php b/src/Parser/PathRoutingParser.php index 7fa5f6aba3..147844fdb6 100644 --- a/src/Parser/PathRoutingParser.php +++ b/src/Parser/PathRoutingParser.php @@ -15,6 +15,9 @@ class PathRoutingParser implements Parser private Parser $php8Parser; + /** @var bool[] filePath(string) => bool(true) */ + private array $analysedFiles = []; + public function __construct( FileHelper $fileHelper, Parser $currentPhpVersionRichParser, @@ -28,6 +31,14 @@ public function __construct( $this->php8Parser = $php8Parser; } + /** + * @param string[] $files + */ + public function setAnalysedFiles(array $files): void + { + $this->analysedFiles = array_fill_keys($files, true); + } + public function parseFile(string $file): array { $file = $this->fileHelper->normalizePath($file, '/'); @@ -35,6 +46,10 @@ public function parseFile(string $file): array return $this->php8Parser->parseFile($file); } + if (!isset($this->analysedFiles[$file])) { + return $this->currentPhpVersionSimpleParser->parseFile($file); + } + return $this->currentPhpVersionRichParser->parseFile($file); } diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index 3e7ad505af..41e3c06cd8 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -89,6 +89,9 @@ public function validate(array $stubFiles, bool $debug): array $nodeScopeResolver = $container->getByType(NodeScopeResolver::class); $nodeScopeResolver->setAnalysedFiles($stubFiles); + $pathRoutingParser = $container->getService('pathRoutingParser'); + $pathRoutingParser->setAnalysedFiles($stubFiles); + $analysedFiles = array_fill_keys($stubFiles, true); $errors = []; diff --git a/src/Reflection/Php/PhpClassReflectionExtension.php b/src/Reflection/Php/PhpClassReflectionExtension.php index 0692f245c4..a03bb4a0e0 100644 --- a/src/Reflection/Php/PhpClassReflectionExtension.php +++ b/src/Reflection/Php/PhpClassReflectionExtension.php @@ -935,7 +935,7 @@ private function inferAndCachePropertyTypes( } $methodNode = $this->findConstructorNode($constructor->getName(), $classNode->stmts); - if ($methodNode === null || $methodNode->stmts === null) { + if ($methodNode === null || $methodNode->stmts === null || count($methodNode->stmts) === 0) { return $this->propertyTypesCache[$declaringClass->getName()] = []; } diff --git a/src/Reflection/Php/PhpFunctionReflection.php b/src/Reflection/Php/PhpFunctionReflection.php index aa5710cfd3..8bdfba309d 100644 --- a/src/Reflection/Php/PhpFunctionReflection.php +++ b/src/Reflection/Php/PhpFunctionReflection.php @@ -170,7 +170,7 @@ private function isVariadic(): bool if ($modifiedTime === false) { $modifiedTime = time(); } - $variableCacheKey = sprintf('%d-v1', $modifiedTime); + $variableCacheKey = sprintf('%d-v2', $modifiedTime); $key = sprintf('variadic-function-%s-%s', $functionName, $fileName); $cachedResult = $this->cache->load($key, $variableCacheKey); if ($cachedResult === null) { diff --git a/src/Reflection/Php/PhpMethodReflection.php b/src/Reflection/Php/PhpMethodReflection.php index 5ca8163673..b3cccfba86 100644 --- a/src/Reflection/Php/PhpMethodReflection.php +++ b/src/Reflection/Php/PhpMethodReflection.php @@ -274,7 +274,7 @@ private function isVariadic(): bool $modifiedTime = time(); } $key = sprintf('variadic-method-%s-%s-%s', $declaringClass->getName(), $this->reflection->getName(), $filename); - $variableCacheKey = sprintf('%d-v2', $modifiedTime); + $variableCacheKey = sprintf('%d-v3', $modifiedTime); $cachedResult = $this->cache->load($key, $variableCacheKey); if ($cachedResult === null || !is_bool($cachedResult)) { $nodes = $this->parser->parseFile($filename); diff --git a/src/Testing/TestCase.neon b/src/Testing/TestCase.neon index 923de028e2..2098f2dad2 100644 --- a/src/Testing/TestCase.neon +++ b/src/Testing/TestCase.neon @@ -4,3 +4,5 @@ services: cacheStorage: class: PHPStan\Cache\MemoryCacheStorage arguments!: [] + pathRoutingParser!: + factory: @currentPhpVersionRichParser diff --git a/tests/PHPStan/Parser/CachedParserTest.php b/tests/PHPStan/Parser/CachedParserTest.php index 3624cc798d..221b26d685 100644 --- a/tests/PHPStan/Parser/CachedParserTest.php +++ b/tests/PHPStan/Parser/CachedParserTest.php @@ -3,6 +3,7 @@ namespace PHPStan\Parser; use PhpParser\Node\Stmt\Namespace_; +use PHPStan\File\FileHelper; use PHPStan\File\FileReader; use PHPStan\Testing\PHPStanTestCase; @@ -81,8 +82,15 @@ private function getPhpParserNodeMock(): \PhpParser\Node public function testParseTheSameFileWithDifferentMethod(): void { - $parser = new CachedParser(self::getContainer()->getService('pathRoutingParser'), 500); + $pathRoutingParser = new PathRoutingParser( + self::getContainer()->getByType(FileHelper::class), + self::getContainer()->getService('currentPhpVersionRichParser'), + self::getContainer()->getService('currentPhpVersionSimpleParser'), + self::getContainer()->getService('php8Parser') + ); + $parser = new CachedParser($pathRoutingParser, 500); $path = __DIR__ . '/data/test.php'; + $pathRoutingParser->setAnalysedFiles([$path]); $contents = FileReader::read($path); $stmts = $parser->parseString($contents); $this->assertInstanceOf(Namespace_::class, $stmts[0]); From f63c54ff4884955afc92d6035d4991d30dc1b78e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 4 Oct 2021 23:30:38 +0200 Subject: [PATCH 0408/1284] Fix --- src/Testing/TestCase.neon | 2 +- tests/PHPStan/Parser/CachedParserTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Testing/TestCase.neon b/src/Testing/TestCase.neon index 2098f2dad2..de27420e8b 100644 --- a/src/Testing/TestCase.neon +++ b/src/Testing/TestCase.neon @@ -4,5 +4,5 @@ services: cacheStorage: class: PHPStan\Cache\MemoryCacheStorage arguments!: [] - pathRoutingParser!: + currentPhpVersionSimpleParser!: factory: @currentPhpVersionRichParser diff --git a/tests/PHPStan/Parser/CachedParserTest.php b/tests/PHPStan/Parser/CachedParserTest.php index 221b26d685..f8fefca030 100644 --- a/tests/PHPStan/Parser/CachedParserTest.php +++ b/tests/PHPStan/Parser/CachedParserTest.php @@ -85,7 +85,7 @@ public function testParseTheSameFileWithDifferentMethod(): void $pathRoutingParser = new PathRoutingParser( self::getContainer()->getByType(FileHelper::class), self::getContainer()->getService('currentPhpVersionRichParser'), - self::getContainer()->getService('currentPhpVersionSimpleParser'), + self::getContainer()->getService('currentPhpVersionSimpleDirectParser'), self::getContainer()->getService('php8Parser') ); $parser = new CachedParser($pathRoutingParser, 500); From 67b6a922d8e6a6c72870fb2db9c316244b43954d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 4 Oct 2021 23:32:46 +0200 Subject: [PATCH 0409/1284] Revert A little bit more RAM for phpstan-static-reflection This reverts commit 05c5ad3c89b089ef3f931d0720be7df5b2370791. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 9d85ecb989..0bfa9d747e 100644 --- a/Makefile +++ b/Makefile @@ -53,7 +53,7 @@ phpstan: php bin/phpstan clear-result-cache -q && php -d memory_limit=768M bin/phpstan phpstan-static-reflection: - php bin/phpstan clear-result-cache -q && php -d memory_limit=800M bin/phpstan analyse -c phpstan-static-reflection.neon + php bin/phpstan clear-result-cache -q && php -d memory_limit=768M bin/phpstan analyse -c phpstan-static-reflection.neon phpstan-result-cache: php -d memory_limit=768M bin/phpstan From c650ed361f24244395991c6da730dfd21a1110e9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 4 Oct 2021 23:43:19 +0200 Subject: [PATCH 0410/1284] Fix --- src/Parser/CleaningVisitor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Parser/CleaningVisitor.php b/src/Parser/CleaningVisitor.php index e6f832e706..2afb48d309 100644 --- a/src/Parser/CleaningVisitor.php +++ b/src/Parser/CleaningVisitor.php @@ -15,7 +15,7 @@ public function enterNode(Node $node): ?Node return $node; } - if ($node instanceof Node\Stmt\ClassMethod) { + if ($node instanceof Node\Stmt\ClassMethod && $node->stmts !== null) { $node->stmts = []; return $node; } From a48c334819e9f78f6f713918b8115870ceacf10f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 4 Oct 2021 23:54:37 +0200 Subject: [PATCH 0411/1284] Fix CleaningParserTest for generators and variadics --- src/Parser/CleaningVisitor.php | 47 +++++++++++++++- src/Reflection/Php/PhpFunctionReflection.php | 2 +- src/Reflection/Php/PhpMethodReflection.php | 2 +- tests/PHPStan/Parser/CleaningParserTest.php | 46 +++++++++++++++ .../PHPStan/Parser/data/cleaning-1-after.php | 33 +++++++++++ .../PHPStan/Parser/data/cleaning-1-before.php | 56 +++++++++++++++++++ 6 files changed, 181 insertions(+), 5 deletions(-) create mode 100644 tests/PHPStan/Parser/CleaningParserTest.php create mode 100644 tests/PHPStan/Parser/data/cleaning-1-after.php create mode 100644 tests/PHPStan/Parser/data/cleaning-1-before.php diff --git a/src/Parser/CleaningVisitor.php b/src/Parser/CleaningVisitor.php index 2afb48d309..8af4c10ad2 100644 --- a/src/Parser/CleaningVisitor.php +++ b/src/Parser/CleaningVisitor.php @@ -3,29 +3,70 @@ namespace PHPStan\Parser; use PhpParser\Node; +use PhpParser\NodeFinder; use PhpParser\NodeVisitorAbstract; +use PHPStan\Reflection\ParametersAcceptor; class CleaningVisitor extends NodeVisitorAbstract { + private NodeFinder $nodeFinder; + + public function __construct() + { + $this->nodeFinder = new NodeFinder(); + } + public function enterNode(Node $node): ?Node { if ($node instanceof Node\Stmt\Function_) { - $node->stmts = []; + $node->stmts = $this->keepVariadicsAndYields($node->stmts); return $node; } if ($node instanceof Node\Stmt\ClassMethod && $node->stmts !== null) { - $node->stmts = []; + $node->stmts = $this->keepVariadicsAndYields($node->stmts); return $node; } if ($node instanceof Node\Expr\Closure) { - $node->stmts = []; + $node->stmts = $this->keepVariadicsAndYields($node->stmts); return $node; } return null; } + /** + * @param Node\Stmt[] $stmts + * @return Node\Stmt[] + */ + private function keepVariadicsAndYields(array $stmts): array + { + $results = $this->nodeFinder->find($stmts, static function (Node $node): bool { + if ($node instanceof Node\Expr\YieldFrom || $node instanceof Node\Expr\Yield_) { + return true; + } + if ($node instanceof Node\Expr\FuncCall && $node->name instanceof Node\Name) { + return in_array($node->name->toLowerString(), ParametersAcceptor::VARIADIC_FUNCTIONS, true); + } + + return false; + }); + $newStmts = []; + foreach ($results as $result) { + if ($result instanceof Node\Expr\Yield_ || $result instanceof Node\Expr\YieldFrom) { + $newStmts[] = new Node\Stmt\Expression($result); + continue; + } + if (!$result instanceof Node\Expr\FuncCall) { + continue; + } + + $newStmts[] = new Node\Stmt\Expression(new Node\Expr\FuncCall(new Node\Name\FullyQualified('func_get_args'))); + } + + return $newStmts; + } + } diff --git a/src/Reflection/Php/PhpFunctionReflection.php b/src/Reflection/Php/PhpFunctionReflection.php index 8bdfba309d..f43eb8be1d 100644 --- a/src/Reflection/Php/PhpFunctionReflection.php +++ b/src/Reflection/Php/PhpFunctionReflection.php @@ -170,7 +170,7 @@ private function isVariadic(): bool if ($modifiedTime === false) { $modifiedTime = time(); } - $variableCacheKey = sprintf('%d-v2', $modifiedTime); + $variableCacheKey = sprintf('%d-v3', $modifiedTime); $key = sprintf('variadic-function-%s-%s', $functionName, $fileName); $cachedResult = $this->cache->load($key, $variableCacheKey); if ($cachedResult === null) { diff --git a/src/Reflection/Php/PhpMethodReflection.php b/src/Reflection/Php/PhpMethodReflection.php index b3cccfba86..71a4f41e27 100644 --- a/src/Reflection/Php/PhpMethodReflection.php +++ b/src/Reflection/Php/PhpMethodReflection.php @@ -274,7 +274,7 @@ private function isVariadic(): bool $modifiedTime = time(); } $key = sprintf('variadic-method-%s-%s-%s', $declaringClass->getName(), $this->reflection->getName(), $filename); - $variableCacheKey = sprintf('%d-v3', $modifiedTime); + $variableCacheKey = sprintf('%d-v4', $modifiedTime); $cachedResult = $this->cache->load($key, $variableCacheKey); if ($cachedResult === null || !is_bool($cachedResult)) { $nodes = $this->parser->parseFile($filename); diff --git a/tests/PHPStan/Parser/CleaningParserTest.php b/tests/PHPStan/Parser/CleaningParserTest.php new file mode 100644 index 0000000000..c9e23e3d61 --- /dev/null +++ b/tests/PHPStan/Parser/CleaningParserTest.php @@ -0,0 +1,46 @@ +parseFile($beforeFile); + $this->assertSame(FileReader::read($afterFile), "prettyPrint($ast) . "\n"); + } + +} diff --git a/tests/PHPStan/Parser/data/cleaning-1-after.php b/tests/PHPStan/Parser/data/cleaning-1-after.php new file mode 100644 index 0000000000..f6e0f33540 --- /dev/null +++ b/tests/PHPStan/Parser/data/cleaning-1-after.php @@ -0,0 +1,33 @@ + Date: Tue, 5 Oct 2021 00:13:22 +0200 Subject: [PATCH 0412/1284] Fix Windows --- src/Parser/PathRoutingParser.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Parser/PathRoutingParser.php b/src/Parser/PathRoutingParser.php index 147844fdb6..a632f823a5 100644 --- a/src/Parser/PathRoutingParser.php +++ b/src/Parser/PathRoutingParser.php @@ -41,11 +41,11 @@ public function setAnalysedFiles(array $files): void public function parseFile(string $file): array { - $file = $this->fileHelper->normalizePath($file, '/'); - if (strpos($file, 'vendor/jetbrains/phpstorm-stubs') !== false) { + if (strpos($this->fileHelper->normalizePath($file, '/'), 'vendor/jetbrains/phpstorm-stubs') !== false) { return $this->php8Parser->parseFile($file); } + $file = $this->fileHelper->normalizePath($file); if (!isset($this->analysedFiles[$file])) { return $this->currentPhpVersionSimpleParser->parseFile($file); } From 7581df1772a992bc6ae3372ddf9e851e46d0c615 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 5 Oct 2021 14:57:45 +0200 Subject: [PATCH 0413/1284] Fix --- tests/PHPStan/Parser/data/cleaning-1-after.php | 2 +- tests/PHPStan/Parser/data/cleaning-1-before.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/PHPStan/Parser/data/cleaning-1-after.php b/tests/PHPStan/Parser/data/cleaning-1-after.php index f6e0f33540..0f22d85600 100644 --- a/tests/PHPStan/Parser/data/cleaning-1-after.php +++ b/tests/PHPStan/Parser/data/cleaning-1-after.php @@ -1,5 +1,5 @@ Date: Mon, 4 Oct 2021 15:45:22 +0200 Subject: [PATCH 0414/1284] Added xdebug 3.1 functions "xdebug_notify" & "xdebug_connect_to_client" --- resources/functionMap.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/resources/functionMap.php b/resources/functionMap.php index 7875a7423c..cef96c1062 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -12973,6 +12973,7 @@ 'xdebug_call_line' => ['int', 'depth=' => 'int'], 'xdebug_clear_aggr_profiling_data' => ['bool'], 'xdebug_code_coverage_started' => ['bool'], +'xdebug_connect_to_client' => ['bool'], 'xdebug_debug_zval' => ['void', '...varName'=>'string'], 'xdebug_debug_zval_stdout' => ['void', '...varName'=>'string'], 'xdebug_disable' => ['void'], @@ -12993,6 +12994,7 @@ 'xdebug_is_debugger_active' => ['bool'], 'xdebug_is_enabled' => ['bool'], 'xdebug_memory_usage' => ['int'], +'xdebug_notify' => ['bool', 'data' => 'mixed'], 'xdebug_peak_memory_usage' => ['int'], 'xdebug_print_function_stack' => ['array', 'message='=>'string', 'options=' => 'int'], 'xdebug_set_filter' => ['void', 'group' => 'int', 'list_type' => 'int', 'configuration' => 'array'], From 32257170add454106df42de80f5d28713c5769c0 Mon Sep 17 00:00:00 2001 From: Jean-Luc Herren Date: Tue, 28 Sep 2021 17:31:07 +0200 Subject: [PATCH 0415/1284] Fix signature of ReflectionClass::setStaticPropertyValue() --- resources/functionMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index cef96c1062..f50b2f852b 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -9690,7 +9690,7 @@ 'ReflectionClass::newInstance' => ['object', 'args='=>'mixed', '...args='=>'mixed'], 'ReflectionClass::newInstanceArgs' => ['object', 'args='=>'array'], 'ReflectionClass::newInstanceWithoutConstructor' => ['object'], -'ReflectionClass::setStaticPropertyValue' => ['void', 'name'=>'string', 'value'=>'string'], +'ReflectionClass::setStaticPropertyValue' => ['void', 'name'=>'string', 'value'=>'mixed'], 'ReflectionClassConstant::__construct' => ['void', 'class'=>'mixed', 'name'=>'string'], 'ReflectionClassConstant::__toString' => ['string'], 'ReflectionClassConstant::export' => ['string', 'class'=>'mixed', 'name'=>'string', 'return='=>'bool'], From d365eff8fd94f4697aef6750353e95166d83226b Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 5 Oct 2021 15:09:15 +0200 Subject: [PATCH 0416/1284] Fixed integer range minus bugs --- src/Analyser/MutatingScope.php | 15 +++++++++++++-- .../PHPStan/Analyser/data/integer-range-types.php | 13 ++++++++----- tests/PHPStan/Analyser/data/math.php | 8 ++++---- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index e031880a6c..ae1b426ab2 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -5241,7 +5241,11 @@ private function integerRangeMath(Type $range, Expr $node, Type $operand): Type if ($operand->getMin() === null) { $min = null; } elseif ($rangeMin !== null) { - $min = $rangeMin - $operand->getMin(); + if ($operand->getMax() !== null) { + $min = $rangeMin - $operand->getMax(); + } else { + $min = $rangeMin - $operand->getMin(); + } } else { $min = null; } @@ -5250,7 +5254,14 @@ private function integerRangeMath(Type $range, Expr $node, Type $operand): Type $min = null; $max = null; } elseif ($rangeMax !== null) { - $max = $rangeMax - $operand->getMax(); + if ($rangeMin !== null && $operand->getMin() === null) { + $min = $rangeMin - $operand->getMax(); + $max = null; + } elseif ($operand->getMin() !== null) { + $max = $rangeMax - $operand->getMin(); + } else { + $max = null; + } } else { $max = null; } diff --git a/tests/PHPStan/Analyser/data/integer-range-types.php b/tests/PHPStan/Analyser/data/integer-range-types.php index b0b23c538a..a7ef89e2cd 100644 --- a/tests/PHPStan/Analyser/data/integer-range-types.php +++ b/tests/PHPStan/Analyser/data/integer-range-types.php @@ -202,13 +202,14 @@ public function supportsPhpdocIntegerRange() { * @param positive-int $pi * @param int<1, 10> $r1 * @param int<5, 10> $r2 + * @param int<-9, 100> $r3 * @param int $rMin * @param int<5, max> $rMax * * @param 20|40|60 $x * @param 2|4 $y */ - public function math($i, $j, $z, $pi, $r1, $r2, $rMin, $rMax, $x, $y) { + public function math($i, $j, $z, $pi, $r1, $r2, $r3, $rMin, $rMax, $x, $y) { assertType('int', $r1 + $i); assertType('int', $r1 - $i); assertType('int', $r1 * $i); @@ -254,16 +255,18 @@ public function math($i, $j, $z, $pi, $r1, $r2, $rMin, $rMax, $x, $y) { assertType('float|int<1, max>', $rMax / 4); assertType('int<6, 20>', $r1 + $r2); - assertType('int<-4, 0>', $r1 - $r2); + assertType('int<-9, 5>', $r1 - $r2); assertType('int<5, 100>', $r1 * $r2); assertType('float|int<0, 1>', $r1 / $r2); + assertType('int<-99, 19>', $r1 - $r3); + assertType('int', $r1 + $rMin); - assertType('int', $r1 - $rMin); + assertType('int<-4, max>', $r1 - $rMin); assertType('int', $r1 * $rMin); assertType('float|int', $r1 / $rMin); assertType('int', $rMin + $r1); - assertType('int', $rMin - $r1); + assertType('int', $rMin - $r1); assertType('int', $rMin * $r1); assertType('float|int', $rMin / $r1); @@ -272,7 +275,7 @@ public function math($i, $j, $z, $pi, $r1, $r2, $rMin, $rMax, $x, $y) { assertType('int<5, max>', $r1 * $rMax); assertType('float|int<0, max>', $r1 / $rMax); assertType('int<6, max>', $rMax + $r1); - assertType('int<4, max>', $rMax - $r1); + assertType('int<-5, max>', $rMax - $r1); assertType('int<5, max>', $rMax * $r1); assertType('float|int<5, max>', $rMax / $r1); diff --git a/tests/PHPStan/Analyser/data/math.php b/tests/PHPStan/Analyser/data/math.php index 99a364ab09..6db10a4e70 100644 --- a/tests/PHPStan/Analyser/data/math.php +++ b/tests/PHPStan/Analyser/data/math.php @@ -52,19 +52,19 @@ public function doBaz(int $rangeFiveBoth, int $rangeFiveLeft, int $rangeFiveRigh assertType('int', $rangeFiveRight - $rangeFiveLeft); assertType('int<-10, 10>', $rangeFiveBoth + $rangeFiveBoth); - assertType('0', $rangeFiveBoth - $rangeFiveBoth); + assertType('int<-10, 10>', $rangeFiveBoth - $rangeFiveBoth); assertType('int<-10, max>', $rangeFiveBoth + $rangeFiveLeft); assertType('int', $rangeFiveBoth - $rangeFiveLeft); assertType('int', $rangeFiveBoth + $rangeFiveRight); - assertType('int', $rangeFiveBoth - $rangeFiveRight); + assertType('int<-10, max>', $rangeFiveBoth - $rangeFiveRight); assertType('int<-10, max>', $rangeFiveLeft + $rangeFiveBoth); - assertType('int<0, max>', $rangeFiveLeft - $rangeFiveBoth); + assertType('int<-10, max>', $rangeFiveLeft - $rangeFiveBoth); assertType('int', $rangeFiveRight + $rangeFiveBoth); - assertType('int', $rangeFiveRight - $rangeFiveBoth); + assertType('int', $rangeFiveRight - $rangeFiveBoth); } public function doLorem($a, $b): void From 1679c34b52e2fae07b955d552ac655d5ea179c87 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 5 Oct 2021 15:00:33 +0200 Subject: [PATCH 0417/1284] Update functionMetadata --- bin/generate-function-metadata.php | 12 ++++++++++-- resources/functionMetadata.php | 18 ++++++++++++------ 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/bin/generate-function-metadata.php b/bin/generate-function-metadata.php index a7e4ebd35a..a3779dda53 100755 --- a/bin/generate-function-metadata.php +++ b/bin/generate-function-metadata.php @@ -29,7 +29,7 @@ public function enterNode(Node $node) foreach ($attrGroup->attrs as $attr) { if ($attr->name->toString() === \JetBrains\PhpStorm\Pure::class) { $this->functions[] = $node->namespacedName->toLowerString(); - break; + break 2; } } } @@ -45,7 +45,7 @@ public function enterNode(Node $node) foreach ($attrGroup->attrs as $attr) { if ($attr->name->toString() === \JetBrains\PhpStorm\Pure::class) { $this->methods[] = sprintf('%s::%s', $className, $node->name->toString()); - break; + break 2; } } } @@ -71,6 +71,14 @@ public function enterNode(Node $node) foreach ($visitor->functions as $functionName) { if (array_key_exists($functionName, $metadata)) { if ($metadata[$functionName]['hasSideEffects']) { + if (in_array($functionName, [ + 'mt_rand', + 'rand', + 'random_bytes', + 'random_int', + ], true)) { + continue; + } throw new \PHPStan\ShouldNotHappenException($functionName); } } diff --git a/resources/functionMetadata.php b/resources/functionMetadata.php index f136e361e4..aa006df990 100644 --- a/resources/functionMetadata.php +++ b/resources/functionMetadata.php @@ -68,6 +68,7 @@ 'DateTimeImmutable::setTimestamp' => ['hasSideEffects' => false], 'DateTimeImmutable::setTimezone' => ['hasSideEffects' => false], 'DateTimeImmutable::sub' => ['hasSideEffects' => false], + 'Error::__construct' => ['hasSideEffects' => false], 'ErrorException::__construct' => ['hasSideEffects' => false], 'Event::__construct' => ['hasSideEffects' => false], 'EventBase::getFeatures' => ['hasSideEffects' => false], @@ -86,6 +87,7 @@ 'EventHttpConnection::__construct' => ['hasSideEffects' => false], 'EventHttpRequest::__construct' => ['hasSideEffects' => false], 'EventHttpRequest::getCommand' => ['hasSideEffects' => false], + 'EventHttpRequest::getConnection' => ['hasSideEffects' => false], 'EventHttpRequest::getHost' => ['hasSideEffects' => false], 'EventHttpRequest::getInputBuffer' => ['hasSideEffects' => false], 'EventHttpRequest::getInputHeaders' => ['hasSideEffects' => false], @@ -495,6 +497,7 @@ 'ReflectionFunctionAbstract::getAttributes' => ['hasSideEffects' => false], 'ReflectionFunctionAbstract::getClosureScopeClass' => ['hasSideEffects' => false], 'ReflectionFunctionAbstract::getClosureThis' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::getClosureUsedVariables' => ['hasSideEffects' => false], 'ReflectionFunctionAbstract::getDocComment' => ['hasSideEffects' => false], 'ReflectionFunctionAbstract::getEndLine' => ['hasSideEffects' => false], 'ReflectionFunctionAbstract::getExtension' => ['hasSideEffects' => false], @@ -509,10 +512,13 @@ 'ReflectionFunctionAbstract::getShortName' => ['hasSideEffects' => false], 'ReflectionFunctionAbstract::getStartLine' => ['hasSideEffects' => false], 'ReflectionFunctionAbstract::getStaticVariables' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::getTentativeReturnType' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::hasTentativeReturnType' => ['hasSideEffects' => false], 'ReflectionFunctionAbstract::isClosure' => ['hasSideEffects' => false], 'ReflectionFunctionAbstract::isDeprecated' => ['hasSideEffects' => false], 'ReflectionFunctionAbstract::isGenerator' => ['hasSideEffects' => false], 'ReflectionFunctionAbstract::isInternal' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::isStatic' => ['hasSideEffects' => false], 'ReflectionFunctionAbstract::isUserDefined' => ['hasSideEffects' => false], 'ReflectionFunctionAbstract::isVariadic' => ['hasSideEffects' => false], 'ReflectionGenerator::getExecutingFile' => ['hasSideEffects' => false], @@ -521,6 +527,7 @@ 'ReflectionGenerator::getFunction' => ['hasSideEffects' => false], 'ReflectionGenerator::getThis' => ['hasSideEffects' => false], 'ReflectionGenerator::getTrace' => ['hasSideEffects' => false], + 'ReflectionIntersectionType::getTypes' => ['hasSideEffects' => false], 'ReflectionMethod::getClosure' => ['hasSideEffects' => false], 'ReflectionMethod::getDeclaringClass' => ['hasSideEffects' => false], 'ReflectionMethod::getModifiers' => ['hasSideEffects' => false], @@ -533,6 +540,7 @@ 'ReflectionMethod::isProtected' => ['hasSideEffects' => false], 'ReflectionMethod::isPublic' => ['hasSideEffects' => false], 'ReflectionMethod::isStatic' => ['hasSideEffects' => false], + 'ReflectionMethod::setAccessible' => ['hasSideEffects' => false], 'ReflectionNamedType::getName' => ['hasSideEffects' => false], 'ReflectionNamedType::isBuiltin' => ['hasSideEffects' => false], 'ReflectionParameter::getAttributes' => ['hasSideEffects' => false], @@ -567,6 +575,7 @@ 'ReflectionProperty::isProtected' => ['hasSideEffects' => false], 'ReflectionProperty::isPublic' => ['hasSideEffects' => false], 'ReflectionProperty::isStatic' => ['hasSideEffects' => false], + 'ReflectionProperty::setAccessible' => ['hasSideEffects' => false], 'ReflectionReference::getId' => ['hasSideEffects' => false], 'ReflectionType::isBuiltin' => ['hasSideEffects' => false], 'ReflectionUnionType::getTypes' => ['hasSideEffects' => false], @@ -601,15 +610,11 @@ 'SimpleXMLIterator::valid' => ['hasSideEffects' => false], 'SoapFault::__construct' => ['hasSideEffects' => false], 'Spoofchecker::__construct' => ['hasSideEffects' => false], - 'StubTests\\Model\\BasePHPElement::getFQN' => ['hasSideEffects' => false], - 'StubTests\\Model\\BasePHPElement::getTypeNameFromNode' => ['hasSideEffects' => false], - 'StubTests\\Model\\BasePHPElement::hasMutedProblem' => ['hasSideEffects' => false], - 'StubTests\\Model\\StubsContainer::getClass' => ['hasSideEffects' => false], - 'StubTests\\Model\\StubsContainer::getInterface' => ['hasSideEffects' => false], + 'StubTests\\CodeStyle\\BracesOneLineFixer::getDefinition' => ['hasSideEffects' => false], 'StubTests\\Parsers\\ExpectedFunctionArgumentsInfo::__toString' => ['hasSideEffects' => false], 'StubTests\\Parsers\\Visitors\\CoreStubASTVisitor::__construct' => ['hasSideEffects' => false], + 'StubTests\\StubsMetaExpectedArgumentsTest::getClassMemberFqn' => ['hasSideEffects' => false], 'StubTests\\StubsParameterNamesTest::printParameters' => ['hasSideEffects' => false], - 'StubTests\\StubsTest::getParameterRepresentation' => ['hasSideEffects' => false], 'Transliterator::createInverse' => ['hasSideEffects' => false], 'Transliterator::getErrorCode' => ['hasSideEffects' => false], 'Transliterator::getErrorMessage' => ['hasSideEffects' => false], @@ -658,6 +663,7 @@ 'array_intersect_key' => ['hasSideEffects' => false], 'array_intersect_uassoc' => ['hasSideEffects' => false], 'array_intersect_ukey' => ['hasSideEffects' => false], + 'array_is_list' => ['hasSideEffects' => false], 'array_key_exists' => ['hasSideEffects' => false], 'array_key_first' => ['hasSideEffects' => false], 'array_key_last' => ['hasSideEffects' => false], From 1a34d800278afdc99ce76a2d3efea34702e210e0 Mon Sep 17 00:00:00 2001 From: Sergei Morozov Date: Tue, 28 Sep 2021 18:40:29 -0700 Subject: [PATCH 0418/1284] mysqli_stmt_param_count() returns a non-negative int --- resources/functionMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index f50b2f852b..fbfdd09f06 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -7352,7 +7352,7 @@ 'mysqli_stmt_more_results' => ['bool', 'stmt'=>'mysqli_stmt'], 'mysqli_stmt_next_result' => ['bool', 'stmt'=>'mysqli_stmt'], 'mysqli_stmt_num_rows' => ['int', 'stmt'=>'mysqli_stmt'], -'mysqli_stmt_param_count' => ['int', 'stmt'=>'mysqli_stmt'], +'mysqli_stmt_param_count' => ['0|positive-int', 'stmt'=>'mysqli_stmt'], 'mysqli_stmt_prepare' => ['bool', 'stmt'=>'mysqli_stmt', 'query'=>'string'], 'mysqli_stmt_reset' => ['bool', 'stmt'=>'mysqli_stmt'], 'mysqli_stmt_result_metadata' => ['mysqli_result|false', 'stmt'=>'mysqli_stmt'], From b9a0f0176c0f4b39f7d87daa59d6f46b883361b3 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 5 Oct 2021 15:19:17 +0200 Subject: [PATCH 0419/1284] Fix test on Windows --- tests/PHPStan/Parser/CachedParserTest.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/PHPStan/Parser/CachedParserTest.php b/tests/PHPStan/Parser/CachedParserTest.php index f8fefca030..e789573d49 100644 --- a/tests/PHPStan/Parser/CachedParserTest.php +++ b/tests/PHPStan/Parser/CachedParserTest.php @@ -82,14 +82,15 @@ private function getPhpParserNodeMock(): \PhpParser\Node public function testParseTheSameFileWithDifferentMethod(): void { + $fileHelper = self::getContainer()->getByType(FileHelper::class); $pathRoutingParser = new PathRoutingParser( - self::getContainer()->getByType(FileHelper::class), + $fileHelper, self::getContainer()->getService('currentPhpVersionRichParser'), self::getContainer()->getService('currentPhpVersionSimpleDirectParser'), self::getContainer()->getService('php8Parser') ); $parser = new CachedParser($pathRoutingParser, 500); - $path = __DIR__ . '/data/test.php'; + $path = $fileHelper->normalizePath(__DIR__ . '/data/test.php'); $pathRoutingParser->setAnalysedFiles([$path]); $contents = FileReader::read($path); $stmts = $parser->parseString($contents); From b7bd0a98dbe3f8536f92b66ff17a42c98968783c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 5 Oct 2021 16:16:22 +0200 Subject: [PATCH 0420/1284] Rewrite OptimizedDirectorySourceLocator to use PhpFileCleaner from Composer See https://github.com/composer/composer/pull/10107 --- phpstan-baseline.neon | 9 +- src/Php/PhpVersion.php | 5 + .../OptimizedDirectorySourceLocator.php | 91 ++------ ...OptimizedDirectorySourceLocatorFactory.php | 8 +- .../SourceLocator/PhpFileCleaner.php | 218 ++++++++++++++++++ 5 files changed, 262 insertions(+), 69 deletions(-) create mode 100644 src/Reflection/BetterReflection/SourceLocator/PhpFileCleaner.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index ac6123fc51..68a7387aaf 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -130,14 +130,19 @@ parameters: path: src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php - - message: "#^Only booleans are allowed in a negated boolean, int\\|false given\\.$#" + message: "#^Only booleans are allowed in a negated boolean, int\\|false\\|null given\\.$#" count: 1 path: src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php + - + message: "#^Only booleans are allowed in &&, int\\|false given on the right side\\.$#" + count: 1 + path: src/Reflection/BetterReflection/SourceLocator/PhpFileCleaner.php + - message: "#^Only booleans are allowed in an if condition, int\\|false given\\.$#" count: 1 - path: src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php + path: src/Reflection/BetterReflection/SourceLocator/PhpFileCleaner.php - message: "#^Method PHPStan\\\\Reflection\\\\ClassReflection\\:\\:__construct\\(\\) has parameter \\$reflection with generic class ReflectionClass but does not specify its types\\: T$#" diff --git a/src/Php/PhpVersion.php b/src/Php/PhpVersion.php index cbb75add69..100cc69ed0 100644 --- a/src/Php/PhpVersion.php +++ b/src/Php/PhpVersion.php @@ -142,4 +142,9 @@ public function supportsReadOnlyProperties(): bool return $this->versionId >= 80100; } + public function supportsEnums(): bool + { + return $this->versionId >= 80100; + } + } diff --git a/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php b/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php index f95853719b..d13ee52738 100644 --- a/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php +++ b/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php @@ -8,6 +8,7 @@ use PHPStan\BetterReflection\Reflector\Reflector; use PHPStan\BetterReflection\SourceLocator\Ast\Strategy\NodeToReflection; use PHPStan\BetterReflection\SourceLocator\Type\SourceLocator; +use PHPStan\Php\PhpVersion; use function array_key_exists; class OptimizedDirectorySourceLocator implements SourceLocator @@ -15,9 +16,15 @@ class OptimizedDirectorySourceLocator implements SourceLocator private \PHPStan\Reflection\BetterReflection\SourceLocator\FileNodesFetcher $fileNodesFetcher; + private PhpVersion $phpVersion; + + private PhpFileCleaner $cleaner; + /** @var string[] */ private array $files; + private string $extraTypes; + /** @var array|null */ private ?array $classToFile = null; @@ -39,11 +46,24 @@ class OptimizedDirectorySourceLocator implements SourceLocator */ public function __construct( FileNodesFetcher $fileNodesFetcher, + PhpVersion $phpVersion, array $files ) { $this->fileNodesFetcher = $fileNodesFetcher; + $this->phpVersion = $phpVersion; $this->files = $files; + + $extraTypes = ''; + $extraTypesArray = []; + if ($this->phpVersion->supportsEnums()) { + $extraTypes = '|enum'; + $extraTypesArray[] = 'enum'; + } + + $this->extraTypes = $extraTypes; + + $this->cleaner = new PhpFileCleaner(array_merge(['class', 'interface', 'trait', 'function'], $extraTypesArray)); } public function locateIdentifier(Reflector $reflector, Identifier $identifier): ?Reflection @@ -197,79 +217,18 @@ private function findSymbols(string $file): array return ['classes' => [], 'functions' => []]; } - if (!preg_match('{\b(?:class|interface|trait|function)\s}i', $contents)) { + if (!preg_match_all(sprintf('{\b(?:class|interface|trait|function%s)\s}i', $this->extraTypes), $contents, $matches)) { return ['classes' => [], 'functions' => []]; } - // strip heredocs/nowdocs - $heredocRegex = '{ - # opening heredoc/nowdoc delimiter (word-chars) - <<<[ \t]*+([\'"]?)(\w++)\\1 - # needs to be followed by a newline - (?:\r\n|\n|\r) - # the meat of it, matching line by line until end delimiter - (?: - # a valid line is optional white-space (possessive match) not followed by the end delimiter, then anything goes for the rest of the line - [\t ]*+(?!\\2 \b)[^\r\n]*+ - # end of line(s) - [\r\n]++ - )* - # end delimiter - [\t ]*+ \\2 (?=\b) - }x'; - - // run first assuming the file is valid unicode - $contentWithoutHeredoc = preg_replace($heredocRegex . 'u', 'null', $contents); - if ($contentWithoutHeredoc === null) { - // run again without unicode support if the file failed to be parsed - $contents = preg_replace($heredocRegex, 'null', $contents); - } else { - $contents = $contentWithoutHeredoc; - } - unset($contentWithoutHeredoc); - - if ($contents === null) { - return ['classes' => [], 'functions' => []]; - } - // strip strings - $contents = preg_replace('{"[^"\\\\]*+(\\\\.[^"\\\\]*+)*+"|\'[^\'\\\\]*+(\\\\.[^\'\\\\]*+)*+\'}s', 'null', $contents); - if ($contents === null) { - return ['classes' => [], 'functions' => []]; - } - // strip leading non-php code if needed - if (strpos($contents, ' [], 'functions' => []]; - } - if ($replacements === 0) { - return ['classes' => [], 'functions' => []]; - } - } - // strip non-php blocks in the file - $contents = preg_replace('{\?>(?:[^<]++|<(?!\?))*+<\?}s', '?> [], 'functions' => []]; - } - // strip trailing non-php code if needed - $pos = strrpos($contents, '?>'); - if ($pos !== false && strpos(substr($contents, $pos), ' [], 'functions' => []]; - } - } + $contents = $this->cleaner->clean($contents, count($matches[0])); - preg_match_all('{ + preg_match_all(sprintf('{ (?: - \b(?])(?Pclass|interface|trait|function) \s++ (?P&\s*)? (?P[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*+) + \b(?])(?Pclass|interface|trait|function%s) \s++ (?P&\s*)? (?P[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*+) | \b(?])(?Pnamespace) (?P\s++[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\s*+\\\\\s*+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+)? \s*+ [\{;] ) - }ix', $contents, $matches); + }ix', $this->extraTypes), $contents, $matches); $classes = []; $functions = []; diff --git a/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorFactory.php b/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorFactory.php index 71aee274ae..fcc27c3034 100644 --- a/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorFactory.php +++ b/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorFactory.php @@ -3,6 +3,7 @@ namespace PHPStan\Reflection\BetterReflection\SourceLocator; use PHPStan\File\FileFinder; +use PHPStan\Php\PhpVersion; class OptimizedDirectorySourceLocatorFactory { @@ -11,16 +12,20 @@ class OptimizedDirectorySourceLocatorFactory private FileFinder $fileFinder; - public function __construct(FileNodesFetcher $fileNodesFetcher, FileFinder $fileFinder) + private PhpVersion $phpVersion; + + public function __construct(FileNodesFetcher $fileNodesFetcher, FileFinder $fileFinder, PhpVersion $phpVersion) { $this->fileNodesFetcher = $fileNodesFetcher; $this->fileFinder = $fileFinder; + $this->phpVersion = $phpVersion; } public function createByDirectory(string $directory): OptimizedDirectorySourceLocator { return new OptimizedDirectorySourceLocator( $this->fileNodesFetcher, + $this->phpVersion, $this->fileFinder->findFiles([$directory])->getFiles() ); } @@ -33,6 +38,7 @@ public function createByFiles(array $files): OptimizedDirectorySourceLocator { return new OptimizedDirectorySourceLocator( $this->fileNodesFetcher, + $this->phpVersion, $files ); } diff --git a/src/Reflection/BetterReflection/SourceLocator/PhpFileCleaner.php b/src/Reflection/BetterReflection/SourceLocator/PhpFileCleaner.php new file mode 100644 index 0000000000..c495909ccf --- /dev/null +++ b/src/Reflection/BetterReflection/SourceLocator/PhpFileCleaner.php @@ -0,0 +1,218 @@ + + * @see https://github.com/composer/composer/pull/10107 + */ +class PhpFileCleaner +{ + + /** @var array */ + private array $typeConfig = []; + + private string $restPattern; + + private string $contents = ''; + + private int $len = 0; + + private int $index = 0; + + /** + * @param string[] $types + */ + public function __construct(array $types) + { + foreach ($types as $type) { + $this->typeConfig[$type[0]] = [ + 'name' => $type, + 'length' => \strlen($type), + 'pattern' => '{.\b(?])' . $type . '\s++[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*+}Ais', + ]; + } + + $this->restPattern = '{[^?"\'typeConfig)) . ']+}A'; + } + + public function clean(string $contents, int $maxMatches): string + { + $this->contents = $contents; + $this->len = strlen($contents); + $this->index = 0; + + $clean = ''; + while ($this->index < $this->len) { + $this->skipToPhp(); + $clean .= 'index < $this->len) { + $char = $this->contents[$this->index]; + if ($char === '?' && $this->peek('>')) { + $clean .= '?>'; + $this->index += 2; + continue 2; + } + + if ($char === '"') { + $this->skipString('"'); + $clean .= 'null'; + continue; + } + + if ($char === "'") { + $this->skipString("'"); + $clean .= 'null'; + continue; + } + + if ($char === '<' && $this->peek('<') && $this->match('{<<<[ \t]*+([\'"]?)([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*+)\\1(?:\r\n|\n|\r)}A', $match)) { + $this->index += \strlen($match[0]); + $this->skipHeredoc($match[2]); + $clean .= 'null'; + continue; + } + + if ($char === '/') { + if ($this->peek('/')) { + $this->skipToNewline(); + continue; + } + if ($this->peek('*')) { + $this->skipComment(); + } + } + + if ($maxMatches === 1 && isset($this->typeConfig[$char])) { + $type = $this->typeConfig[$char]; + if ( + \substr($this->contents, $this->index, $type['length']) === $type['name'] + && \preg_match($type['pattern'], $this->contents, $match, 0, $this->index - 1) + ) { + return $clean . $match[0]; + } + } + + $this->index += 1; + if ($this->match($this->restPattern, $match)) { + $clean .= $char . $match[0]; + $this->index += \strlen($match[0]); + } else { + $clean .= $char; + } + } + } + + return $clean; + } + + private function skipToPhp(): void + { + while ($this->index < $this->len) { + if ($this->contents[$this->index] === '<' && $this->peek('?')) { + $this->index += 2; + break; + } + + $this->index += 1; + } + } + + private function skipString(string $delimiter): void + { + $this->index += 1; + while ($this->index < $this->len) { + if ($this->contents[$this->index] === '\\' && ($this->peek('\\') || $this->peek($delimiter))) { + $this->index += 2; + continue; + } + if ($this->contents[$this->index] === $delimiter) { + $this->index += 1; + break; + } + $this->index += 1; + } + } + + private function skipComment(): void + { + $this->index += 2; + while ($this->index < $this->len) { + if ($this->contents[$this->index] === '*' && $this->peek('/')) { + $this->index += 2; + break; + } + + $this->index += 1; + } + } + + private function skipToNewline(): void + { + while ($this->index < $this->len) { + if ($this->contents[$this->index] === "\r" || $this->contents[$this->index] === "\n") { + return; + } + $this->index += 1; + } + } + + private function skipHeredoc(string $delimiter): void + { + $firstDelimiterChar = $delimiter[0]; + $delimiterLength = \strlen($delimiter); + $delimiterPattern = '{' . preg_quote($delimiter) . '(?![a-zA-Z0-9_\x80-\xff])}A'; + + while ($this->index < $this->len) { + // check if we find the delimiter after some spaces/tabs + switch ($this->contents[$this->index]) { + case "\t": + case ' ': + $this->index += 1; + continue 2; + case $firstDelimiterChar: + if ( + \substr($this->contents, $this->index, $delimiterLength) === $delimiter + && $this->match($delimiterPattern) + ) { + $this->index += $delimiterLength; + return; + } + break; + } + + // skip the rest of the line + while ($this->index < $this->len) { + $this->skipToNewline(); + + // skip newlines + while ($this->index < $this->len && ($this->contents[$this->index] === "\r" || $this->contents[$this->index] === "\n")) { + $this->index += 1; + } + + break; + } + } + } + + private function peek(string $char): bool + { + return $this->index + 1 < $this->len && $this->contents[$this->index + 1] === $char; + } + + /** + * @param string $regex + * @param string[]|null $match + * @return bool + */ + private function match(string $regex, ?array &$match = null): bool + { + if (\preg_match($regex, $this->contents, $match, 0, $this->index)) { + return true; + } + + return false; + } + +} From 04ea1d72e6983e8d1ecd65cc0408c62d69681a98 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 6 Oct 2021 12:49:00 +0200 Subject: [PATCH 0421/1284] Fix false negative with checkExplicitMixed --- src/Type/MixedType.php | 45 ++++++++++++++++--- src/Type/StrictMixedType.php | 14 +++--- .../Levels/data/arrayDimFetches-9.json | 7 --- .../Arrays/IterableInForeachRuleTest.php | 20 ++++++++- ...nexistentOffsetInArrayDimFetchRuleTest.php | 20 ++++++++- tests/PHPStan/Rules/Arrays/data/bug-5744.php | 43 ++++++++++++++++++ tests/PHPStan/Type/MixedTypeTest.php | 5 +++ tests/PHPStan/Type/TypeCombinatorTest.php | 16 +++++++ 8 files changed, 148 insertions(+), 22 deletions(-) delete mode 100644 tests/PHPStan/Levels/data/arrayDimFetches-9.json create mode 100644 tests/PHPStan/Rules/Arrays/data/bug-5744.php diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index 5ae8f3e09f..5ae269ffa9 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -18,8 +18,6 @@ use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Generic\TemplateMixedType; use PHPStan\Type\Generic\TemplateType; -use PHPStan\Type\Traits\MaybeIterableTypeTrait; -use PHPStan\Type\Traits\MaybeOffsetAccessibleTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; @@ -27,8 +25,6 @@ class MixedType implements CompoundType, SubtractableType { - use MaybeIterableTypeTrait; - use MaybeOffsetAccessibleTypeTrait; use NonGenericTypeTrait; use UndecidedComparisonCompoundTypeTrait; @@ -118,7 +114,7 @@ public function isSuperTypeOf(Type $type): TrinaryLogic public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type { - return new MixedType(); + return new self($this->isExplicitMixed); } public function isCallable(): TrinaryLogic @@ -332,7 +328,44 @@ public function toString(): Type public function toArray(): Type { - return new ArrayType(new MixedType(), new MixedType()); + $mixed = new self($this->isExplicitMixed); + + return new ArrayType($mixed, $mixed); + } + + public function isIterable(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function isIterableAtLeastOnce(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function getIterableKeyType(): Type + { + return new self($this->isExplicitMixed); + } + + public function getIterableValueType(): Type + { + return new self($this->isExplicitMixed); + } + + public function isOffsetAccessible(): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function hasOffsetValueType(Type $offsetType): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function getOffsetValueType(Type $offsetType): Type + { + return new self($this->isExplicitMixed); } public function isExplicitMixed(): bool diff --git a/src/Type/StrictMixedType.php b/src/Type/StrictMixedType.php index 03846473ed..114a8ed6f3 100644 --- a/src/Type/StrictMixedType.php +++ b/src/Type/StrictMixedType.php @@ -163,12 +163,12 @@ public function hasOffsetValueType(Type $offsetType): TrinaryLogic public function getOffsetValueType(Type $offsetType): Type { - return new ErrorType(); + return $this; } public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type { - return new ErrorType(); + return $this; } public function isCallable(): TrinaryLogic @@ -193,27 +193,27 @@ public function toBoolean(): BooleanType public function toNumber(): Type { - return new ErrorType(); + return $this; } public function toInteger(): Type { - return new ErrorType(); + return $this; } public function toFloat(): Type { - return new ErrorType(); + return $this; } public function toString(): Type { - return new ErrorType(); + return $this; } public function toArray(): Type { - return new ErrorType(); + return $this; } public function inferTemplateTypes(Type $receivedType): TemplateTypeMap diff --git a/tests/PHPStan/Levels/data/arrayDimFetches-9.json b/tests/PHPStan/Levels/data/arrayDimFetches-9.json deleted file mode 100644 index fb1695ce25..0000000000 --- a/tests/PHPStan/Levels/data/arrayDimFetches-9.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - { - "message": "Parameter #1 (mixed) of echo cannot be converted to string.", - "line": 15, - "ignorable": true - } -] \ No newline at end of file diff --git a/tests/PHPStan/Rules/Arrays/IterableInForeachRuleTest.php b/tests/PHPStan/Rules/Arrays/IterableInForeachRuleTest.php index 6025adb242..f370d4fbc1 100644 --- a/tests/PHPStan/Rules/Arrays/IterableInForeachRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/IterableInForeachRuleTest.php @@ -10,9 +10,12 @@ class IterableInForeachRuleTest extends \PHPStan\Testing\RuleTestCase { + /** @var bool */ + private $checkExplicitMixed = false; + protected function getRule(): \PHPStan\Rules\Rule { - return new IterableInForeachRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false)); + return new IterableInForeachRule(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed)); } public function testCheckWithMaybes(): void @@ -34,4 +37,19 @@ public function testCheckWithMaybes(): void ]); } + public function testBug5744(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/bug-5744.php'], [ + /*[ + 'Argument of an invalid type mixed supplied for foreach, only iterables are supported.', + 15, + ],*/ + [ + 'Argument of an invalid type mixed supplied for foreach, only iterables are supported.', + 28, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index 30277d2480..e401b42aed 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -10,9 +10,12 @@ class NonexistentOffsetInArrayDimFetchRuleTest extends \PHPStan\Testing\RuleTestCase { + /** @var bool */ + private $checkExplicitMixed = false; + protected function getRule(): \PHPStan\Rules\Rule { - $ruleLevelHelper = new RuleLevelHelper($this->createReflectionProvider(), true, false, true, false); + $ruleLevelHelper = new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed); return new NonexistentOffsetInArrayDimFetchRule( $ruleLevelHelper, @@ -314,4 +317,19 @@ public function testBug5669(): void ]); } + public function testBug5744(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/bug-5744.php'], [ + /*[ + 'Cannot access offset \'permission\' on mixed.', + 16, + ],*/ + [ + 'Cannot access offset \'permission\' on mixed.', + 29, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Arrays/data/bug-5744.php b/tests/PHPStan/Rules/Arrays/data/bug-5744.php new file mode 100644 index 0000000000..638911e179 --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/bug-5744.php @@ -0,0 +1,43 @@ + $commandData){ + var_dump($commandData["permission"]); + } + } + } + + /** + * @phpstan-param mixed[] $plugin + */ + public function sayHello2(array $plugin): void + { + if(isset($plugin["commands"])){ + $pluginCommands = $plugin["commands"]; + foreach($pluginCommands as $commandName => $commandData){ + var_dump($commandData["permission"]); + } + } + } + + public function sayHello3(array $plugin): void + { + if(isset($plugin["commands"]) and is_array($plugin["commands"])){ + $pluginCommands = $plugin["commands"]; + foreach($pluginCommands as $commandName => $commandData){ + var_dump($commandData["permission"]); + } + } + } +} diff --git a/tests/PHPStan/Type/MixedTypeTest.php b/tests/PHPStan/Type/MixedTypeTest.php index a0cda10b5f..1385ee92e9 100644 --- a/tests/PHPStan/Type/MixedTypeTest.php +++ b/tests/PHPStan/Type/MixedTypeTest.php @@ -141,6 +141,11 @@ public function dataIsSuperTypeOf(): array new UnionType([new StringType(), new IntegerType()]), TrinaryLogic::createYes(), ], + 26 => [ + new MixedType(), + new StrictMixedType(), + TrinaryLogic::createYes(), + ], ]; } diff --git a/tests/PHPStan/Type/TypeCombinatorTest.php b/tests/PHPStan/Type/TypeCombinatorTest.php index e5610edffb..e9f940aba7 100644 --- a/tests/PHPStan/Type/TypeCombinatorTest.php +++ b/tests/PHPStan/Type/TypeCombinatorTest.php @@ -1855,6 +1855,14 @@ public function dataUnion(): array UnionType::class, 'array|(callable(): mixed)|(TCode of array|int (class Foo, parameter))', ], + [ + [ + new MixedType(), + new StrictMixedType(), + ], + MixedType::class, + 'mixed=implicit', + ], ]; } @@ -3081,6 +3089,14 @@ public function dataIntersect(): array UnionType::class, 'T of int|string (function my_array_keys(), parameter)', ], + [ + [ + new MixedType(), + new StrictMixedType(), + ], + StrictMixedType::class, + 'mixed', + ], ]; } From 0d025e7bc2ec6e84a441b2da7813ff933b5a00aa Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 6 Oct 2021 13:46:19 +0200 Subject: [PATCH 0422/1284] preg_match_all can return null --- resources/functionMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index fbfdd09f06..26f931c8c9 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -8834,7 +8834,7 @@ 'preg_grep' => ['array|false', 'regex'=>'string', 'input'=>'array', 'flags='=>'int'], 'preg_last_error' => ['int'], 'preg_match' => ['int|false', 'pattern'=>'string', 'subject'=>'string', '&w_subpatterns='=>'string[]', 'flags='=>'int', 'offset='=>'int'], -'preg_match_all' => ['int|false', 'pattern'=>'string', 'subject'=>'string', '&w_subpatterns='=>'array', 'flags='=>'int', 'offset='=>'int'], +'preg_match_all' => ['int|false|null', 'pattern'=>'string', 'subject'=>'string', '&w_subpatterns='=>'array', 'flags='=>'int', 'offset='=>'int'], 'preg_quote' => ['string', 'str'=>'string', 'delim_char='=>'string'], 'preg_replace' => ['string|array|null', 'regex'=>'string|array', 'replace'=>'string|array', 'subject'=>'string|array', 'limit='=>'int', '&w_count='=>'int'], 'preg_replace_callback' => ['string|array|null', 'regex'=>'string|array', 'callback'=>'callable', 'subject'=>'string|array', 'limit='=>'int', '&w_count='=>'int'], From 84bafe79d6b59f18dcbc0b3c8b9978d7af9bd837 Mon Sep 17 00:00:00 2001 From: Jean-Luc Herren Date: Fri, 24 Sep 2021 22:18:11 +0200 Subject: [PATCH 0423/1284] Make array_map() accept null as first argument --- src/Reflection/ParametersAcceptorSelector.php | 7 +++- .../ArrayMapFunctionReturnTypeExtension.php | 33 +++++++++++++++---- .../Analyser/data/array_map_multiple.php | 15 +++++++++ .../CallToFunctionParametersRuleTest.php | 8 ++--- .../Functions/data/array_map_multiple.php | 5 +++ 5 files changed, 56 insertions(+), 12 deletions(-) diff --git a/src/Reflection/ParametersAcceptorSelector.php b/src/Reflection/ParametersAcceptorSelector.php index 3b3db43e26..08afdd5700 100644 --- a/src/Reflection/ParametersAcceptorSelector.php +++ b/src/Reflection/ParametersAcceptorSelector.php @@ -12,7 +12,9 @@ use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Generic\TemplateTypeMap; use PHPStan\Type\MixedType; +use PHPStan\Type\NullType; use PHPStan\Type\TypeCombinator; +use PHPStan\Type\UnionType; /** @api */ class ParametersAcceptorSelector @@ -80,7 +82,10 @@ public static function selectFromArgs( $parameters[0] = new NativeParameterReflection( $parameters[0]->getName(), $parameters[0]->isOptional(), - new CallableType($callbackParameters, new MixedType(), false), + new UnionType([ + new CallableType($callbackParameters, new MixedType(), false), + new NullType(), + ]), $parameters[0]->passedByReference(), $parameters[0]->isVariadic(), $parameters[0]->getDefaultValue() diff --git a/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php b/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php index 543f3ac725..1a9772dd0c 100644 --- a/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayMapFunctionReturnTypeExtension.php @@ -9,9 +9,11 @@ use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\ArrayType; use PHPStan\Type\Constant\ConstantArrayTypeBuilder; +use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\IntegerType; use PHPStan\Type\MixedType; use PHPStan\Type\NeverType; +use PHPStan\Type\NullType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use PHPStan\Type\TypeUtils; @@ -30,23 +32,35 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); } - $valueType = new MixedType(); + $singleArrayArgument = !isset($functionCall->getArgs()[2]); $callableType = $scope->getType($functionCall->getArgs()[0]->value); + $callableIsNull = (new NullType())->isSuperTypeOf($callableType)->yes(); + if ($callableType->isCallable()->yes()) { $valueType = new NeverType(); foreach ($callableType->getCallableParametersAcceptors($scope) as $parametersAcceptor) { $valueType = TypeCombinator::union($valueType, $parametersAcceptor->getReturnType()); } + } elseif ($callableIsNull) { + $arrayBuilder = ConstantArrayTypeBuilder::createEmpty(); + foreach (array_slice($functionCall->getArgs(), 1) as $index => $arg) { + $arrayBuilder->setOffsetValueType( + new ConstantIntegerType($index), + $scope->getType($arg->value)->getIterableValueType() + ); + } + $valueType = $arrayBuilder->getArray(); + } else { + $valueType = new MixedType(); } - $mappedArrayType = new ArrayType( - new MixedType(), - $valueType - ); $arrayType = $scope->getType($functionCall->getArgs()[1]->value); - $constantArrays = TypeUtils::getConstantArrays($arrayType); - if (!isset($functionCall->getArgs()[2])) { + if ($singleArrayArgument) { + if ($callableIsNull) { + return $arrayType; + } + $constantArrays = TypeUtils::getConstantArrays($arrayType); if (count($constantArrays) > 0) { $arrayTypes = []; foreach ($constantArrays as $constantArray) { @@ -66,6 +80,11 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, $arrayType->getIterableKeyType(), $valueType ), ...TypeUtils::getAccessoryTypes($arrayType)); + } else { + $mappedArrayType = new ArrayType( + new MixedType(), + $valueType + ); } } else { $mappedArrayType = TypeCombinator::intersect(new ArrayType( diff --git a/tests/PHPStan/Analyser/data/array_map_multiple.php b/tests/PHPStan/Analyser/data/array_map_multiple.php index 651ffde7f7..a384a33ac5 100644 --- a/tests/PHPStan/Analyser/data/array_map_multiple.php +++ b/tests/PHPStan/Analyser/data/array_map_multiple.php @@ -18,4 +18,19 @@ public function doFoo(int $i, string $s): void assertType('array&nonEmpty', $result); } + /** + * @param non-empty-array $array + * @param non-empty-array $other + */ + public function arrayMapNull(array $array, array $other): void + { + assertType('array()', array_map(null, [])); + assertType('array(\'foo\' => true)', array_map(null, ['foo' => true])); + assertType('array&nonEmpty', array_map(null, [1, 2, 3], [4, 5, 6])); + + assertType('array&nonEmpty', array_map(null, $array)); + assertType('array&nonEmpty', array_map(null, $array, $array)); + assertType('array&nonEmpty', array_map(null, $array, $other)); + } + } diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 89544277ab..6d0dd8a99d 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -796,7 +796,7 @@ public function testArrayMapMultiple(bool $checkExplicitMixed): void $this->checkExplicitMixed = $checkExplicitMixed; $this->analyse([__DIR__ . '/data/array_map_multiple.php'], [ [ - 'Parameter #1 $callback of function array_map expects callable(1|2, \'bar\'|\'foo\'): mixed, Closure(int, int): void given.', + 'Parameter #1 $callback of function array_map expects (callable(1|2, \'bar\'|\'foo\'): mixed)|null, Closure(int, int): void given.', 58, ], ]); @@ -840,11 +840,11 @@ public function testBug5356(): void $this->analyse([__DIR__ . '/data/bug-5356.php'], [ [ - 'Parameter #1 $callback of function array_map expects callable(string): mixed, Closure(array): \'a\' given.', + 'Parameter #1 $callback of function array_map expects (callable(string): mixed)|null, Closure(array): \'a\' given.', 13, ], [ - 'Parameter #1 $callback of function array_map expects callable(string): mixed, Closure(array): \'a\' given.', + 'Parameter #1 $callback of function array_map expects (callable(string): mixed)|null, Closure(array): \'a\' given.', 21, ], ]); @@ -854,7 +854,7 @@ public function testBug1954(): void { $this->analyse([__DIR__ . '/data/bug-1954.php'], [ [ - 'Parameter #1 $callback of function array_map expects callable(1|stdClass): mixed, Closure(string): string given.', + 'Parameter #1 $callback of function array_map expects (callable(1|stdClass): mixed)|null, Closure(string): string given.', 7, ], ]); diff --git a/tests/PHPStan/Rules/Functions/data/array_map_multiple.php b/tests/PHPStan/Rules/Functions/data/array_map_multiple.php index 06d872a41f..0d96ce95e9 100644 --- a/tests/PHPStan/Rules/Functions/data/array_map_multiple.php +++ b/tests/PHPStan/Rules/Functions/data/array_map_multiple.php @@ -60,4 +60,9 @@ public function doFoo(): void }, [1, 2], ['foo', 'bar']); } + public function arrayMapNull(): void + { + array_map(null, [1, 2], [3, 4]); + } + } From bd81cdc16249817570db7cdd6b724ce6f604f94c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 6 Oct 2021 14:03:37 +0200 Subject: [PATCH 0424/1284] Fix --- src/Type/StrictMixedType.php | 14 +++++++------- tests/PHPStan/Levels/data/arrayDimFetches-9.json | 7 +++++++ 2 files changed, 14 insertions(+), 7 deletions(-) create mode 100644 tests/PHPStan/Levels/data/arrayDimFetches-9.json diff --git a/src/Type/StrictMixedType.php b/src/Type/StrictMixedType.php index 114a8ed6f3..03846473ed 100644 --- a/src/Type/StrictMixedType.php +++ b/src/Type/StrictMixedType.php @@ -163,12 +163,12 @@ public function hasOffsetValueType(Type $offsetType): TrinaryLogic public function getOffsetValueType(Type $offsetType): Type { - return $this; + return new ErrorType(); } public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type { - return $this; + return new ErrorType(); } public function isCallable(): TrinaryLogic @@ -193,27 +193,27 @@ public function toBoolean(): BooleanType public function toNumber(): Type { - return $this; + return new ErrorType(); } public function toInteger(): Type { - return $this; + return new ErrorType(); } public function toFloat(): Type { - return $this; + return new ErrorType(); } public function toString(): Type { - return $this; + return new ErrorType(); } public function toArray(): Type { - return $this; + return new ErrorType(); } public function inferTemplateTypes(Type $receivedType): TemplateTypeMap diff --git a/tests/PHPStan/Levels/data/arrayDimFetches-9.json b/tests/PHPStan/Levels/data/arrayDimFetches-9.json new file mode 100644 index 0000000000..fb1695ce25 --- /dev/null +++ b/tests/PHPStan/Levels/data/arrayDimFetches-9.json @@ -0,0 +1,7 @@ +[ + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 15, + "ignorable": true + } +] \ No newline at end of file From d5c2a0733a7cbe839520bede75b0c44ea3f94371 Mon Sep 17 00:00:00 2001 From: Sergei Morozov Date: Tue, 5 Oct 2021 20:56:11 -0700 Subject: [PATCH 0425/1284] Add stub for mysqli_stmt properties Additionally, update the corresponding procedural style aliases. --- conf/config.stubFiles.neon | 1 + resources/functionMap.php | 8 +++--- stubs/mysqli.stub | 51 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 stubs/mysqli.stub diff --git a/conf/config.stubFiles.neon b/conf/config.stubFiles.neon index 1c90d8cdf1..7f8c6e81d6 100644 --- a/conf/config.stubFiles.neon +++ b/conf/config.stubFiles.neon @@ -12,6 +12,7 @@ parameters: - ../stubs/ext-ds.stub - ../stubs/PDOStatement.stub - ../stubs/date.stub + - ../stubs/mysqli.stub - ../stubs/zip.stub - ../stubs/dom.stub - ../stubs/spl.stub diff --git a/resources/functionMap.php b/resources/functionMap.php index 26f931c8c9..a79cc24a97 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -7340,10 +7340,10 @@ 'mysqli_stmt_data_seek' => ['void', 'stmt'=>'mysqli_stmt', 'offset'=>'int'], 'mysqli_stmt_errno' => ['int', 'stmt'=>'mysqli_stmt'], 'mysqli_stmt_error' => ['string', 'stmt'=>'mysqli_stmt'], -'mysqli_stmt_error_list' => ['array', 'stmt'=>'mysqli_stmt'], +'mysqli_stmt_error_list' => ['list', 'stmt'=>'mysqli_stmt'], 'mysqli_stmt_execute' => ['bool', 'stmt'=>'mysqli_stmt'], 'mysqli_stmt_fetch' => ['bool|null', 'stmt'=>'mysqli_stmt'], -'mysqli_stmt_field_count' => ['int', 'stmt'=>'mysqli_stmt'], +'mysqli_stmt_field_count' => ['0|positive-int', 'stmt'=>'mysqli_stmt'], 'mysqli_stmt_free_result' => ['void', 'stmt'=>'mysqli_stmt'], 'mysqli_stmt_get_result' => ['mysqli_result|false', 'stmt'=>'mysqli_stmt'], 'mysqli_stmt_get_warnings' => ['object|false', 'stmt'=>'mysqli_stmt'], @@ -7351,13 +7351,13 @@ 'mysqli_stmt_insert_id' => ['', 'stmt'=>'mysqli_stmt'], 'mysqli_stmt_more_results' => ['bool', 'stmt'=>'mysqli_stmt'], 'mysqli_stmt_next_result' => ['bool', 'stmt'=>'mysqli_stmt'], -'mysqli_stmt_num_rows' => ['int', 'stmt'=>'mysqli_stmt'], +'mysqli_stmt_num_rows' => ['0|positive-int', 'stmt'=>'mysqli_stmt'], 'mysqli_stmt_param_count' => ['0|positive-int', 'stmt'=>'mysqli_stmt'], 'mysqli_stmt_prepare' => ['bool', 'stmt'=>'mysqli_stmt', 'query'=>'string'], 'mysqli_stmt_reset' => ['bool', 'stmt'=>'mysqli_stmt'], 'mysqli_stmt_result_metadata' => ['mysqli_result|false', 'stmt'=>'mysqli_stmt'], 'mysqli_stmt_send_long_data' => ['bool', 'stmt'=>'mysqli_stmt', 'param_nr'=>'int', 'data'=>'string'], -'mysqli_stmt_sqlstate' => ['string', 'stmt'=>'mysqli_stmt'], +'mysqli_stmt_sqlstate' => ['non-empty-string', 'stmt'=>'mysqli_stmt'], 'mysqli_stmt_store_result' => ['bool', 'stmt'=>'mysqli_stmt'], 'mysqli_store_result' => ['mysqli_result|false', 'link'=>'mysqli', 'option='=>'int'], 'mysqli_thread_id' => ['int', 'link'=>'mysqli'], diff --git a/stubs/mysqli.stub b/stubs/mysqli.stub new file mode 100644 index 0000000000..350a645ba3 --- /dev/null +++ b/stubs/mysqli.stub @@ -0,0 +1,51 @@ + + */ + public $error_list; + + /** + * @var string + */ + public $error; + + /** + * @var 0|positive-int + */ + public $field_count; + + /** + * @var int|string + */ + public $insert_id; + + /** + * @var 0|positive-int + */ + public $num_rows; + + /** + * @var 0|positive-int + */ + public $param_count; + + /** + * @var non-empty-string + */ + public $sqlstate; + +} From d1cf59a4ad16ae12ec1305d980142e4d5d2fe234 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 6 Oct 2021 17:24:15 +0200 Subject: [PATCH 0426/1284] Fix tests --- .../Levels/data/stringOffsetAccess-9.json | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/PHPStan/Levels/data/stringOffsetAccess-9.json b/tests/PHPStan/Levels/data/stringOffsetAccess-9.json index bf290e21ef..1e218ab052 100644 --- a/tests/PHPStan/Levels/data/stringOffsetAccess-9.json +++ b/tests/PHPStan/Levels/data/stringOffsetAccess-9.json @@ -4,19 +4,39 @@ "line": 39, "ignorable": true }, + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 39, + "ignorable": true + }, { "message": "Cannot access offset 'foo' on mixed.", "line": 43, "ignorable": true }, + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 43, + "ignorable": true + }, { "message": "Cannot access offset 12.34 on mixed.", "line": 47, "ignorable": true }, + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 47, + "ignorable": true + }, { "message": "Cannot access offset int|object on mixed.", "line": 51, "ignorable": true + }, + { + "message": "Parameter #1 (mixed) of echo cannot be converted to string.", + "line": 51, + "ignorable": true } ] \ No newline at end of file From 98f0b02d1953580ef1b18a55affb1565da55be1b Mon Sep 17 00:00:00 2001 From: Steevan BARBOYON Date: Thu, 7 Oct 2021 00:08:24 +0200 Subject: [PATCH 0427/1284] Remove specific configuration for array_fill() with PHP 8 --- resources/functionMap_php80delta.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/resources/functionMap_php80delta.php b/resources/functionMap_php80delta.php index f6a4efdd98..6353bcee2e 100644 --- a/resources/functionMap_php80delta.php +++ b/resources/functionMap_php80delta.php @@ -22,7 +22,6 @@ return [ 'new' => [ 'array_combine' => ['associative-array', 'keys'=>'string[]|int[]', 'values'=>'array'], - 'array_fill' => ['array', 'start_key'=>'int', 'num'=>'0|positive-int', 'val'=>'mixed'], 'bcdiv' => ['string', 'dividend'=>'string', 'divisor'=>'string', 'scale='=>'int'], 'bcmod' => ['string', 'dividend'=>'string', 'divisor'=>'string', 'scale='=>'int'], 'bcpowmod' => ['string', 'base'=>'string', 'exponent'=>'string', 'modulus'=>'string', 'scale='=>'int'], @@ -147,7 +146,6 @@ 'old' => [ 'array_combine' => ['associative-array|false', 'keys'=>'string[]|int[]', 'values'=>'array'], - 'array_fill' => ['array', 'start_key'=>'int', 'num'=>'int', 'val'=>'mixed'], 'bcdiv' => ['?string', 'dividend'=>'string', 'divisor'=>'string', 'scale='=>'int'], 'bcmod' => ['?string', 'dividend'=>'string', 'divisor'=>'string', 'scale='=>'int'], 'bcpowmod' => ['?string', 'base'=>'string', 'exponent'=>'string', 'modulus'=>'string', 'scale='=>'int'], From 4f5dacff0cbdd42d51644f6d2a8c20cdcf5d9941 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 8 Oct 2021 09:39:17 +0200 Subject: [PATCH 0428/1284] Updated BetterReflection --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 88aaae4948..20469e615d 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "nette/utils": "^3.1.3", "nikic/php-parser": "4.13.0", "ondram/ci-detector": "^3.4.0", - "ondrejmirtes/better-reflection": "4.3.70", + "ondrejmirtes/better-reflection": "4.3.71", "phpstan/php-8-stubs": "^0.1.23", "phpstan/phpdoc-parser": "^1.2.0", "react/child-process": "^0.6.1", diff --git a/composer.lock b/composer.lock index 5e67ba1ccf..7cdc344932 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c1d91467e312e364a2d0be0d50163a50", + "content-hash": "a56f93f1b1de4b42ec862f98080ebc7a", "packages": [ { "name": "clue/block-react", @@ -2074,16 +2074,16 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "4.3.70", + "version": "4.3.71", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "ec87deadc70f01c6d66ed22d653b7292cccdcbc9" + "reference": "2b7b2495ec99287e89dea471b7adc42abadcdb56" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/ec87deadc70f01c6d66ed22d653b7292cccdcbc9", - "reference": "ec87deadc70f01c6d66ed22d653b7292cccdcbc9", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/2b7b2495ec99287e89dea471b7adc42abadcdb56", + "reference": "2b7b2495ec99287e89dea471b7adc42abadcdb56", "shasum": "" }, "require": { @@ -2138,9 +2138,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/4.3.70" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/4.3.71" }, - "time": "2021-09-20T14:59:55+00:00" + "time": "2021-10-08T07:38:32+00:00" }, { "name": "phpstan/php-8-stubs", From 5670cf221723eb652643623ad487351cb187f6c3 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 11 Oct 2021 17:03:24 +0200 Subject: [PATCH 0429/1284] [BCB] Removed --paths-file CLI option --- src/Command/AnalyseCommand.php | 4 ---- src/Command/ClearResultCacheCommand.php | 1 - src/Command/CommandHelper.php | 25 --------------------- src/Command/FixerWorkerCommand.php | 4 ---- src/Command/WorkerCommand.php | 4 ---- src/Process/ProcessHelper.php | 1 - tests/PHPStan/Command/CommandHelperTest.php | 2 -- 7 files changed, 41 deletions(-) diff --git a/src/Command/AnalyseCommand.php b/src/Command/AnalyseCommand.php index 4f54ceec3c..0fab3d7261 100644 --- a/src/Command/AnalyseCommand.php +++ b/src/Command/AnalyseCommand.php @@ -48,7 +48,6 @@ protected function configure(): void ->setDescription('Analyses source code') ->setDefinition([ new InputArgument('paths', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'Paths with source code to run analysis on'), - new InputOption('paths-file', null, InputOption::VALUE_REQUIRED, 'Path to a file with a list of paths to run analysis on'), new InputOption('configuration', 'c', InputOption::VALUE_REQUIRED, 'Path to project configuration file'), new InputOption(self::OPTION_LEVEL, 'l', InputOption::VALUE_REQUIRED, 'Level of rule options - the higher the stricter'), new InputOption(ErrorsConsoleStyle::OPTION_NO_PROGRESS, null, InputOption::VALUE_NONE, 'Do not show progress bar, only results'), @@ -91,7 +90,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int $autoloadFile = $input->getOption('autoload-file'); $configuration = $input->getOption('configuration'); $level = $input->getOption(self::OPTION_LEVEL); - $pathsFile = $input->getOption('paths-file'); $allowXdebug = $input->getOption('xdebug'); $debugEnabled = (bool) $input->getOption('debug'); $fix = (bool) $input->getOption('fix') || (bool) $input->getOption('watch') || (bool) $input->getOption('pro'); @@ -110,7 +108,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int || (!is_string($autoloadFile) && $autoloadFile !== null) || (!is_string($configuration) && $configuration !== null) || (!is_string($level) && $level !== null) - || (!is_string($pathsFile) && $pathsFile !== null) || (!is_bool($allowXdebug)) ) { throw new \PHPStan\ShouldNotHappenException(); @@ -121,7 +118,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int $input, $output, $paths, - $pathsFile, $memoryLimit, $autoloadFile, $this->composerAutoloaderProjectPaths, diff --git a/src/Command/ClearResultCacheCommand.php b/src/Command/ClearResultCacheCommand.php index 6b5198c4dc..d4a8103184 100644 --- a/src/Command/ClearResultCacheCommand.php +++ b/src/Command/ClearResultCacheCommand.php @@ -55,7 +55,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int $output, ['.'], null, - null, $autoloadFile, $this->composerAutoloaderProjectPaths, $configuration, diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php index e784a0aef8..935d5eb7bb 100644 --- a/src/Command/CommandHelper.php +++ b/src/Command/CommandHelper.php @@ -37,7 +37,6 @@ public static function begin( InputInterface $input, OutputInterface $output, array $paths, - ?string $pathsFile, ?string $memoryLimit, ?string $autoloadFile, array $composerAutoloaderProjectPaths, @@ -118,30 +117,6 @@ public static function begin( return $currentWorkingDirectoryFileHelper->normalizePath($currentWorkingDirectoryFileHelper->absolutizePath($path)); }, $paths); - if (count($paths) === 0 && $pathsFile !== null) { - $pathsFile = $currentWorkingDirectoryFileHelper->absolutizePath($pathsFile); - if (!file_exists($pathsFile)) { - $errorOutput->writeLineFormatted(sprintf('Paths file %s does not exist.', $pathsFile)); - throw new \PHPStan\Command\InceptionNotSuccessfulException(); - } - - try { - $pathsString = FileReader::read($pathsFile); - } catch (\PHPStan\File\CouldNotReadFileException $e) { - $errorOutput->writeLineFormatted($e->getMessage()); - throw new \PHPStan\Command\InceptionNotSuccessfulException(); - } - - $paths = array_values(array_filter(explode("\n", $pathsString), static function (string $path): bool { - return trim($path) !== ''; - })); - - $pathsFileFileHelper = new FileHelper(dirname($pathsFile)); - $paths = array_map(static function (string $path) use ($pathsFileFileHelper): string { - return $pathsFileFileHelper->normalizePath($pathsFileFileHelper->absolutizePath($path)); - }, $paths); - } - $analysedPathsFromConfig = []; $containerFactory = new ContainerFactory($currentWorkingDirectory); $projectConfig = null; diff --git a/src/Command/FixerWorkerCommand.php b/src/Command/FixerWorkerCommand.php index 3a82fac66f..03ffaacd60 100644 --- a/src/Command/FixerWorkerCommand.php +++ b/src/Command/FixerWorkerCommand.php @@ -38,7 +38,6 @@ protected function configure(): void ->setDescription('(Internal) Support for PHPStan Pro.') ->setDefinition([ new InputArgument('paths', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'Paths with source code to run analysis on'), - new InputOption('paths-file', null, InputOption::VALUE_REQUIRED, 'Path to a file with a list of paths to run analysis on'), new InputOption('configuration', 'c', InputOption::VALUE_REQUIRED, 'Path to project configuration file'), new InputOption(AnalyseCommand::OPTION_LEVEL, 'l', InputOption::VALUE_REQUIRED, 'Level of rule options - the higher the stricter'), new InputOption('autoload-file', 'a', InputOption::VALUE_REQUIRED, 'Project\'s additional autoload file path'), @@ -59,7 +58,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int $autoloadFile = $input->getOption('autoload-file'); $configuration = $input->getOption('configuration'); $level = $input->getOption(AnalyseCommand::OPTION_LEVEL); - $pathsFile = $input->getOption('paths-file'); $allowXdebug = $input->getOption('xdebug'); $allowParallel = $input->getOption('allow-parallel'); @@ -69,7 +67,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int || (!is_string($autoloadFile) && $autoloadFile !== null) || (!is_string($configuration) && $configuration !== null) || (!is_string($level) && $level !== null) - || (!is_string($pathsFile) && $pathsFile !== null) || (!is_bool($allowXdebug)) || (!is_bool($allowParallel)) ) { @@ -107,7 +104,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int $input, $output, $paths, - $pathsFile, $memoryLimit, $autoloadFile, $this->composerAutoloaderProjectPaths, diff --git a/src/Command/WorkerCommand.php b/src/Command/WorkerCommand.php index e1154d3829..69dba03e90 100644 --- a/src/Command/WorkerCommand.php +++ b/src/Command/WorkerCommand.php @@ -46,7 +46,6 @@ protected function configure(): void ->setDescription('(Internal) Support for parallel analysis.') ->setDefinition([ new InputArgument('paths', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'Paths with source code to run analysis on'), - new InputOption('paths-file', null, InputOption::VALUE_REQUIRED, 'Path to a file with a list of paths to run analysis on'), new InputOption('configuration', 'c', InputOption::VALUE_REQUIRED, 'Path to project configuration file'), new InputOption(AnalyseCommand::OPTION_LEVEL, 'l', InputOption::VALUE_REQUIRED, 'Level of rule options - the higher the stricter'), new InputOption('autoload-file', 'a', InputOption::VALUE_REQUIRED, 'Project\'s additional autoload file path'), @@ -66,7 +65,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int $autoloadFile = $input->getOption('autoload-file'); $configuration = $input->getOption('configuration'); $level = $input->getOption(AnalyseCommand::OPTION_LEVEL); - $pathsFile = $input->getOption('paths-file'); $allowXdebug = $input->getOption('xdebug'); $port = $input->getOption('port'); $identifier = $input->getOption('identifier'); @@ -77,7 +75,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int || (!is_string($autoloadFile) && $autoloadFile !== null) || (!is_string($configuration) && $configuration !== null) || (!is_string($level) && $level !== null) - || (!is_string($pathsFile) && $pathsFile !== null) || (!is_bool($allowXdebug)) || !is_string($port) || !is_string($identifier) @@ -101,7 +98,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int $input, $output, $paths, - $pathsFile, $memoryLimit, $autoloadFile, $this->composerAutoloaderProjectPaths, diff --git a/src/Process/ProcessHelper.php b/src/Process/ProcessHelper.php index c3d6a7d05e..0e16484ddc 100644 --- a/src/Process/ProcessHelper.php +++ b/src/Process/ProcessHelper.php @@ -46,7 +46,6 @@ public static function getWorkerCommand( } $options = [ - 'paths-file', AnalyseCommand::OPTION_LEVEL, 'autoload-file', 'memory-limit', diff --git a/tests/PHPStan/Command/CommandHelperTest.php b/tests/PHPStan/Command/CommandHelperTest.php index 191b70fbfc..5f72cadaee 100644 --- a/tests/PHPStan/Command/CommandHelperTest.php +++ b/tests/PHPStan/Command/CommandHelperTest.php @@ -119,7 +119,6 @@ public function testBegin( [__DIR__], null, null, - null, [], $projectConfigFile, null, @@ -304,7 +303,6 @@ public function testResolveParameters( [__DIR__], null, null, - null, [], $configFile, null, From ba3f7ed925a7c63e7c8369484eb5ab873080f217 Mon Sep 17 00:00:00 2001 From: olsavmic Date: Mon, 11 Oct 2021 17:35:41 +0200 Subject: [PATCH 0430/1284] RuleLevelHelper::findTypeToCheck - make null-safe shortcircuiting optional --- .../NonexistentOffsetInArrayDimFetchCheck.php | 3 ++- .../NonexistentOffsetInArrayDimFetchRule.php | 3 ++- .../Arrays/OffsetAccessAssignmentRule.php | 3 ++- src/Rules/Classes/ClassConstantRule.php | 3 ++- src/Rules/Functions/CallCallablesRule.php | 3 ++- src/Rules/Methods/CallMethodsRule.php | 4 +++- src/Rules/Methods/CallStaticMethodsRule.php | 3 ++- ...oMethodStatementWithoutSideEffectsRule.php | 3 ++- ...cMethodStatementWithoutSideEffectsRule.php | 3 ++- .../InvalidComparisonOperationRule.php | 18 +++++++++++---- src/Rules/Properties/AccessPropertiesRule.php | 3 ++- .../Properties/AccessStaticPropertiesRule.php | 3 ++- src/Rules/RuleLevelHelper.php | 5 ---- .../Levels/data/arrayDestructuring.php | 2 +- .../Arrays/ArrayDestructuringRuleTest.php | 14 +++++++++++ .../Arrays/IterableInForeachRuleTest.php | 15 ++++++++++++ ...nexistentOffsetInArrayDimFetchRuleTest.php | 14 +++++++++++ .../Arrays/OffsetAccessAssignOpRuleTest.php | 10 ++++++++ .../Arrays/OffsetAccessAssignmentRuleTest.php | 16 +++++++++++++ .../OffsetAccessValueAssignmentRuleTest.php | 14 +++++++++++ .../Arrays/UnpackIterableInArrayRuleTest.php | 14 +++++++++++ .../data/array-destructuring-nullsafe.php | 23 +++++++++++++++++++ .../Arrays/data/foreach-iterable-nullsafe.php | 17 ++++++++++++++ .../data/nonexistent-offset-nullsafe.php | 19 +++++++++++++++ .../offset-access-assignment-nullsafe.php | 15 ++++++++++++ .../data/offset-access-assignop-nullsafe.php | 22 ++++++++++++++++++ ...ffset-access-value-assignment-nullsafe.php | 19 +++++++++++++++ .../Arrays/data/unpack-iterable-nullsafe.php | 21 +++++++++++++++++ tests/PHPStan/Rules/Cast/EchoRuleTest.php | 14 +++++++++++ .../Rules/Cast/InvalidCastRuleTest.php | 14 +++++++++++ .../InvalidPartOfEncapsedStringRuleTest.php | 14 +++++++++++ tests/PHPStan/Rules/Cast/PrintRuleTest.php | 14 +++++++++++ .../PHPStan/Rules/Cast/data/echo-nullsafe.php | 16 +++++++++++++ .../Rules/Cast/data/invalid-cast-nullsafe.php | 14 +++++++++++ .../data/invalid-encapsed-part-nullsafe.php | 12 ++++++++++ .../Rules/Cast/data/print-nullsafe.php | 16 +++++++++++++ .../Rules/Classes/ClassConstantRuleTest.php | 10 ++++++++ .../Classes/data/class-constant-nullsafe.php | 17 ++++++++++++++ .../Rules/Functions/CallCallablesRuleTest.php | 15 ++++++++++++ .../Functions/data/callables-nullsafe.php | 20 ++++++++++++++++ .../InvalidBinaryOperationRuleTest.php | 14 +++++++++++ .../InvalidComparisonOperationRuleTest.php | 14 +++++++++++ .../data/invalid-binary-nullsafe.php | 13 +++++++++++ .../data/invalid-comparison-nullsafe.php | 13 +++++++++++ .../Rules/Variables/ThrowTypeRuleTest.php | 14 +++++++++++ .../Variables/VariableCloningRuleTest.php | 14 +++++++++++ .../Variables/data/throw-values-nullsafe.php | 18 +++++++++++++++ .../data/variable-cloning-nullsafe.php | 12 ++++++++++ 48 files changed, 559 insertions(+), 21 deletions(-) create mode 100644 tests/PHPStan/Rules/Arrays/data/array-destructuring-nullsafe.php create mode 100644 tests/PHPStan/Rules/Arrays/data/foreach-iterable-nullsafe.php create mode 100644 tests/PHPStan/Rules/Arrays/data/nonexistent-offset-nullsafe.php create mode 100644 tests/PHPStan/Rules/Arrays/data/offset-access-assignment-nullsafe.php create mode 100644 tests/PHPStan/Rules/Arrays/data/offset-access-assignop-nullsafe.php create mode 100644 tests/PHPStan/Rules/Arrays/data/offset-access-value-assignment-nullsafe.php create mode 100644 tests/PHPStan/Rules/Arrays/data/unpack-iterable-nullsafe.php create mode 100644 tests/PHPStan/Rules/Cast/data/echo-nullsafe.php create mode 100644 tests/PHPStan/Rules/Cast/data/invalid-cast-nullsafe.php create mode 100644 tests/PHPStan/Rules/Cast/data/invalid-encapsed-part-nullsafe.php create mode 100644 tests/PHPStan/Rules/Cast/data/print-nullsafe.php create mode 100644 tests/PHPStan/Rules/Classes/data/class-constant-nullsafe.php create mode 100644 tests/PHPStan/Rules/Functions/data/callables-nullsafe.php create mode 100644 tests/PHPStan/Rules/Operators/data/invalid-binary-nullsafe.php create mode 100644 tests/PHPStan/Rules/Operators/data/invalid-comparison-nullsafe.php create mode 100644 tests/PHPStan/Rules/Variables/data/throw-values-nullsafe.php create mode 100644 tests/PHPStan/Rules/Variables/data/variable-cloning-nullsafe.php diff --git a/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchCheck.php b/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchCheck.php index f9e5c2f32f..84d91b14d0 100644 --- a/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchCheck.php +++ b/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchCheck.php @@ -3,6 +3,7 @@ namespace PHPStan\Rules\Arrays; use PhpParser\Node\Expr; +use PHPStan\Analyser\NullsafeOperatorHelper; use PHPStan\Analyser\Scope; use PHPStan\Rules\RuleError; use PHPStan\Rules\RuleErrorBuilder; @@ -43,7 +44,7 @@ public function check( { $typeResult = $this->ruleLevelHelper->findTypeToCheck( $scope, - $var, + NullsafeOperatorHelper::getNullsafeShortcircuitedExpr($var), $unknownClassPattern, static function (Type $type) use ($dimType): bool { return $type->hasOffsetValueType($dimType)->yes(); diff --git a/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchRule.php b/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchRule.php index 758d76cb43..105f846047 100644 --- a/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchRule.php +++ b/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchRule.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\Arrays; +use PHPStan\Analyser\NullsafeOperatorHelper; use PHPStan\Analyser\Scope; use PHPStan\Internal\SprintfHelper; use PHPStan\Rules\RuleErrorBuilder; @@ -50,7 +51,7 @@ public function processNode(\PhpParser\Node $node, Scope $scope): array $isOffsetAccessibleTypeResult = $this->ruleLevelHelper->findTypeToCheck( $scope, - $node->var, + NullsafeOperatorHelper::getNullsafeShortcircuitedExpr($node->var), $unknownClassPattern, static function (Type $type): bool { return $type->isOffsetAccessible()->yes(); diff --git a/src/Rules/Arrays/OffsetAccessAssignmentRule.php b/src/Rules/Arrays/OffsetAccessAssignmentRule.php index e111fca026..f84b52bfd9 100644 --- a/src/Rules/Arrays/OffsetAccessAssignmentRule.php +++ b/src/Rules/Arrays/OffsetAccessAssignmentRule.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\Arrays; +use PHPStan\Analyser\NullsafeOperatorHelper; use PHPStan\Analyser\Scope; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Rules\RuleLevelHelper; @@ -41,7 +42,7 @@ public function processNode(\PhpParser\Node $node, Scope $scope): array $varTypeResult = $this->ruleLevelHelper->findTypeToCheck( $scope, - $node->var, + NullsafeOperatorHelper::getNullsafeShortcircuitedExpr($node->var), '', static function (Type $varType) use ($potentialDimType): bool { $arrayDimType = $varType->setOffsetValueType($potentialDimType, new MixedType()); diff --git a/src/Rules/Classes/ClassConstantRule.php b/src/Rules/Classes/ClassConstantRule.php index 58d5b8e65e..f9c8bdc908 100644 --- a/src/Rules/Classes/ClassConstantRule.php +++ b/src/Rules/Classes/ClassConstantRule.php @@ -4,6 +4,7 @@ use PhpParser\Node; use PhpParser\Node\Expr\ClassConstFetch; +use PHPStan\Analyser\NullsafeOperatorHelper; use PHPStan\Analyser\Scope; use PHPStan\Internal\SprintfHelper; use PHPStan\Php\PhpVersion; @@ -118,7 +119,7 @@ public function processNode(Node $node, Scope $scope): array } else { $classTypeResult = $this->ruleLevelHelper->findTypeToCheck( $scope, - $class, + NullsafeOperatorHelper::getNullsafeShortcircuitedExpr($class), sprintf('Access to constant %s on an unknown class %%s.', SprintfHelper::escapeFormatString($constantName)), static function (Type $type) use ($constantName): bool { return $type->canAccessConstants()->yes() && $type->hasConstant($constantName)->yes(); diff --git a/src/Rules/Functions/CallCallablesRule.php b/src/Rules/Functions/CallCallablesRule.php index cbe6728ed7..d1a51c1d0e 100644 --- a/src/Rules/Functions/CallCallablesRule.php +++ b/src/Rules/Functions/CallCallablesRule.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\Functions; +use PHPStan\Analyser\NullsafeOperatorHelper; use PHPStan\Analyser\Scope; use PHPStan\Internal\SprintfHelper; use PHPStan\Reflection\InaccessibleMethod; @@ -53,7 +54,7 @@ public function processNode( $typeResult = $this->ruleLevelHelper->findTypeToCheck( $scope, - $node->name, + NullsafeOperatorHelper::getNullsafeShortcircuitedExpr($node->name), 'Invoking callable on an unknown class %s.', static function (Type $type): bool { return $type->isCallable()->yes(); diff --git a/src/Rules/Methods/CallMethodsRule.php b/src/Rules/Methods/CallMethodsRule.php index d04d600986..b6c9c6953a 100644 --- a/src/Rules/Methods/CallMethodsRule.php +++ b/src/Rules/Methods/CallMethodsRule.php @@ -4,6 +4,7 @@ use PhpParser\Node; use PhpParser\Node\Expr\MethodCall; +use PHPStan\Analyser\NullsafeOperatorHelper; use PHPStan\Analyser\Scope; use PHPStan\Internal\SprintfHelper; use PHPStan\Reflection\ParametersAcceptorSelector; @@ -58,9 +59,10 @@ public function processNode(Node $node, Scope $scope): array } $name = $node->name->name; + $typeResult = $this->ruleLevelHelper->findTypeToCheck( $scope, - $node->var, + NullsafeOperatorHelper::getNullsafeShortcircuitedExpr($node->var), sprintf('Call to method %s() on an unknown class %%s.', SprintfHelper::escapeFormatString($name)), static function (Type $type) use ($name): bool { return $type->canCallMethods()->yes() && $type->hasMethod($name)->yes(); diff --git a/src/Rules/Methods/CallStaticMethodsRule.php b/src/Rules/Methods/CallStaticMethodsRule.php index 87fda9718c..d535942bde 100644 --- a/src/Rules/Methods/CallStaticMethodsRule.php +++ b/src/Rules/Methods/CallStaticMethodsRule.php @@ -5,6 +5,7 @@ use PhpParser\Node; use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Name; +use PHPStan\Analyser\NullsafeOperatorHelper; use PHPStan\Analyser\Scope; use PHPStan\Internal\SprintfHelper; use PHPStan\Reflection\MethodReflection; @@ -150,7 +151,7 @@ public function processNode(Node $node, Scope $scope): array } else { $classTypeResult = $this->ruleLevelHelper->findTypeToCheck( $scope, - $class, + NullsafeOperatorHelper::getNullsafeShortcircuitedExpr($class), sprintf('Call to static method %s() on an unknown class %%s.', SprintfHelper::escapeFormatString($methodName)), static function (Type $type) use ($methodName): bool { return $type->canCallMethods()->yes() && $type->hasMethod($methodName)->yes(); diff --git a/src/Rules/Methods/CallToMethodStatementWithoutSideEffectsRule.php b/src/Rules/Methods/CallToMethodStatementWithoutSideEffectsRule.php index 0ade6b4c03..9a70df628c 100644 --- a/src/Rules/Methods/CallToMethodStatementWithoutSideEffectsRule.php +++ b/src/Rules/Methods/CallToMethodStatementWithoutSideEffectsRule.php @@ -3,6 +3,7 @@ namespace PHPStan\Rules\Methods; use PhpParser\Node; +use PHPStan\Analyser\NullsafeOperatorHelper; use PHPStan\Analyser\Scope; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; @@ -46,7 +47,7 @@ public function processNode(Node $node, Scope $scope): array $typeResult = $this->ruleLevelHelper->findTypeToCheck( $scope, - $methodCall->var, + NullsafeOperatorHelper::getNullsafeShortcircuitedExpr($methodCall->var), '', static function (Type $type) use ($methodName): bool { return $type->canCallMethods()->yes() && $type->hasMethod($methodName)->yes(); diff --git a/src/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRule.php b/src/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRule.php index 70d25300bb..ffbc18b185 100644 --- a/src/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRule.php +++ b/src/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRule.php @@ -3,6 +3,7 @@ namespace PHPStan\Rules\Methods; use PhpParser\Node; +use PHPStan\Analyser\NullsafeOperatorHelper; use PHPStan\Analyser\Scope; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\Rule; @@ -60,7 +61,7 @@ public function processNode(Node $node, Scope $scope): array } else { $typeResult = $this->ruleLevelHelper->findTypeToCheck( $scope, - $staticCall->class, + NullsafeOperatorHelper::getNullsafeShortcircuitedExpr($staticCall->class), '', static function (Type $type) use ($methodName): bool { return $type->canCallMethods()->yes() && $type->hasMethod($methodName)->yes(); diff --git a/src/Rules/Operators/InvalidComparisonOperationRule.php b/src/Rules/Operators/InvalidComparisonOperationRule.php index 83c4ac30ad..df88d40f18 100644 --- a/src/Rules/Operators/InvalidComparisonOperationRule.php +++ b/src/Rules/Operators/InvalidComparisonOperationRule.php @@ -9,8 +9,10 @@ use PHPStan\Type\ErrorType; use PHPStan\Type\FloatType; use PHPStan\Type\IntegerType; +use PHPStan\Type\NullType; use PHPStan\Type\ObjectWithoutClassType; use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; @@ -48,10 +50,10 @@ public function processNode(Node $node, Scope $scope): array if ( ($this->isNumberType($scope, $node->left) && ( - $this->isObjectType($scope, $node->right) || $this->isArrayType($scope, $node->right) + $this->isPossiblyNullableObjectType($scope, $node->right) || $this->isPossiblyNullableArrayType($scope, $node->right) )) || ($this->isNumberType($scope, $node->right) && ( - $this->isObjectType($scope, $node->left) || $this->isArrayType($scope, $node->left) + $this->isPossiblyNullableObjectType($scope, $node->left) || $this->isPossiblyNullableArrayType($scope, $node->left) )) ) { return [ @@ -86,7 +88,7 @@ private function isNumberType(Scope $scope, Node\Expr $expr): bool return !$acceptedType->isSuperTypeOf($type)->no(); } - private function isObjectType(Scope $scope, Node\Expr $expr): bool + private function isPossiblyNullableObjectType(Scope $scope, Node\Expr $expr): bool { $acceptedType = new ObjectWithoutClassType(); @@ -103,6 +105,10 @@ static function (Type $type) use ($acceptedType): bool { return false; } + if (TypeCombinator::containsNull($type) && !$type instanceof NullType) { + $type = TypeCombinator::removeNull($type); + } + $isSuperType = $acceptedType->isSuperTypeOf($type); if ($type instanceof \PHPStan\Type\BenevolentUnionType) { return !$isSuperType->no(); @@ -111,7 +117,7 @@ static function (Type $type) use ($acceptedType): bool { return $isSuperType->yes(); } - private function isArrayType(Scope $scope, Node\Expr $expr): bool + private function isPossiblyNullableArrayType(Scope $scope, Node\Expr $expr): bool { $type = $this->ruleLevelHelper->findTypeToCheck( $scope, @@ -122,6 +128,10 @@ static function (Type $type): bool { } )->getType(); + if (TypeCombinator::containsNull($type) && !$type instanceof NullType) { + $type = TypeCombinator::removeNull($type); + } + return !($type instanceof ErrorType) && $type->isArray()->yes(); } diff --git a/src/Rules/Properties/AccessPropertiesRule.php b/src/Rules/Properties/AccessPropertiesRule.php index c2d43fab36..61c9373577 100644 --- a/src/Rules/Properties/AccessPropertiesRule.php +++ b/src/Rules/Properties/AccessPropertiesRule.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\PropertyFetch; use PhpParser\Node\Identifier; +use PHPStan\Analyser\NullsafeOperatorHelper; use PHPStan\Analyser\Scope; use PHPStan\Internal\SprintfHelper; use PHPStan\Reflection\ReflectionProvider; @@ -72,7 +73,7 @@ private function processSingleProperty(Scope $scope, PropertyFetch $node, string { $typeResult = $this->ruleLevelHelper->findTypeToCheck( $scope, - $node->var, + NullsafeOperatorHelper::getNullsafeShortcircuitedExpr($node->var), sprintf('Access to property $%s on an unknown class %%s.', SprintfHelper::escapeFormatString($name)), static function (Type $type) use ($name): bool { return $type->canAccessProperties()->yes() && $type->hasProperty($name)->yes(); diff --git a/src/Rules/Properties/AccessStaticPropertiesRule.php b/src/Rules/Properties/AccessStaticPropertiesRule.php index 7d96f249f3..b269eb5bc7 100644 --- a/src/Rules/Properties/AccessStaticPropertiesRule.php +++ b/src/Rules/Properties/AccessStaticPropertiesRule.php @@ -5,6 +5,7 @@ use PhpParser\Node; use PhpParser\Node\Expr\StaticPropertyFetch; use PhpParser\Node\Name; +use PHPStan\Analyser\NullsafeOperatorHelper; use PHPStan\Analyser\Scope; use PHPStan\Internal\SprintfHelper; use PHPStan\Reflection\ReflectionProvider; @@ -146,7 +147,7 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node, } else { $classTypeResult = $this->ruleLevelHelper->findTypeToCheck( $scope, - $node->class, + NullsafeOperatorHelper::getNullsafeShortcircuitedExpr($node->class), sprintf('Access to static property $%s on an unknown class %%s.', SprintfHelper::escapeFormatString($name)), static function (Type $type) use ($name): bool { return $type->canAccessProperties()->yes() && $type->hasProperty($name)->yes(); diff --git a/src/Rules/RuleLevelHelper.php b/src/Rules/RuleLevelHelper.php index 5ace8c6903..ab0437b604 100644 --- a/src/Rules/RuleLevelHelper.php +++ b/src/Rules/RuleLevelHelper.php @@ -3,7 +3,6 @@ namespace PHPStan\Rules; use PhpParser\Node\Expr; -use PHPStan\Analyser\NullsafeOperatorHelper; use PHPStan\Analyser\Scope; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\BenevolentUnionType; @@ -143,10 +142,6 @@ public function findTypeToCheck( $type = \PHPStan\Type\TypeCombinator::removeNull($type); } - if (TypeCombinator::containsNull($type)) { - $type = $scope->getType(NullsafeOperatorHelper::getNullsafeShortcircuitedExpr($var)); - } - if ( $this->checkExplicitMixed && $type instanceof MixedType diff --git a/tests/PHPStan/Levels/data/arrayDestructuring.php b/tests/PHPStan/Levels/data/arrayDestructuring.php index 5d7e61ea93..c97a3e6328 100644 --- a/tests/PHPStan/Levels/data/arrayDestructuring.php +++ b/tests/PHPStan/Levels/data/arrayDestructuring.php @@ -1,6 +1,6 @@ markTestSkipped('Test requires PHP 8.0.'); + } + + $this->analyse([__DIR__ . '/data/array-destructuring-nullsafe.php'], [ + [ + 'Cannot use array destructuring on array|null.', + 10, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Arrays/IterableInForeachRuleTest.php b/tests/PHPStan/Rules/Arrays/IterableInForeachRuleTest.php index f370d4fbc1..e678815bee 100644 --- a/tests/PHPStan/Rules/Arrays/IterableInForeachRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/IterableInForeachRuleTest.php @@ -52,4 +52,19 @@ public function testBug5744(): void ]); } + public function testRuleWithNullsafeVariant(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/foreach-iterable-nullsafe.php'], [ + [ + 'Argument of an invalid type array|null supplied for foreach, only iterables are supported.', + 14, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index e401b42aed..d473d9cc8b 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -332,4 +332,18 @@ public function testBug5744(): void ]); } + public function testRuleWithNullsafeVariant(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->analyse([__DIR__ . '/data/nonexistent-offset-nullsafe.php'], [ + [ + 'Offset 1 does not exist on array(\'a\' => int).', + 18, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Arrays/OffsetAccessAssignOpRuleTest.php b/tests/PHPStan/Rules/Arrays/OffsetAccessAssignOpRuleTest.php index 624b446d91..ec15c47d95 100644 --- a/tests/PHPStan/Rules/Arrays/OffsetAccessAssignOpRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/OffsetAccessAssignOpRuleTest.php @@ -37,4 +37,14 @@ public function testRuleWithoutUnions(): void $this->analyse([__DIR__ . '/data/offset-access-assignop.php'], []); } + public function testRuleWithNullsafeVariant(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->checkUnions = true; + $this->analyse([__DIR__ . '/data/offset-access-assignop-nullsafe.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Arrays/OffsetAccessAssignmentRuleTest.php b/tests/PHPStan/Rules/Arrays/OffsetAccessAssignmentRuleTest.php index 88f38c8ee1..665b9ec8fa 100644 --- a/tests/PHPStan/Rules/Arrays/OffsetAccessAssignmentRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/OffsetAccessAssignmentRuleTest.php @@ -127,4 +127,20 @@ public function testAssignNewOffsetToStubbedClass(): void $this->analyse([__DIR__ . '/data/new-offset-stub.php'], []); } + + public function testRuleWithNullsafeVariant(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/offset-access-assignment-nullsafe.php'], [ + [ + 'Cannot assign offset int|null to string.', + 14, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Arrays/OffsetAccessValueAssignmentRuleTest.php b/tests/PHPStan/Rules/Arrays/OffsetAccessValueAssignmentRuleTest.php index 366937c226..de0bb20612 100644 --- a/tests/PHPStan/Rules/Arrays/OffsetAccessValueAssignmentRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/OffsetAccessValueAssignmentRuleTest.php @@ -47,4 +47,18 @@ public function testRule(): void ]); } + public function testRuleWithNullsafeVariant(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->analyse([__DIR__ . '/data/offset-access-value-assignment-nullsafe.php'], [ + [ + 'ArrayAccess does not accept int|null.', + 18, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Arrays/UnpackIterableInArrayRuleTest.php b/tests/PHPStan/Rules/Arrays/UnpackIterableInArrayRuleTest.php index 833f4d6b78..22e1487a7b 100644 --- a/tests/PHPStan/Rules/Arrays/UnpackIterableInArrayRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/UnpackIterableInArrayRuleTest.php @@ -38,4 +38,18 @@ public function testRule(): void ]); } + public function testRuleWithNullsafeVariant(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->analyse([__DIR__ . '/data/unpack-iterable-nullsafe.php'], [ + [ + 'Only iterables can be unpacked, array|null given.', + 17, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Arrays/data/array-destructuring-nullsafe.php b/tests/PHPStan/Rules/Arrays/data/array-destructuring-nullsafe.php new file mode 100644 index 0000000000..d8389afe5e --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/array-destructuring-nullsafe.php @@ -0,0 +1,23 @@ += 8.0 + +namespace ArrayDestructuringNullsafe; + +class Foo +{ + + public function doFooBar(?Bar $bar): void + { + [$a] = $bar?->getArray(); + } + +} + +class Bar +{ + + public function getArray(): array + { + return []; + } + +} diff --git a/tests/PHPStan/Rules/Arrays/data/foreach-iterable-nullsafe.php b/tests/PHPStan/Rules/Arrays/data/foreach-iterable-nullsafe.php new file mode 100644 index 0000000000..4f19692219 --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/foreach-iterable-nullsafe.php @@ -0,0 +1,17 @@ += 8.0 + +namespace IterablesInForeachNullsafe; + +class Foo +{ + + /** @var int[] */ + public array $array; +} + +function doFoo(?Foo $foo) +{ + foreach ($foo?->array as $x) { + // pass + } +} diff --git a/tests/PHPStan/Rules/Arrays/data/nonexistent-offset-nullsafe.php b/tests/PHPStan/Rules/Arrays/data/nonexistent-offset-nullsafe.php new file mode 100644 index 0000000000..8185c01f8f --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/nonexistent-offset-nullsafe.php @@ -0,0 +1,19 @@ += 8.0 + +namespace NonexistentOffsetNullsafe; + +class Foo +{ + + /** @var array{a: int} */ + public array $array = [ + 'a' => 1, + ]; + +} + +function nonexistentOffsetOnArray(?Foo $foo): void +{ + echo $foo?->array['a']; + echo $foo?->array[1]; +} diff --git a/tests/PHPStan/Rules/Arrays/data/offset-access-assignment-nullsafe.php b/tests/PHPStan/Rules/Arrays/data/offset-access-assignment-nullsafe.php new file mode 100644 index 0000000000..3ddac9f8c8 --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/offset-access-assignment-nullsafe.php @@ -0,0 +1,15 @@ += 8.0 +declare(strict_types = 1); + +namespace OffsetAccessAssignmentNullsafe; + +class Bar +{ + public int $val; +} + +function doFoo(?Bar $bar) +{ + $str = 'abcd'; + $str[$bar?->val] = 'ok'; +} diff --git a/tests/PHPStan/Rules/Arrays/data/offset-access-assignop-nullsafe.php b/tests/PHPStan/Rules/Arrays/data/offset-access-assignop-nullsafe.php new file mode 100644 index 0000000000..8b81b5d9aa --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/offset-access-assignop-nullsafe.php @@ -0,0 +1,22 @@ += 8.0 +declare(strict_types=1); + +namespace OffsetAccessAssignOpNullsafe; + +class Bar +{ + public const INDEX = 'b'; + + /** @phpstan-var Bar::INDEX */ + public string $index = self::INDEX; +} + +function doFoo(?Bar $bar) +{ + /** @var array $array */ + $array = [ + 'a' => 123, + ]; + + $array['b'] += 'str'; +} diff --git a/tests/PHPStan/Rules/Arrays/data/offset-access-value-assignment-nullsafe.php b/tests/PHPStan/Rules/Arrays/data/offset-access-value-assignment-nullsafe.php new file mode 100644 index 0000000000..16e6d3cb00 --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/offset-access-value-assignment-nullsafe.php @@ -0,0 +1,19 @@ += 8.0 +declare(strict_types = 1); + +namespace OffsetAccessValueAssignmentNullsafe; + +class Bar +{ + public int $val; +} + +function doFoo(?Bar $bar) +{ + /** @var \ArrayAccess $array */ + $array = [ + 'a' => 123, + ]; + + $array['a'] = $bar?->val; +} diff --git a/tests/PHPStan/Rules/Arrays/data/unpack-iterable-nullsafe.php b/tests/PHPStan/Rules/Arrays/data/unpack-iterable-nullsafe.php new file mode 100644 index 0000000000..c4b49f2b75 --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/unpack-iterable-nullsafe.php @@ -0,0 +1,21 @@ += 8.0 + +namespace UnpackIterableNullsafe; + +class Bar +{ + /** @var int[] */ + public array $array; +} + +class Foo +{ + + public function doFoo(?Bar $bar) + { + $foo = [ + ...$bar?->array, + ]; + } + +} diff --git a/tests/PHPStan/Rules/Cast/EchoRuleTest.php b/tests/PHPStan/Rules/Cast/EchoRuleTest.php index 81621e9f19..b4d5bf6b54 100644 --- a/tests/PHPStan/Rules/Cast/EchoRuleTest.php +++ b/tests/PHPStan/Rules/Cast/EchoRuleTest.php @@ -49,4 +49,18 @@ public function testEchoRule(): void ]); } + public function testRuleWithNullsafeVariant(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->analyse([__DIR__ . '/data/echo-nullsafe.php'], [ + [ + 'Parameter #1 (array|null) of echo cannot be converted to string.', + 15, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Cast/InvalidCastRuleTest.php b/tests/PHPStan/Rules/Cast/InvalidCastRuleTest.php index 8794b9b6d9..a2ad501a6a 100644 --- a/tests/PHPStan/Rules/Cast/InvalidCastRuleTest.php +++ b/tests/PHPStan/Rules/Cast/InvalidCastRuleTest.php @@ -47,4 +47,18 @@ public function testBug5162(): void $this->analyse([__DIR__ . '/data/bug-5162.php'], []); } + public function testRuleWithNullsafeVariant(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->analyse([__DIR__ . '/data/invalid-cast-nullsafe.php'], [ + [ + 'Cannot cast stdClass|null to string.', + 13, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Cast/InvalidPartOfEncapsedStringRuleTest.php b/tests/PHPStan/Rules/Cast/InvalidPartOfEncapsedStringRuleTest.php index 0429ec48ee..f3d9da4630 100644 --- a/tests/PHPStan/Rules/Cast/InvalidPartOfEncapsedStringRuleTest.php +++ b/tests/PHPStan/Rules/Cast/InvalidPartOfEncapsedStringRuleTest.php @@ -28,4 +28,18 @@ public function testRule(): void ]); } + public function testRuleWithNullsafeVariant(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->analyse([__DIR__ . '/data/invalid-encapsed-part-nullsafe.php'], [ + [ + 'Part $bar?->obj (stdClass|null) of encapsed string cannot be cast to string.', + 11, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Cast/PrintRuleTest.php b/tests/PHPStan/Rules/Cast/PrintRuleTest.php index fa12ad4424..dc5e0429cf 100644 --- a/tests/PHPStan/Rules/Cast/PrintRuleTest.php +++ b/tests/PHPStan/Rules/Cast/PrintRuleTest.php @@ -53,4 +53,18 @@ public function testPrintRule(): void ]); } + public function testRuleWithNullsafeVariant(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->analyse([__DIR__ . '/data/print-nullsafe.php'], [ + [ + 'Parameter array|null of print cannot be converted to string.', + 15, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Cast/data/echo-nullsafe.php b/tests/PHPStan/Rules/Cast/data/echo-nullsafe.php new file mode 100644 index 0000000000..05df4374c0 --- /dev/null +++ b/tests/PHPStan/Rules/Cast/data/echo-nullsafe.php @@ -0,0 +1,16 @@ += 8.0 + +declare(strict_types = 1); + +namespace EchoNullsafe; + +class Bar +{ + /** @var int[] */ + public array $array; +} + +function def(?Bar $bar) +{ + echo $bar?->array; +} diff --git a/tests/PHPStan/Rules/Cast/data/invalid-cast-nullsafe.php b/tests/PHPStan/Rules/Cast/data/invalid-cast-nullsafe.php new file mode 100644 index 0000000000..ccdd3b4a61 --- /dev/null +++ b/tests/PHPStan/Rules/Cast/data/invalid-cast-nullsafe.php @@ -0,0 +1,14 @@ += 8.0 + +namespace InvalidCastNullsafe; + +class Bar +{ + public \stdClass $obj; +} + +function doFoo( + ?Bar $bar +) { + (string) $bar?->obj; +}; diff --git a/tests/PHPStan/Rules/Cast/data/invalid-encapsed-part-nullsafe.php b/tests/PHPStan/Rules/Cast/data/invalid-encapsed-part-nullsafe.php new file mode 100644 index 0000000000..25f93d4fd1 --- /dev/null +++ b/tests/PHPStan/Rules/Cast/data/invalid-encapsed-part-nullsafe.php @@ -0,0 +1,12 @@ += 8.0 + +namespace InvalidEncapsedPartNullsafe; + +class Bar +{ + public \stdClass $obj; +} + +function doFoo(?Bar $bar) { + "{$bar?->obj} bar"; +} diff --git a/tests/PHPStan/Rules/Cast/data/print-nullsafe.php b/tests/PHPStan/Rules/Cast/data/print-nullsafe.php new file mode 100644 index 0000000000..444692e425 --- /dev/null +++ b/tests/PHPStan/Rules/Cast/data/print-nullsafe.php @@ -0,0 +1,16 @@ += 8.0 + +declare(strict_types = 1); + +namespace PrintNullsafe; + +class Bar +{ + /** @var int[] */ + public array $array; +} + +function def(?Bar $bar) +{ + print $bar?->array; +} diff --git a/tests/PHPStan/Rules/Classes/ClassConstantRuleTest.php b/tests/PHPStan/Rules/Classes/ClassConstantRuleTest.php index 19b0edef06..4a8b7ce697 100644 --- a/tests/PHPStan/Rules/Classes/ClassConstantRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ClassConstantRuleTest.php @@ -277,4 +277,14 @@ public function testAttributes(): void ]); } + public function testRuleWithNullsafeVariant(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->phpVersion = PHP_VERSION_ID; + $this->analyse([__DIR__ . '/data/class-constant-nullsafe.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Classes/data/class-constant-nullsafe.php b/tests/PHPStan/Rules/Classes/data/class-constant-nullsafe.php new file mode 100644 index 0000000000..4d9ecf91bb --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/class-constant-nullsafe.php @@ -0,0 +1,17 @@ += 8.0 + +namespace ClassConstantNullsafeNamespace; + +class Foo { + public const LOREM = 'lorem'; + +} +class Bar +{ + public Foo $foo; +} + +function doFoo(?Bar $bar) +{ + $bar?->foo::LOREM; +} diff --git a/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php b/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php index 221ba5c90b..5353534e56 100644 --- a/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php @@ -198,4 +198,19 @@ public function testBug3566(bool $checkExplicitMixed, array $errors): void $this->analyse([__DIR__ . '/data/bug-3566.php'], $errors); } + public function testRuleWithNullsafeVariant(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/callables-nullsafe.php'], [ + [ + 'Parameter #1 $val of closure expects int, int|null given.', + 18, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/callables-nullsafe.php b/tests/PHPStan/Rules/Functions/data/callables-nullsafe.php new file mode 100644 index 0000000000..8cba81fa7f --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/callables-nullsafe.php @@ -0,0 +1,20 @@ += 8.0 + +namespace CallablesNullsafe; + +class Bar +{ + + public int $val; + +} + +function doFoo(?Bar $bar): void +{ + $fn = function (int $val) { + + }; + + $fn($bar?->val); +} + diff --git a/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php b/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php index e72e1f3364..a606e66cfb 100644 --- a/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php +++ b/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php @@ -122,4 +122,18 @@ public function testBug3515(): void $this->analyse([__DIR__ . '/data/bug-3515.php'], []); } + public function testRuleWithNullsafeVariant(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->analyse([__DIR__ . '/data/invalid-binary-nullsafe.php'], [ + [ + 'Binary operation "+" between array|null and \'2\' results in an error.', + 12, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Operators/InvalidComparisonOperationRuleTest.php b/tests/PHPStan/Rules/Operators/InvalidComparisonOperationRuleTest.php index ef493ca348..72f122261f 100644 --- a/tests/PHPStan/Rules/Operators/InvalidComparisonOperationRuleTest.php +++ b/tests/PHPStan/Rules/Operators/InvalidComparisonOperationRuleTest.php @@ -143,4 +143,18 @@ public function testRule(): void ]); } + public function testRuleWithNullsafeVariant(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->analyse([__DIR__ . '/data/invalid-comparison-nullsafe.php'], [ + [ + 'Comparison operation "==" between stdClass|null and int results in an error.', + 12, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Operators/data/invalid-binary-nullsafe.php b/tests/PHPStan/Rules/Operators/data/invalid-binary-nullsafe.php new file mode 100644 index 0000000000..5227e1fcac --- /dev/null +++ b/tests/PHPStan/Rules/Operators/data/invalid-binary-nullsafe.php @@ -0,0 +1,13 @@ += 8.0 + +namespace InvalidBinaryNullsafe; + +class Bar +{ + public array $array; +} + +function dooFoo(?Bar $bar) +{ + $bar?->array + '2'; +} diff --git a/tests/PHPStan/Rules/Operators/data/invalid-comparison-nullsafe.php b/tests/PHPStan/Rules/Operators/data/invalid-comparison-nullsafe.php new file mode 100644 index 0000000000..e3c5338507 --- /dev/null +++ b/tests/PHPStan/Rules/Operators/data/invalid-comparison-nullsafe.php @@ -0,0 +1,13 @@ += 8.0 + +namespace InvalidComparisonNullsafe; + +class Bar +{ + public \stdClass $val; +} + +function doFoo(?Bar $bar, int $a) +{ + $bar?->val == $a; +} diff --git a/tests/PHPStan/Rules/Variables/ThrowTypeRuleTest.php b/tests/PHPStan/Rules/Variables/ThrowTypeRuleTest.php index f674af7523..d997c2de09 100644 --- a/tests/PHPStan/Rules/Variables/ThrowTypeRuleTest.php +++ b/tests/PHPStan/Rules/Variables/ThrowTypeRuleTest.php @@ -50,4 +50,18 @@ public function testClassExists(): void $this->analyse([__DIR__ . '/data/throw-class-exists.php'], []); } + public function testRuleWithNullsafeVariant(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->analyse([__DIR__ . '/data/throw-values-nullsafe.php'], [ + [ + 'Invalid type Exception|null to throw.', + 17, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Variables/VariableCloningRuleTest.php b/tests/PHPStan/Rules/Variables/VariableCloningRuleTest.php index 4371c48567..fe8bc056d9 100644 --- a/tests/PHPStan/Rules/Variables/VariableCloningRuleTest.php +++ b/tests/PHPStan/Rules/Variables/VariableCloningRuleTest.php @@ -42,4 +42,18 @@ public function testClone(): void ]); } + public function testRuleWithNullsafeVariant(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->analyse([__DIR__ . '/data/variable-cloning-nullsafe.php'], [ + [ + 'Cannot clone stdClass|null.', + 11, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Variables/data/throw-values-nullsafe.php b/tests/PHPStan/Rules/Variables/data/throw-values-nullsafe.php new file mode 100644 index 0000000000..24ace167df --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/throw-values-nullsafe.php @@ -0,0 +1,18 @@ += 8.0 + +namespace ThrowValuesNullsafe; + +class Bar +{ + + function doException(): \Exception + { + return new \Exception(); + } + +} + +function doFoo(?Bar $bar) +{ + throw $bar?->doException(); +} diff --git a/tests/PHPStan/Rules/Variables/data/variable-cloning-nullsafe.php b/tests/PHPStan/Rules/Variables/data/variable-cloning-nullsafe.php new file mode 100644 index 0000000000..41640daa30 --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/variable-cloning-nullsafe.php @@ -0,0 +1,12 @@ += 8.0 + +namespace VariableCloningNullsafe; + +class Bar +{ + public \stdClass $foo; +} + +function doFoo(?Bar $bar) { + clone $bar?->foo; +}; From cc6d89cb5978492801a95d0e3e3d7190c19ce30b Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 11 Oct 2021 17:39:39 +0200 Subject: [PATCH 0431/1284] IntegerRangeMath: cover more maxima cases --- src/Analyser/MutatingScope.php | 9 +++++++++ tests/PHPStan/Analyser/data/integer-range-types.php | 10 ++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index ae1b426ab2..de1467d0a5 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -5300,6 +5300,15 @@ private function integerRangeMath(Type $range, Expr $node, Type $operand): Type $max = $rangeMax !== null && $operand->getMax() !== null && $operand->getMax() !== 0 ? $rangeMax / $operand->getMax() : null; } + if ($range instanceof IntegerRangeType && $operand instanceof IntegerRangeType) { + if ($rangeMax === null && $operand->getMax() === null) { + $min = 0; + } elseif ($rangeMin === null && $operand->getMin() === null) { + $min = null; + $max = null; + } + } + if ($operand instanceof IntegerRangeType && ($operand->getMin() === null || $operand->getMax() === null) || ($rangeMin === null || $rangeMax === null) diff --git a/tests/PHPStan/Analyser/data/integer-range-types.php b/tests/PHPStan/Analyser/data/integer-range-types.php index a7ef89e2cd..aa9608fbc3 100644 --- a/tests/PHPStan/Analyser/data/integer-range-types.php +++ b/tests/PHPStan/Analyser/data/integer-range-types.php @@ -281,6 +281,16 @@ public function math($i, $j, $z, $pi, $r1, $r2, $r3, $rMin, $rMax, $x, $y) { assertType('5|10|15|20|30', $x / $y); + assertType('float|int<0, max>', $rMax / $rMax); + assertType('(float|int)', $rMin / $rMin); + } + + /** + * @param int<0, max> $a + * @param int<0, max> $b + */ + function divisionLoosesInformation(int $a, int $b): void { + assertType('float|int<0, max>',$a/$b); } /** From 4653d1574ec85393d2f083ddd134c3d48ec960e1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 11 Oct 2021 18:09:04 +0200 Subject: [PATCH 0432/1284] SimpleXMLElement probably iterates over `static` --- stubs/iterable.stub | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/stubs/iterable.stub b/stubs/iterable.stub index 4e42965e80..3401915391 100644 --- a/stubs/iterable.stub +++ b/stubs/iterable.stub @@ -81,10 +81,10 @@ class Generator implements Iterator } /** - * @implements Traversable - * @implements ArrayAccess - * @implements Iterator - * @implements RecursiveIterator + * @implements Traversable + * @implements ArrayAccess + * @implements Iterator + * @implements RecursiveIterator */ class SimpleXMLElement implements Traversable, ArrayAccess, Iterator, RecursiveIterator { From ca3aad0790d9a470f74992e1c0196d593f5bebeb Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 12 Oct 2021 13:05:09 +0200 Subject: [PATCH 0433/1284] Update react/child-process --- composer.json | 4 +- composer.lock | 150 +++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 125 insertions(+), 29 deletions(-) diff --git a/composer.json b/composer.json index 20469e615d..1ce1713ed0 100644 --- a/composer.json +++ b/composer.json @@ -26,8 +26,8 @@ "ondrejmirtes/better-reflection": "4.3.71", "phpstan/php-8-stubs": "^0.1.23", "phpstan/phpdoc-parser": "^1.2.0", - "react/child-process": "^0.6.1", - "react/event-loop": "^1.1", + "react/child-process": "^0.6.4", + "react/event-loop": "^1.2", "react/http": "^1.1", "react/promise": "^2.8", "react/socket": "^1.3", diff --git a/composer.lock b/composer.lock index 7cdc344932..d7a83e1adc 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a56f93f1b1de4b42ec862f98080ebc7a", + "content-hash": "9adfaa28cae0183fb1e2bc8ca56a2bdf", "packages": [ { "name": "clue/block-react", @@ -2452,28 +2452,28 @@ }, { "name": "react/child-process", - "version": "v0.6.1", + "version": "v0.6.4", "source": { "type": "git", "url": "https://github.com/reactphp/child-process.git", - "reference": "6895afa583d51dc10a4b9e93cd3bce17b3b77ac3" + "reference": "a778f3fb828d68caf8a9ab6567fd8342a86f12fe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/child-process/zipball/6895afa583d51dc10a4b9e93cd3bce17b3b77ac3", - "reference": "6895afa583d51dc10a4b9e93cd3bce17b3b77ac3", + "url": "https://api.github.com/repos/reactphp/child-process/zipball/a778f3fb828d68caf8a9ab6567fd8342a86f12fe", + "reference": "a778f3fb828d68caf8a9ab6567fd8342a86f12fe", "shasum": "" }, "require": { "evenement/evenement": "^3.0 || ^2.0 || ^1.0", "php": ">=5.3.0", - "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5", - "react/stream": "^1.0 || ^0.7.6" + "react/event-loop": "^1.2", + "react/stream": "^1.2" }, "require-dev": { - "phpunit/phpunit": "^7.0 || ^6.4 || ^5.7 || ^4.8.35", - "react/socket": "^1.0", - "sebastian/environment": "^3.0 || ^2.0 || ^1.0" + "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35", + "react/socket": "^1.8", + "sebastian/environment": "^5.0 || ^3.0 || ^2.0 || ^1.0" }, "type": "library", "autoload": { @@ -2485,6 +2485,28 @@ "license": [ "MIT" ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], "description": "Event-driven library for executing child processes with ReactPHP.", "keywords": [ "event-driven", @@ -2493,9 +2515,19 @@ ], "support": { "issues": "https://github.com/reactphp/child-process/issues", - "source": "https://github.com/reactphp/child-process/tree/v0.6.1" + "source": "https://github.com/reactphp/child-process/tree/v0.6.4" }, - "time": "2019-02-15T13:48:16+00:00" + "funding": [ + { + "url": "https://github.com/WyriHaximus", + "type": "github" + }, + { + "url": "https://github.com/clue", + "type": "github" + } + ], + "time": "2021-10-12T10:37:07+00:00" }, { "name": "react/dns", @@ -2579,23 +2611,23 @@ }, { "name": "react/event-loop", - "version": "v1.1.1", + "version": "v1.2.0", "source": { "type": "git", "url": "https://github.com/reactphp/event-loop.git", - "reference": "6d24de090cd59cfc830263cfba965be77b563c13" + "reference": "be6dee480fc4692cec0504e65eb486e3be1aa6f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/event-loop/zipball/6d24de090cd59cfc830263cfba965be77b563c13", - "reference": "6d24de090cd59cfc830263cfba965be77b563c13", + "url": "https://api.github.com/repos/reactphp/event-loop/zipball/be6dee480fc4692cec0504e65eb486e3be1aa6f2", + "reference": "be6dee480fc4692cec0504e65eb486e3be1aa6f2", "shasum": "" }, "require": { "php": ">=5.3.0" }, "require-dev": { - "phpunit/phpunit": "^7.0 || ^6.4 || ^5.7 || ^4.8.35" + "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35" }, "suggest": { "ext-event": "~1.0 for ExtEventLoop", @@ -2612,6 +2644,28 @@ "license": [ "MIT" ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", "keywords": [ "asynchronous", @@ -2619,9 +2673,19 @@ ], "support": { "issues": "https://github.com/reactphp/event-loop/issues", - "source": "https://github.com/reactphp/event-loop/tree/v1.1.1" + "source": "https://github.com/reactphp/event-loop/tree/v1.2.0" }, - "time": "2020-01-01T18:39:52+00:00" + "funding": [ + { + "url": "https://github.com/WyriHaximus", + "type": "github" + }, + { + "url": "https://github.com/clue", + "type": "github" + } + ], + "time": "2021-07-11T12:31:24+00:00" }, { "name": "react/http", @@ -2970,26 +3034,26 @@ }, { "name": "react/stream", - "version": "v1.1.1", + "version": "v1.2.0", "source": { "type": "git", "url": "https://github.com/reactphp/stream.git", - "reference": "7c02b510ee3f582c810aeccd3a197b9c2f52ff1a" + "reference": "7a423506ee1903e89f1e08ec5f0ed430ff784ae9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/stream/zipball/7c02b510ee3f582c810aeccd3a197b9c2f52ff1a", - "reference": "7c02b510ee3f582c810aeccd3a197b9c2f52ff1a", + "url": "https://api.github.com/repos/reactphp/stream/zipball/7a423506ee1903e89f1e08ec5f0ed430ff784ae9", + "reference": "7a423506ee1903e89f1e08ec5f0ed430ff784ae9", "shasum": "" }, "require": { "evenement/evenement": "^3.0 || ^2.0 || ^1.0", "php": ">=5.3.8", - "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5" + "react/event-loop": "^1.2" }, "require-dev": { "clue/stream-filter": "~1.2", - "phpunit/phpunit": "^7.0 || ^6.4 || ^5.7 || ^4.8.35" + "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35" }, "type": "library", "autoload": { @@ -3001,6 +3065,28 @@ "license": [ "MIT" ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", "keywords": [ "event-driven", @@ -3014,9 +3100,19 @@ ], "support": { "issues": "https://github.com/reactphp/stream/issues", - "source": "https://github.com/reactphp/stream/tree/v1.1.1" + "source": "https://github.com/reactphp/stream/tree/v1.2.0" }, - "time": "2020-05-04T10:17:57+00:00" + "funding": [ + { + "url": "https://github.com/WyriHaximus", + "type": "github" + }, + { + "url": "https://github.com/clue", + "type": "github" + } + ], + "time": "2021-07-11T12:37:55+00:00" }, { "name": "ringcentral/psr7", From 92d54224551018a51f01ae1b5b00aab93f653c3e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 12 Oct 2021 13:26:57 +0200 Subject: [PATCH 0434/1284] Type description test to ensure interoperability with PHPDoc types --- tests/PHPStan/PhpDoc/TypeDescriptionTest.php | 40 ++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 tests/PHPStan/PhpDoc/TypeDescriptionTest.php diff --git a/tests/PHPStan/PhpDoc/TypeDescriptionTest.php b/tests/PHPStan/PhpDoc/TypeDescriptionTest.php new file mode 100644 index 0000000000..9bf88e8aed --- /dev/null +++ b/tests/PHPStan/PhpDoc/TypeDescriptionTest.php @@ -0,0 +1,40 @@ +getByType(TypeStringResolver::class); + $type = $typeStringResolver->resolve($description); + $this->assertTrue($expectedType->equals($type), sprintf('Parsing %s did not result in %s, but in %s', $description, $expectedType->describe(VerbosityLevel::value()), $type->describe(VerbosityLevel::value()))); + + $newDescription = $type->describe(VerbosityLevel::value()); + $newType = $typeStringResolver->resolve($newDescription); + $this->assertTrue($type->equals($newType), sprintf('Parsing %s again did not result in %s, but in %s', $newDescription, $type->describe(VerbosityLevel::value()), $newType->describe(VerbosityLevel::value()))); + } + +} From 47b5293248d3bfd98d75b06575eb79fcbed0709a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 12 Oct 2021 13:32:11 +0200 Subject: [PATCH 0435/1284] More tests --- tests/PHPStan/PhpDoc/TypeDescriptionTest.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/PHPStan/PhpDoc/TypeDescriptionTest.php b/tests/PHPStan/PhpDoc/TypeDescriptionTest.php index 9bf88e8aed..5aef8fe030 100644 --- a/tests/PHPStan/PhpDoc/TypeDescriptionTest.php +++ b/tests/PHPStan/PhpDoc/TypeDescriptionTest.php @@ -26,7 +26,7 @@ public function dataTest(): iterable /** * @dataProvider dataTest */ - public function testDescriptionToType(string $description, Type $expectedType): void + public function testParsingDesiredTypeDescription(string $description, Type $expectedType): void { $typeStringResolver = self::getContainer()->getByType(TypeStringResolver::class); $type = $typeStringResolver->resolve($description); @@ -37,4 +37,16 @@ public function testDescriptionToType(string $description, Type $expectedType): $this->assertTrue($type->equals($newType), sprintf('Parsing %s again did not result in %s, but in %s', $newDescription, $type->describe(VerbosityLevel::value()), $newType->describe(VerbosityLevel::value()))); } + /** + * @dataProvider dataTest + */ + public function testDesiredTypeDescription(string $description, Type $expectedType): void + { + $this->assertSame($description, $expectedType->describe(VerbosityLevel::value())); + + $typeStringResolver = self::getContainer()->getByType(TypeStringResolver::class); + $type = $typeStringResolver->resolve($description); + $this->assertSame($description, $type->describe(VerbosityLevel::value())); + } + } From f7d232a666151bd3cd6417e103b358b8b13afa69 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 12 Oct 2021 14:11:29 +0200 Subject: [PATCH 0436/1284] Cannot create IntersectionType with less than two types --- src/Type/IntersectionType.php | 10 ++++++++++ tests/PHPStan/Type/CallableTypeTest.php | 10 +++------- tests/PHPStan/Type/IntersectionTypeTest.php | 20 -------------------- tests/PHPStan/Type/TypeCombinatorTest.php | 4 ++-- 4 files changed, 15 insertions(+), 29 deletions(-) diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index 8003be5471..b21073da04 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -33,6 +33,16 @@ class IntersectionType implements CompoundType */ public function __construct(array $types) { + if (count($types) < 2) { + throw new \PHPStan\ShouldNotHappenException(sprintf( + 'Cannot create %s with: %s', + self::class, + implode(', ', array_map(static function (Type $type): string { + return $type->describe(VerbosityLevel::value()); + }, $types)) + )); + } + $this->types = UnionTypeHelper::sortTypes($types); } diff --git a/tests/PHPStan/Type/CallableTypeTest.php b/tests/PHPStan/Type/CallableTypeTest.php index 9695e032ee..c887c80101 100644 --- a/tests/PHPStan/Type/CallableTypeTest.php +++ b/tests/PHPStan/Type/CallableTypeTest.php @@ -5,6 +5,7 @@ use PHPStan\Reflection\Native\NativeParameterReflection; use PHPStan\Reflection\PassedByReference; use PHPStan\TrinaryLogic; +use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Accessory\HasMethodType; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantIntegerType; @@ -99,17 +100,12 @@ public function dataIsSubTypeOf(): array ], [ new CallableType(), - new IntersectionType([new CallableType()]), - TrinaryLogic::createYes(), - ], - [ - new CallableType(), - new IntersectionType([new StringType()]), + new IntersectionType([new StringType(), new AccessoryNonEmptyStringType()]), TrinaryLogic::createMaybe(), ], [ new CallableType(), - new IntersectionType([new IntegerType()]), + new IntegerType(), TrinaryLogic::createNo(), ], [ diff --git a/tests/PHPStan/Type/IntersectionTypeTest.php b/tests/PHPStan/Type/IntersectionTypeTest.php index 03434722b1..2310f3585d 100644 --- a/tests/PHPStan/Type/IntersectionTypeTest.php +++ b/tests/PHPStan/Type/IntersectionTypeTest.php @@ -147,16 +147,6 @@ public function dataIsSuperTypeOf(): \Iterator TrinaryLogic::createNo(), ]; - $intersectionTypeB = new IntersectionType([ - new IntegerType(), - ]); - - yield [ - $intersectionTypeB, - $intersectionTypeB, - TrinaryLogic::createYes(), - ]; - yield [ new IntersectionType([ new ArrayType(new MixedType(), new MixedType()), @@ -335,16 +325,6 @@ public function dataIsSubTypeOf(): \Iterator TrinaryLogic::createNo(), ]; - $intersectionTypeB = new IntersectionType([ - new IntegerType(), - ]); - - yield [ - $intersectionTypeB, - $intersectionTypeB, - TrinaryLogic::createYes(), - ]; - $intersectionTypeC = new IntersectionType([ new StringType(), new CallableType(), diff --git a/tests/PHPStan/Type/TypeCombinatorTest.php b/tests/PHPStan/Type/TypeCombinatorTest.php index e9f940aba7..c418c4148c 100644 --- a/tests/PHPStan/Type/TypeCombinatorTest.php +++ b/tests/PHPStan/Type/TypeCombinatorTest.php @@ -1825,7 +1825,7 @@ public function dataUnion(): array [ [ new UnionType([ - new IntersectionType([new ArrayType(new MixedType(), new MixedType())]), + new ArrayType(new MixedType(), new MixedType()), IntegerRangeType::fromInterval(null, -1), IntegerRangeType::fromInterval(1, null), ]), @@ -1842,7 +1842,7 @@ public function dataUnion(): array [ [ new UnionType([ - new IntersectionType([new ArrayType(new MixedType(), new MixedType())]), + new ArrayType(new MixedType(), new MixedType()), new CallableType(), ]), TemplateTypeFactory::create( From 03341cc6bf010faf1e99f1dbddf5cea66d56e3cf Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 12 Oct 2021 13:36:44 +0200 Subject: [PATCH 0437/1284] Nicer type descriptions usable in PHPDocs --- build/baseline-8.0.neon | 2 +- phpstan-baseline.neon | 5 + .../Accessory/AccessoryNumericStringType.php | 2 +- src/Type/Accessory/NonEmptyArrayType.php | 2 +- src/Type/IntersectionType.php | 94 ++++++++++++------- src/Type/VerbosityLevel.php | 12 ++- .../Analyser/LegacyNodeScopeResolverTest.php | 92 +++++++++--------- tests/PHPStan/Analyser/TypeSpecifierTest.php | 18 ++-- tests/PHPStan/Analyser/data/array-map.php | 4 +- .../Analyser/data/array_map_multiple.php | 10 +- .../Analyser/data/assign-nested-arrays.php | 4 +- .../Analyser/data/bcmath-dynamic-return.php | 86 ++++++++--------- tests/PHPStan/Analyser/data/bug-1861.php | 4 +- tests/PHPStan/Analyser/data/bug-2112.php | 4 +- tests/PHPStan/Analyser/data/bug-2142.php | 18 ++-- tests/PHPStan/Analyser/data/bug-2899.php | 2 +- tests/PHPStan/Analyser/data/bug-3133.php | 10 +- tests/PHPStan/Analyser/data/bug-3266.php | 2 +- tests/PHPStan/Analyser/data/bug-3915.php | 2 +- tests/PHPStan/Analyser/data/bug-3961-php8.php | 10 +- tests/PHPStan/Analyser/data/bug-3961.php | 10 +- tests/PHPStan/Analyser/data/bug-4016.php | 4 +- tests/PHPStan/Analyser/data/bug-4207.php | 2 +- tests/PHPStan/Analyser/data/bug-4398.php | 8 +- tests/PHPStan/Analyser/data/bug-4499.php | 2 +- tests/PHPStan/Analyser/data/bug-4558.php | 2 +- tests/PHPStan/Analyser/data/bug-4587.php | 4 +- tests/PHPStan/Analyser/data/bug-4650.php | 2 +- tests/PHPStan/Analyser/data/bug-4711.php | 4 +- tests/PHPStan/Analyser/data/bug-5017.php | 4 +- tests/PHPStan/Analyser/data/bug-5219.php | 2 +- .../Analyser/data/cast-to-numeric-string.php | 46 ++++----- .../data/conditional-non-empty-array.php | 2 +- tests/PHPStan/Analyser/data/generics.php | 4 +- tests/PHPStan/Analyser/data/list-type.php | 12 +-- tests/PHPStan/Analyser/data/native-types.php | 8 +- .../data/non-empty-array-key-type.php | 2 +- .../PHPStan/Analyser/data/non-empty-array.php | 26 ++--- .../Analyser/data/non-empty-string.php | 8 +- tests/PHPStan/Analyser/data/number_format.php | 6 +- .../data/phpdoc-pseudotype-global.php | 2 +- tests/PHPStan/Analyser/data/sizeof.php | 8 +- tests/PHPStan/Analyser/data/strval.php | 6 +- tests/PHPStan/Levels/data/acceptTypes-5.json | 10 +- tests/PHPStan/PhpDoc/TypeDescriptionTest.php | 12 +++ ...rictComparisonOfDifferentTypesRuleTest.php | 8 +- .../PHPStan/Rules/Debug/DumpTypeRuleTest.php | 8 +- .../PHPStan/Rules/Debug/data/file-asserts.php | 2 +- .../Rules/Methods/CallMethodsRuleTest.php | 8 +- .../PHPStan/Rules/Variables/IssetRuleTest.php | 2 +- tests/PHPStan/Type/TypeCombinatorTest.php | 8 +- tests/PHPStan/Type/UnionTypeTest.php | 2 +- 52 files changed, 333 insertions(+), 284 deletions(-) diff --git a/build/baseline-8.0.neon b/build/baseline-8.0.neon index b9be86042c..7d6f91d1c8 100644 --- a/build/baseline-8.0.neon +++ b/build/baseline-8.0.neon @@ -11,7 +11,7 @@ parameters: path: ../src/Type/Php/MbFunctionsReturnTypeExtension.php - - message: "#^Strict comparison using \\=\\=\\= between array&nonEmpty and false will always evaluate to false\\.$#" + message: "#^Strict comparison using \\=\\=\\= between non-empty-array and false will always evaluate to false\\.$#" count: 1 path: ../src/Type/Php/StrSplitFunctionReturnTypeExtension.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 68a7387aaf..8078e4d048 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -259,6 +259,11 @@ parameters: count: 1 path: src/Testing/PHPStanTestCase.php + - + message: "#^Call to function in_array\\(\\) with arguments 'array', array\\ and true will always evaluate to true\\.$#" + count: 1 + path: src/Type/IntersectionType.php + - message: """ diff --git a/src/Type/Accessory/AccessoryNumericStringType.php b/src/Type/Accessory/AccessoryNumericStringType.php index 3463615c91..62db966318 100644 --- a/src/Type/Accessory/AccessoryNumericStringType.php +++ b/src/Type/Accessory/AccessoryNumericStringType.php @@ -84,7 +84,7 @@ public function equals(Type $type): bool public function describe(\PHPStan\Type\VerbosityLevel $level): string { - return 'numeric'; + return 'numeric-string'; } public function isOffsetAccessible(): TrinaryLogic diff --git a/src/Type/Accessory/NonEmptyArrayType.php b/src/Type/Accessory/NonEmptyArrayType.php index 0e255f70f9..4043debcf0 100644 --- a/src/Type/Accessory/NonEmptyArrayType.php +++ b/src/Type/Accessory/NonEmptyArrayType.php @@ -83,7 +83,7 @@ public function equals(Type $type): bool public function describe(\PHPStan\Type\VerbosityLevel $level): string { - return 'nonEmpty'; + return 'non-empty-array'; } public function isOffsetAccessible(): TrinaryLogic diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index b21073da04..2dd12d90fc 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -149,45 +149,75 @@ function () use ($level): string { return implode('&', $typeNames); }, function () use ($level): string { - $typeNames = []; - $accessoryTypes = []; - foreach ($this->types as $type) { - if ($type instanceof AccessoryNonEmptyStringType || $type instanceof AccessoryLiteralStringType) { - $accessoryTypes[] = $type; - } - if ($type instanceof AccessoryType && !$type instanceof AccessoryNumericStringType && !$type instanceof NonEmptyArrayType && !$type instanceof AccessoryNonEmptyStringType) { - continue; - } - $typeNames[] = $type->describe($level); - } - - if (count($accessoryTypes) > 0) { - return implode('&', array_map(static function (Type $type) use ($level): string { - return $type->describe($level); - }, $accessoryTypes)); - } - - return implode('&', $typeNames); + return $this->describeItself($level, true); }, function () use ($level): string { - $typeNames = []; - $accessoryTypes = []; - foreach ($this->types as $type) { - if ($type instanceof AccessoryNonEmptyStringType || $type instanceof AccessoryLiteralStringType) { - $accessoryTypes[] = $type; + return $this->describeItself($level, false); + } + ); + } + + private function describeItself(VerbosityLevel $level, bool $skipAccessoryTypes): string + { + $typesToDescribe = []; + $skipTypeNames = []; + foreach ($this->types as $type) { + if ($type instanceof AccessoryNonEmptyStringType || $type instanceof AccessoryLiteralStringType || $type instanceof AccessoryNumericStringType) { + $typesToDescribe[] = $type; + $skipTypeNames[] = 'string'; + continue; + } + if ($type instanceof NonEmptyArrayType) { + $typesToDescribe[] = $type; + $skipTypeNames[] = 'array'; + continue; + } + + if ($skipAccessoryTypes) { + continue; + } + + if (!$type instanceof AccessoryType) { + continue; + } + + $typesToDescribe[] = $type; + } + + $describedTypes = []; + foreach ($this->types as $type) { + if ($type instanceof AccessoryType) { + continue; + } + $typeDescription = $type->describe($level); + if ( + substr($typeDescription, 0, strlen('array<')) === 'array<' + && in_array('array', $skipTypeNames, true) + ) { + foreach ($typesToDescribe as $j => $typeToDescribe) { + if (!$typeToDescribe instanceof NonEmptyArrayType) { + continue; } - $typeNames[] = $type->describe($level); - } - if (count($accessoryTypes) > 0) { - return implode('&', array_map(static function (Type $type) use ($level): string { - return $type->describe($level); - }, $accessoryTypes)); + unset($typesToDescribe[$j]); } - return implode('&', $typeNames); + $describedTypes[] = 'non-empty-array<' . substr($typeDescription, strlen('array<')); + continue; } - ); + + if (in_array($typeDescription, $skipTypeNames, true)) { + continue; + } + + $describedTypes[] = $type->describe($level); + } + + foreach ($typesToDescribe as $typeToDescribe) { + $describedTypes[] = $typeToDescribe->describe($level); + } + + return implode('&', $describedTypes); } public function canAccessProperties(): TrinaryLogic diff --git a/src/Type/VerbosityLevel.php b/src/Type/VerbosityLevel.php index 8ae7d0060d..5b340bdbc1 100644 --- a/src/Type/VerbosityLevel.php +++ b/src/Type/VerbosityLevel.php @@ -69,11 +69,13 @@ public static function getRecommendedLevelByType(Type $acceptingType, ?Type $acc $moreVerbose = true; return $type; } - if ($type instanceof AccessoryNumericStringType || $type instanceof AccessoryNonEmptyStringType || $type instanceof AccessoryLiteralStringType) { - $moreVerbose = true; - return $type; - } - if ($type instanceof NonEmptyArrayType) { + if ( + // synced with IntersectionType::describe() + $type instanceof AccessoryNonEmptyStringType + || $type instanceof AccessoryLiteralStringType + || $type instanceof AccessoryNumericStringType + || $type instanceof NonEmptyArrayType + ) { $moreVerbose = true; return $type; } diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 7e1f8e5af3..8c58d5a4db 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -2982,11 +2982,11 @@ public function dataBinaryOperations(): array '"$std bar"', ], [ - 'array<\'foo\'|int|stdClass>&nonEmpty', + 'non-empty-array<\'foo\'|int|stdClass>', '$arrToPush', ], [ - 'array<\'foo\'|int|stdClass>&nonEmpty', + 'non-empty-array<\'foo\'|int|stdClass>', '$arrToPush2', ], [ @@ -2994,7 +2994,7 @@ public function dataBinaryOperations(): array '$arrToUnshift', ], [ - 'array<\'lorem\'|int|stdClass>&nonEmpty', + 'non-empty-array<\'lorem\'|int|stdClass>', '$arrToUnshift2', ], [ @@ -3174,7 +3174,7 @@ public function dataBinaryOperations(): array '$shiftedNonEmptyArray', ], [ - 'array&nonEmpty', + 'non-empty-array', '$unshiftedArray', ], [ @@ -3182,7 +3182,7 @@ public function dataBinaryOperations(): array '$poppedNonEmptyArray', ], [ - 'array&nonEmpty', + 'non-empty-array', '$pushedArray', ], [ @@ -4453,7 +4453,7 @@ public function dataForeachArrayType(): array ], [ __DIR__ . '/data/foreach/foreach-with-specified-key-type.php', - 'array&nonEmpty', + 'non-empty-array', '$list', ], [ @@ -4779,7 +4779,7 @@ public function dataArrayFunctions(): array '$filledIntegersWithKeys', ], [ - 'array&nonEmpty', + 'non-empty-array', '$filledNonEmptyArray', ], [ @@ -4795,7 +4795,7 @@ public function dataArrayFunctions(): array '$filledByMaybeNegativeRange', ], [ - 'array&nonEmpty', + 'non-empty-array', '$filledByPositiveRange', ], [ @@ -4823,7 +4823,7 @@ public function dataArrayFunctions(): array 'array_values($generalStringKeys)', ], [ - 'array&nonEmpty', + 'non-empty-array', 'array_merge($stringOrIntegerKeys)', ], [ @@ -4831,23 +4831,23 @@ public function dataArrayFunctions(): array 'array_merge($generalStringKeys, $generalDateTimeValues)', ], [ - 'array&nonEmpty', + 'non-empty-array', 'array_merge($generalStringKeys, $stringOrIntegerKeys)', ], [ - 'array&nonEmpty', + 'non-empty-array', 'array_merge($stringOrIntegerKeys, $generalStringKeys)', ], [ - 'array&nonEmpty', + 'non-empty-array', 'array_merge($stringKeys, $stringOrIntegerKeys)', ], [ - 'array&nonEmpty', + 'non-empty-array', 'array_merge($stringOrIntegerKeys, $stringKeys)', ], [ - 'array&nonEmpty', + 'non-empty-array', 'array_merge(array("color" => "red", 2, 4), array("a", "b", "color" => "green", "shape" => "trapezoid", 4))', ], [ @@ -4863,7 +4863,7 @@ public function dataArrayFunctions(): array 'array_fill(5, 6, \'banana\')', ], [ - 'array&nonEmpty', + 'non-empty-array', 'array_fill(0, 101, \'apple\')', ], [ @@ -4871,7 +4871,7 @@ public function dataArrayFunctions(): array 'array_fill(-2, 4, \'pear\')', ], [ - 'array&nonEmpty', + 'non-empty-array', 'array_fill($integer, 2, new \stdClass())', ], [ @@ -5550,7 +5550,7 @@ public function dataFunctions(): array '$gettimeofdayBenevolent', ], [ - PHP_VERSION_ID < 80000 ? '(array&nonEmpty)|false' : 'array&nonEmpty', + PHP_VERSION_ID < 80000 ? '(non-empty-array)|false' : 'non-empty-array', '$strSplitConstantStringWithoutDefinedParameters', ], [ @@ -5558,7 +5558,7 @@ public function dataFunctions(): array '$strSplitConstantStringWithoutDefinedSplitLength', ], [ - 'array&nonEmpty', + 'non-empty-array', '$strSplitStringWithoutDefinedSplitLength', ], [ @@ -5574,15 +5574,15 @@ public function dataFunctions(): array '$strSplitConstantStringWithFailureSplitLength', ], [ - PHP_VERSION_ID < 80000 ? '(array&nonEmpty)|false' : 'array&nonEmpty', + PHP_VERSION_ID < 80000 ? '(non-empty-array)|false' : 'non-empty-array', '$strSplitConstantStringWithInvalidSplitLengthType', ], [ - 'array&nonEmpty', + 'non-empty-array', '$strSplitConstantStringWithVariableStringAndConstantSplitLength', ], [ - PHP_VERSION_ID < 80000 ? '(array&nonEmpty)|false' : 'array&nonEmpty', + PHP_VERSION_ID < 80000 ? '(non-empty-array)|false' : 'non-empty-array', '$strSplitConstantStringWithVariableStringAndVariableSplitLength', ], // parse_url @@ -5887,7 +5887,7 @@ public function dataRangeFunction(): array 'range(3, -1)', ], [ - 'array&nonEmpty', + 'non-empty-array', 'range(0, 50)', ], ]; @@ -6141,7 +6141,7 @@ public function dataIterable(): array '$unionBar', ], [ - 'array&nonEmpty', + 'non-empty-array', '$mixedUnionIterableType', ], [ @@ -7149,7 +7149,7 @@ public function dataForeachLoopVariables(): array "'end'", ], [ - 'array&nonEmpty', + 'non-empty-array', '$integers', "'end'", ], @@ -7164,7 +7164,7 @@ public function dataForeachLoopVariables(): array "'begin'", ], [ - 'array&nonEmpty', + 'non-empty-array', '$this->property', "'end'", ], @@ -7482,7 +7482,7 @@ public function dataExplode(): array { return [ [ - 'array&nonEmpty', + 'non-empty-array', '$sureArray', ], [ @@ -7490,15 +7490,15 @@ public function dataExplode(): array '$sureFalse', ], [ - PHP_VERSION_ID < 80000 ? '(array&nonEmpty)|false' : 'array&nonEmpty', + PHP_VERSION_ID < 80000 ? '(non-empty-array)|false' : 'non-empty-array', '$arrayOrFalse', ], [ - PHP_VERSION_ID < 80000 ? '(array&nonEmpty)|false' : 'array&nonEmpty', + PHP_VERSION_ID < 80000 ? '(non-empty-array)|false' : 'non-empty-array', '$anotherArrayOrFalse', ], [ - PHP_VERSION_ID < 80000 ? '((array&nonEmpty)|false)' : 'array&nonEmpty', + PHP_VERSION_ID < 80000 ? '(non-empty-array|false)' : 'non-empty-array', '$benevolentArrayOrFalse', ], ]; @@ -9097,7 +9097,7 @@ public function dataPhp74Functions(): array { return [ [ - PHP_VERSION_ID < 80000 ? '(array&nonEmpty)|false' : 'array&nonEmpty', + PHP_VERSION_ID < 80000 ? 'non-empty-array|false' : 'non-empty-array', '$mbStrSplitConstantStringWithoutDefinedParameters', ], [ @@ -9105,7 +9105,7 @@ public function dataPhp74Functions(): array '$mbStrSplitConstantStringWithoutDefinedSplitLength', ], [ - 'array&nonEmpty', + 'non-empty-array', '$mbStrSplitStringWithoutDefinedSplitLength', ], [ @@ -9121,15 +9121,15 @@ public function dataPhp74Functions(): array '$mbStrSplitConstantStringWithFailureSplitLength', ], [ - PHP_VERSION_ID < 80000 ? '(array&nonEmpty)|false' : 'array&nonEmpty', + PHP_VERSION_ID < 80000 ? 'non-empty-array|false' : 'non-empty-array', '$mbStrSplitConstantStringWithInvalidSplitLengthType', ], [ - 'array&nonEmpty', + 'non-empty-array', '$mbStrSplitConstantStringWithVariableStringAndConstantSplitLength', ], [ - PHP_VERSION_ID < 80000 ? '(array&nonEmpty)|false' : 'array&nonEmpty', + PHP_VERSION_ID < 80000 ? 'non-empty-|false' : 'non-empty-array', '$mbStrSplitConstantStringWithVariableStringAndVariableSplitLength', ], [ @@ -9141,7 +9141,7 @@ public function dataPhp74Functions(): array '$mbStrSplitConstantStringWithOneSplitLengthAndInvalidEncoding', ], [ - PHP_VERSION_ID < 80000 ? '(array&nonEmpty)|false' : 'array&nonEmpty', + PHP_VERSION_ID < 80000 ? 'non-empty-array|false' : 'non-empty-array', '$mbStrSplitConstantStringWithOneSplitLengthAndVariableEncoding', ], [ @@ -9153,7 +9153,7 @@ public function dataPhp74Functions(): array '$mbStrSplitConstantStringWithGreaterSplitLengthThanStringLengthAndInvalidEncoding', ], [ - PHP_VERSION_ID < 80000 ? '(array&nonEmpty)|false' : 'array&nonEmpty', + PHP_VERSION_ID < 80000 ? 'non-empty-array|false' : 'non-empty-array', '$mbStrSplitConstantStringWithGreaterSplitLengthThanStringLengthAndVariableEncoding', ], [ @@ -9169,7 +9169,7 @@ public function dataPhp74Functions(): array '$mbStrSplitConstantStringWithFailureSplitLengthAndVariableEncoding', ], [ - PHP_VERSION_ID < 80000 ? '(array&nonEmpty)|false' : 'array&nonEmpty', + PHP_VERSION_ID < 80000 ? 'non-empty-array|false' : 'non-empty-array', '$mbStrSplitConstantStringWithInvalidSplitLengthTypeAndValidEncoding', ], [ @@ -9177,11 +9177,11 @@ public function dataPhp74Functions(): array '$mbStrSplitConstantStringWithInvalidSplitLengthTypeAndInvalidEncoding', ], [ - PHP_VERSION_ID < 80000 ? '(array&nonEmpty)|false' : 'array&nonEmpty', + PHP_VERSION_ID < 80000 ? 'non-empty-array|false' : 'non-empty-array', '$mbStrSplitConstantStringWithInvalidSplitLengthTypeAndVariableEncoding', ], [ - 'array&nonEmpty', + 'non-empty-array', '$mbStrSplitConstantStringWithVariableStringAndConstantSplitLengthAndValidEncoding', ], [ @@ -9189,11 +9189,11 @@ public function dataPhp74Functions(): array '$mbStrSplitConstantStringWithVariableStringAndConstantSplitLengthAndInvalidEncoding', ], [ - PHP_VERSION_ID < 80000 ? '(array&nonEmpty)|false' : 'array&nonEmpty', + PHP_VERSION_ID < 80000 ? 'non-empty-array|false' : 'non-empty-array', '$mbStrSplitConstantStringWithVariableStringAndConstantSplitLengthAndVariableEncoding', ], [ - PHP_VERSION_ID < 80000 ? '(array&nonEmpty)|false' : 'array&nonEmpty', + PHP_VERSION_ID < 80000 ? 'non-empty-array|false' : 'non-empty-array', '$mbStrSplitConstantStringWithVariableStringAndVariableSplitLengthAndValidEncoding', ], [ @@ -9201,7 +9201,7 @@ public function dataPhp74Functions(): array '$mbStrSplitConstantStringWithVariableStringAndVariableSplitLengthAndInvalidEncoding', ], [ - PHP_VERSION_ID < 80000 ? '(array&nonEmpty)|false' : 'array&nonEmpty', + PHP_VERSION_ID < 80000 ? 'non-empty-array|false' : 'non-empty-array', '$mbStrSplitConstantStringWithVariableStringAndVariableSplitLengthAndVariableEncoding', ], ]; @@ -9640,11 +9640,11 @@ public function dataArraySpread(): array { return [ [ - 'array&nonEmpty', + 'non-empty-array', '$integersOne', ], [ - 'array&nonEmpty', + 'non-empty-array', '$integersTwo', ], [ @@ -9652,11 +9652,11 @@ public function dataArraySpread(): array '$integersThree', ], [ - 'array&nonEmpty', + 'non-empty-array', '$integersFour', ], [ - 'array&nonEmpty', + 'non-empty-array', '$integersFive', ], [ diff --git a/tests/PHPStan/Analyser/TypeSpecifierTest.php b/tests/PHPStan/Analyser/TypeSpecifierTest.php index b6b46a0ba3..fce28bca4f 100644 --- a/tests/PHPStan/Analyser/TypeSpecifierTest.php +++ b/tests/PHPStan/Analyser/TypeSpecifierTest.php @@ -90,7 +90,7 @@ public function dataCondition(): array ], [ $this->createFunctionCall('is_numeric'), - ['$foo' => 'float|int|(string&numeric)'], + ['$foo' => 'float|int|numeric-string'], ['$foo' => '~float|int'], ], [ @@ -567,10 +567,10 @@ public function dataCondition(): array new Arg(new Variable('array')), ]), [ - '$array' => 'nonEmpty', + '$array' => 'non-empty-array', ], [ - '$array' => '~nonEmpty', + '$array' => '~non-empty-array', ], ], [ @@ -578,10 +578,10 @@ public function dataCondition(): array new Arg(new Variable('array')), ])), [ - '$array' => '~nonEmpty', + '$array' => '~non-empty-array', ], [ - '$array' => 'nonEmpty', + '$array' => 'non-empty-array', ], ], [ @@ -589,10 +589,10 @@ public function dataCondition(): array new Arg(new Variable('array')), ]), [ - '$array' => 'nonEmpty', + '$array' => 'non-empty-array', ], [ - '$array' => '~nonEmpty', + '$array' => '~non-empty-array', ], ], [ @@ -600,10 +600,10 @@ public function dataCondition(): array new Arg(new Variable('array')), ])), [ - '$array' => '~nonEmpty', + '$array' => '~non-empty-array', ], [ - '$array' => 'nonEmpty', + '$array' => 'non-empty-array', ], ], [ diff --git a/tests/PHPStan/Analyser/data/array-map.php b/tests/PHPStan/Analyser/data/array-map.php index 97767fb116..7845d56c8a 100644 --- a/tests/PHPStan/Analyser/data/array-map.php +++ b/tests/PHPStan/Analyser/data/array-map.php @@ -30,7 +30,7 @@ static function(string $string): string { $array ); - assertType('array&nonEmpty', $mapped); + assertType('non-empty-array', $mapped); } /** @@ -58,5 +58,5 @@ static function(string $string): string { $array ); - assertType('array&nonEmpty', $mapped); + assertType('non-empty-array', $mapped); } diff --git a/tests/PHPStan/Analyser/data/array_map_multiple.php b/tests/PHPStan/Analyser/data/array_map_multiple.php index a384a33ac5..381bccec47 100644 --- a/tests/PHPStan/Analyser/data/array_map_multiple.php +++ b/tests/PHPStan/Analyser/data/array_map_multiple.php @@ -15,7 +15,7 @@ public function doFoo(int $i, string $s): void return rand(0, 1) ? $a : $b; }, ['foo' => $i], ['bar' => $s]); - assertType('array&nonEmpty', $result); + assertType('non-empty-array', $result); } /** @@ -26,11 +26,11 @@ public function arrayMapNull(array $array, array $other): void { assertType('array()', array_map(null, [])); assertType('array(\'foo\' => true)', array_map(null, ['foo' => true])); - assertType('array&nonEmpty', array_map(null, [1, 2, 3], [4, 5, 6])); + assertType('non-empty-array', array_map(null, [1, 2, 3], [4, 5, 6])); - assertType('array&nonEmpty', array_map(null, $array)); - assertType('array&nonEmpty', array_map(null, $array, $array)); - assertType('array&nonEmpty', array_map(null, $array, $other)); + assertType('non-empty-array', array_map(null, $array)); + assertType('non-empty-array', array_map(null, $array, $array)); + assertType('non-empty-array', array_map(null, $array, $other)); } } diff --git a/tests/PHPStan/Analyser/data/assign-nested-arrays.php b/tests/PHPStan/Analyser/data/assign-nested-arrays.php index 08227f4a8e..8463b19c6e 100644 --- a/tests/PHPStan/Analyser/data/assign-nested-arrays.php +++ b/tests/PHPStan/Analyser/data/assign-nested-arrays.php @@ -14,7 +14,7 @@ public function doFoo(int $i) $array[$i]['bar'] = 1; $array[$i]['baz'] = 2; - assertType('array 1, \'baz\' => 2)>&nonEmpty', $array); + assertType('non-empty-array 1, \'baz\' => 2)>', $array); } public function doBar(int $i, int $j) @@ -27,7 +27,7 @@ public function doBar(int $i, int $j) echo $array[$i][$j]['bar']; echo $array[$i][$j]['baz']; - assertType('array 1, \'baz\' => 2)>&nonEmpty>&nonEmpty', $array); + assertType('non-empty-array 1, \'baz\' => 2)>>', $array); } } diff --git a/tests/PHPStan/Analyser/data/bcmath-dynamic-return.php b/tests/PHPStan/Analyser/data/bcmath-dynamic-return.php index 485709cb35..e7be5836a4 100644 --- a/tests/PHPStan/Analyser/data/bcmath-dynamic-return.php +++ b/tests/PHPStan/Analyser/data/bcmath-dynamic-return.php @@ -21,16 +21,16 @@ \PHPStan\Testing\assertType('null', bcdiv('10', '0')); // Warning: Division by zero \PHPStan\Testing\assertType('null', bcdiv('10', '0.0')); // Warning: Division by zero \PHPStan\Testing\assertType('null', bcdiv('10', 0.0)); // Warning: Division by zero -\PHPStan\Testing\assertType('string&numeric', bcdiv('10', '1')); -\PHPStan\Testing\assertType('string&numeric', bcdiv('10', '-1')); -\PHPStan\Testing\assertType('string&numeric', bcdiv('10', '2', 0)); -\PHPStan\Testing\assertType('string&numeric', bcdiv('10', '2', 1)); -\PHPStan\Testing\assertType('string&numeric', bcdiv('10', $iNeg)); -\PHPStan\Testing\assertType('string&numeric', bcdiv('10', $iPos)); -\PHPStan\Testing\assertType('string&numeric', bcdiv($iPos, $iPos)); -\PHPStan\Testing\assertType('(string&numeric)|null', bcdiv('10', $mixed)); -\PHPStan\Testing\assertType('string&numeric', bcdiv('10', $iPos, $iPos)); -\PHPStan\Testing\assertType('string&numeric', bcdiv('10', $iUnknown)); +\PHPStan\Testing\assertType('numeric-string', bcdiv('10', '1')); +\PHPStan\Testing\assertType('numeric-string', bcdiv('10', '-1')); +\PHPStan\Testing\assertType('numeric-string', bcdiv('10', '2', 0)); +\PHPStan\Testing\assertType('numeric-string', bcdiv('10', '2', 1)); +\PHPStan\Testing\assertType('numeric-string', bcdiv('10', $iNeg)); +\PHPStan\Testing\assertType('numeric-string', bcdiv('10', $iPos)); +\PHPStan\Testing\assertType('numeric-string', bcdiv($iPos, $iPos)); +\PHPStan\Testing\assertType('numeric-string|null', bcdiv('10', $mixed)); +\PHPStan\Testing\assertType('numeric-string', bcdiv('10', $iPos, $iPos)); +\PHPStan\Testing\assertType('numeric-string', bcdiv('10', $iUnknown)); \PHPStan\Testing\assertType('null', bcdiv('10', $iPos, $nonNumeric)); // Warning: expects parameter 3 to be int, string given in \PHPStan\Testing\assertType('null', bcdiv('10', $nonNumeric)); // Warning: bcmath function argument is not well-formed @@ -39,18 +39,18 @@ \PHPStan\Testing\assertType('null', bcmod('10', '0')); \PHPStan\Testing\assertType('null', bcmod($iPos, '0')); // Warning: Division by zero \PHPStan\Testing\assertType('null', bcmod('10', $nonNumeric)); -\PHPStan\Testing\assertType('string&numeric', bcmod('10', '1')); -\PHPStan\Testing\assertType('string&numeric', bcmod('10', '2', 0)); -\PHPStan\Testing\assertType('string&numeric', bcmod('5.7', '1.3', 1)); -\PHPStan\Testing\assertType('string&numeric', bcmod('10', 2.2)); -\PHPStan\Testing\assertType('string&numeric', bcmod('10', $iUnknown)); -\PHPStan\Testing\assertType('string&numeric', bcmod('10', '-1')); -\PHPStan\Testing\assertType('string&numeric', bcmod($iPos, '-1')); -\PHPStan\Testing\assertType('string&numeric', bcmod('10', $iNeg)); -\PHPStan\Testing\assertType('string&numeric', bcmod('10', $iPos)); -\PHPStan\Testing\assertType('string&numeric', bcmod('10', -$iNeg)); -\PHPStan\Testing\assertType('string&numeric', bcmod('10', -$iPos)); -\PHPStan\Testing\assertType('(string&numeric)|null', bcmod('10', $mixed)); +\PHPStan\Testing\assertType('numeric-string', bcmod('10', '1')); +\PHPStan\Testing\assertType('numeric-string', bcmod('10', '2', 0)); +\PHPStan\Testing\assertType('numeric-string', bcmod('5.7', '1.3', 1)); +\PHPStan\Testing\assertType('numeric-string', bcmod('10', 2.2)); +\PHPStan\Testing\assertType('numeric-string', bcmod('10', $iUnknown)); +\PHPStan\Testing\assertType('numeric-string', bcmod('10', '-1')); +\PHPStan\Testing\assertType('numeric-string', bcmod($iPos, '-1')); +\PHPStan\Testing\assertType('numeric-string', bcmod('10', $iNeg)); +\PHPStan\Testing\assertType('numeric-string', bcmod('10', $iPos)); +\PHPStan\Testing\assertType('numeric-string', bcmod('10', -$iNeg)); +\PHPStan\Testing\assertType('numeric-string', bcmod('10', -$iPos)); +\PHPStan\Testing\assertType('numeric-string|null', bcmod('10', $mixed)); // bcpowmod ( string $base , string $exponent , string $modulus [, int $scale = 0 ] ) : string // Returns the result as a numeric-string, or FALSE if modulus is 0 or exponent is negative. @@ -66,32 +66,32 @@ \PHPStan\Testing\assertType('false', bcpowmod('10', '2', '0')); // modulus is 0 \PHPStan\Testing\assertType('false', bcpowmod('10', 2.3, '0')); // modulus is 0 \PHPStan\Testing\assertType('false', bcpowmod('10', '0', '0')); // modulus is 0 -\PHPStan\Testing\assertType('string&numeric', bcpowmod('10', '0', '-2')); -\PHPStan\Testing\assertType('string&numeric', bcpowmod('10', '2', '2')); -\PHPStan\Testing\assertType('string&numeric', bcpowmod('10', $iUnknown, '2')); -\PHPStan\Testing\assertType('string&numeric', bcpowmod($iPos, '2', '2')); -\PHPStan\Testing\assertType('(string&numeric)|false', bcpowmod('10', $mixed, $mixed)); -\PHPStan\Testing\assertType('string&numeric', bcpowmod('10', '2', '2')); -\PHPStan\Testing\assertType('string&numeric', bcpowmod('10', -$iNeg, '2')); -\PHPStan\Testing\assertType('string&numeric', bcpowmod('10', $nonNumeric, '2')); // Warning: bcmath function argument is not well-formed -\PHPStan\Testing\assertType('(string&numeric)|false', bcpowmod('10', $iUnknown, $iUnknown)); +\PHPStan\Testing\assertType('numeric-string', bcpowmod('10', '0', '-2')); +\PHPStan\Testing\assertType('numeric-string', bcpowmod('10', '2', '2')); +\PHPStan\Testing\assertType('numeric-string', bcpowmod('10', $iUnknown, '2')); +\PHPStan\Testing\assertType('numeric-string', bcpowmod($iPos, '2', '2')); +\PHPStan\Testing\assertType('numeric-string|false', bcpowmod('10', $mixed, $mixed)); +\PHPStan\Testing\assertType('numeric-string', bcpowmod('10', '2', '2')); +\PHPStan\Testing\assertType('numeric-string', bcpowmod('10', -$iNeg, '2')); +\PHPStan\Testing\assertType('numeric-string', bcpowmod('10', $nonNumeric, '2')); // Warning: bcmath function argument is not well-formed +\PHPStan\Testing\assertType('numeric-string|false', bcpowmod('10', $iUnknown, $iUnknown)); // bcsqrt ( string $operand [, int $scale = 0 ] ) : string // Returns the square root as a numeric-string, or NULL if operand is negative. -\PHPStan\Testing\assertType('string&numeric', bcsqrt('10', $iNeg)); -\PHPStan\Testing\assertType('string&numeric', bcsqrt('10', 1)); -\PHPStan\Testing\assertType('string&numeric', bcsqrt('0.00', 1)); -\PHPStan\Testing\assertType('string&numeric', bcsqrt(0.0, 1)); -\PHPStan\Testing\assertType('string&numeric', bcsqrt('0', 1)); -\PHPStan\Testing\assertType('(string&numeric)|null', bcsqrt($iUnknown, $iUnknown)); -\PHPStan\Testing\assertType('string&numeric', bcsqrt('10', $iPos)); +\PHPStan\Testing\assertType('numeric-string', bcsqrt('10', $iNeg)); +\PHPStan\Testing\assertType('numeric-string', bcsqrt('10', 1)); +\PHPStan\Testing\assertType('numeric-string', bcsqrt('0.00', 1)); +\PHPStan\Testing\assertType('numeric-string', bcsqrt(0.0, 1)); +\PHPStan\Testing\assertType('numeric-string', bcsqrt('0', 1)); +\PHPStan\Testing\assertType('numeric-string|null', bcsqrt($iUnknown, $iUnknown)); +\PHPStan\Testing\assertType('numeric-string', bcsqrt('10', $iPos)); \PHPStan\Testing\assertType('null', bcsqrt('-10', 0)); // Warning: Square root of negative number \PHPStan\Testing\assertType('null', bcsqrt($iNeg, 0)); \PHPStan\Testing\assertType('null', bcsqrt('10', $nonNumeric)); // Warning: Second argument must be ?int (Fatal in PHP8) -\PHPStan\Testing\assertType('string&numeric', bcsqrt('10')); -\PHPStan\Testing\assertType('(string&numeric)|null', bcsqrt($iUnknown)); +\PHPStan\Testing\assertType('numeric-string', bcsqrt('10')); +\PHPStan\Testing\assertType('numeric-string|null', bcsqrt($iUnknown)); \PHPStan\Testing\assertType('null', bcsqrt('-10')); // Warning: Square root of negative number -\PHPStan\Testing\assertType('(string&numeric)|null', bcsqrt($nonNumeric, -1)); // Warning: bcmath function argument is not well-formed -\PHPStan\Testing\assertType('(string&numeric)|null', bcsqrt('10', $mixed)); -\PHPStan\Testing\assertType('string&numeric', bcsqrt($iPos)); +\PHPStan\Testing\assertType('numeric-string|null', bcsqrt($nonNumeric, -1)); // Warning: bcmath function argument is not well-formed +\PHPStan\Testing\assertType('numeric-string|null', bcsqrt('10', $mixed)); +\PHPStan\Testing\assertType('numeric-string', bcsqrt($iPos)); diff --git a/tests/PHPStan/Analyser/data/bug-1861.php b/tests/PHPStan/Analyser/data/bug-1861.php index 0f00af5b8a..c7bbcc29f7 100644 --- a/tests/PHPStan/Analyser/data/bug-1861.php +++ b/tests/PHPStan/Analyser/data/bug-1861.php @@ -18,11 +18,11 @@ public function isPath(): void assertType('array()', $this->children); break; case 1: - assertType('array<' . self::class . '>&nonEmpty', $this->children); + assertType('non-empty-array<' . self::class . '>', $this->children); assertType(self::class, reset($this->children)); break; default: - assertType('array<' . self::class . '>&nonEmpty', $this->children); + assertType('non-empty-array<' . self::class . '>', $this->children); break; } } diff --git a/tests/PHPStan/Analyser/data/bug-2112.php b/tests/PHPStan/Analyser/data/bug-2112.php index 0a4a0a5bde..756c061b5e 100644 --- a/tests/PHPStan/Analyser/data/bug-2112.php +++ b/tests/PHPStan/Analyser/data/bug-2112.php @@ -19,7 +19,7 @@ public function doBar(): void $foos[0] = null; assertType('null', $foos[0]); - assertType('array&nonEmpty', $foos); + assertType('non-empty-array', $foos); } /** @return self[] */ @@ -35,7 +35,7 @@ public function doBars(): void $foos[0] = null; assertType('null', $foos[0]); - assertType('array&nonEmpty', $foos); + assertType('non-empty-array', $foos); } } diff --git a/tests/PHPStan/Analyser/data/bug-2142.php b/tests/PHPStan/Analyser/data/bug-2142.php index 9834c2dc28..64e39ecd25 100644 --- a/tests/PHPStan/Analyser/data/bug-2142.php +++ b/tests/PHPStan/Analyser/data/bug-2142.php @@ -10,7 +10,7 @@ class Foo function doFoo1(array $arr): void { if (count($arr) > 0) { - assertType('array&nonEmpty', $arr); + assertType('non-empty-array', $arr); } } @@ -20,7 +20,7 @@ function doFoo1(array $arr): void function doFoo2(array $arr): void { if (count($arr) != 0) { - assertType('array&nonEmpty', $arr); + assertType('non-empty-array', $arr); } } @@ -30,7 +30,7 @@ function doFoo2(array $arr): void function doFoo3(array $arr): void { if (count($arr) == 1) { - assertType('array&nonEmpty', $arr); + assertType('non-empty-array', $arr); } } @@ -40,7 +40,7 @@ function doFoo3(array $arr): void function doFoo4(array $arr): void { if ($arr != []) { - assertType('array&nonEmpty', $arr); + assertType('non-empty-array', $arr); } } @@ -50,7 +50,7 @@ function doFoo4(array $arr): void function doFoo5(array $arr): void { if (sizeof($arr) !== 0) { - assertType('array&nonEmpty', $arr); + assertType('non-empty-array', $arr); } } @@ -60,7 +60,7 @@ function doFoo5(array $arr): void function doFoo6(array $arr): void { if (count($arr) !== 0) { - assertType('array&nonEmpty', $arr); + assertType('non-empty-array', $arr); } } @@ -71,7 +71,7 @@ function doFoo6(array $arr): void function doFoo7(array $arr): void { if (!empty($arr)) { - assertType('array&nonEmpty', $arr); + assertType('non-empty-array', $arr); } } @@ -81,7 +81,7 @@ function doFoo7(array $arr): void function doFoo8(array $arr): void { if (count($arr) === 1) { - assertType('array&nonEmpty', $arr); + assertType('non-empty-array', $arr); } } @@ -92,7 +92,7 @@ function doFoo8(array $arr): void function doFoo9(array $arr): void { if ($arr !== []) { - assertType('array&nonEmpty', $arr); + assertType('non-empty-array', $arr); } } diff --git a/tests/PHPStan/Analyser/data/bug-2899.php b/tests/PHPStan/Analyser/data/bug-2899.php index 2342fb2466..dd8b8115c8 100644 --- a/tests/PHPStan/Analyser/data/bug-2899.php +++ b/tests/PHPStan/Analyser/data/bug-2899.php @@ -9,7 +9,7 @@ class Foo public function doFoo(string $s, $mixed) { - assertType('string&numeric', date('Y')); + assertType('numeric-string', date('Y')); assertType('string', date('Y.m.d')); assertType('string', date($s)); assertType('string', date($mixed)); diff --git a/tests/PHPStan/Analyser/data/bug-3133.php b/tests/PHPStan/Analyser/data/bug-3133.php index ea003461fa..98eb5841f0 100644 --- a/tests/PHPStan/Analyser/data/bug-3133.php +++ b/tests/PHPStan/Analyser/data/bug-3133.php @@ -17,7 +17,7 @@ public function doFoo($arg): void return; } - assertType('string&numeric', $arg); + assertType('numeric-string', $arg); } /** @@ -26,7 +26,7 @@ public function doFoo($arg): void public function doBar($arg): void { if (\is_numeric($arg)) { - assertType('float|int|(string&numeric)', $arg); + assertType('float|int|numeric-string', $arg); } } @@ -39,8 +39,8 @@ public function doBaz( string $numericString ) { - assertType('float|int|(string&numeric)', $numeric); - assertType('string&numeric', $numericString); + assertType('float|int|numeric-string', $numeric); + assertType('numeric-string', $numericString); } /** @@ -52,7 +52,7 @@ public function doLorem( { $a = []; $a[$numericString] = 'foo'; - assertType('array&nonEmpty', $a); + assertType('non-empty-array', $a); } } diff --git a/tests/PHPStan/Analyser/data/bug-3266.php b/tests/PHPStan/Analyser/data/bug-3266.php index 9e1d2a3ebe..bee0c7e6f8 100644 --- a/tests/PHPStan/Analyser/data/bug-3266.php +++ b/tests/PHPStan/Analyser/data/bug-3266.php @@ -21,7 +21,7 @@ public function iteratorToArray($iterator) assertType('TKey of (int|string) (method Bug3266\Foo::iteratorToArray(), argument)', $key); assertType('TValue (method Bug3266\Foo::iteratorToArray(), argument)', $value); $array[$key] = $value; - assertType('array&nonEmpty', $array); + assertType('non-empty-array', $array); } assertType('array', $array); diff --git a/tests/PHPStan/Analyser/data/bug-3915.php b/tests/PHPStan/Analyser/data/bug-3915.php index 9526f4a869..2ba8131fbf 100644 --- a/tests/PHPStan/Analyser/data/bug-3915.php +++ b/tests/PHPStan/Analyser/data/bug-3915.php @@ -13,7 +13,7 @@ public function sayHello(): void foreach ([1] as $row) { $lengths[] = self::getInt(); } - assertType('array&nonEmpty', $lengths); + assertType('non-empty-array', $lengths); } public static function getInt(): int diff --git a/tests/PHPStan/Analyser/data/bug-3961-php8.php b/tests/PHPStan/Analyser/data/bug-3961-php8.php index 657fdf22f6..93b19e3857 100644 --- a/tests/PHPStan/Analyser/data/bug-3961-php8.php +++ b/tests/PHPStan/Analyser/data/bug-3961-php8.php @@ -9,13 +9,13 @@ class Foo public function doFoo(string $v, string $d, $m): void { - assertType('array&nonEmpty', explode('.', $v)); + assertType('non-empty-array', explode('.', $v)); assertType('*NEVER*', explode('', $v)); assertType('array', explode('.', $v, -2)); - assertType('array&nonEmpty', explode('.', $v, 0)); - assertType('array&nonEmpty', explode('.', $v, 1)); - assertType('array&nonEmpty', explode($d, $v)); - assertType('array&nonEmpty', explode($m, $v)); + assertType('non-empty-array', explode('.', $v, 0)); + assertType('non-empty-array', explode('.', $v, 1)); + assertType('non-empty-array', explode($d, $v)); + assertType('non-empty-array', explode($m, $v)); } } diff --git a/tests/PHPStan/Analyser/data/bug-3961.php b/tests/PHPStan/Analyser/data/bug-3961.php index e17706794d..35a1a2ba11 100644 --- a/tests/PHPStan/Analyser/data/bug-3961.php +++ b/tests/PHPStan/Analyser/data/bug-3961.php @@ -9,13 +9,13 @@ class Foo public function doFoo(string $v, string $d, $m): void { - assertType('array&nonEmpty', explode('.', $v)); + assertType('non-empty-array', explode('.', $v)); assertType('false', explode('', $v)); assertType('array', explode('.', $v, -2)); - assertType('array&nonEmpty', explode('.', $v, 0)); - assertType('array&nonEmpty', explode('.', $v, 1)); - assertType('(array&nonEmpty)|false', explode($d, $v)); - assertType('((array&nonEmpty)|false)', explode($m, $v)); + assertType('non-empty-array', explode('.', $v, 0)); + assertType('non-empty-array', explode('.', $v, 1)); + assertType('non-empty-array|false', explode($d, $v)); + assertType('(non-empty-array|false)', explode($m, $v)); } } diff --git a/tests/PHPStan/Analyser/data/bug-4016.php b/tests/PHPStan/Analyser/data/bug-4016.php index 8582c3e5e6..5446e5dd0c 100644 --- a/tests/PHPStan/Analyser/data/bug-4016.php +++ b/tests/PHPStan/Analyser/data/bug-4016.php @@ -14,7 +14,7 @@ public function doFoo(array $a): void { assertType('array', $a); $a[] = 2; - assertType('array&nonEmpty', $a); + assertType('non-empty-array', $a); unset($a[0]); assertType('array', $a); @@ -27,7 +27,7 @@ public function doBar(array $a): void { assertType('array', $a); $a[1] = 2; - assertType('array&nonEmpty', $a); + assertType('non-empty-array', $a); unset($a[1]); assertType('array', $a); diff --git a/tests/PHPStan/Analyser/data/bug-4207.php b/tests/PHPStan/Analyser/data/bug-4207.php index e8a1de5f02..5e9b0bc854 100644 --- a/tests/PHPStan/Analyser/data/bug-4207.php +++ b/tests/PHPStan/Analyser/data/bug-4207.php @@ -5,5 +5,5 @@ use function PHPStan\Testing\assertType; function (): void { - assertType('array&nonEmpty', range(1, 10000)); + assertType('non-empty-array', range(1, 10000)); }; diff --git a/tests/PHPStan/Analyser/data/bug-4398.php b/tests/PHPStan/Analyser/data/bug-4398.php index 23bab3eaa2..ee0c2a2565 100644 --- a/tests/PHPStan/Analyser/data/bug-4398.php +++ b/tests/PHPStan/Analyser/data/bug-4398.php @@ -10,9 +10,9 @@ function (array $meters): void { throw new \Exception('NO_METERS_FOUND'); } - assertType('array&nonEmpty', $meters); + assertType('non-empty-array', $meters); assertType('array', array_reverse()); - assertType('array&nonEmpty', array_reverse($meters)); - assertType('array&nonEmpty', array_keys($meters)); - assertType('array&nonEmpty', array_values($meters)); + assertType('non-empty-array', array_reverse($meters)); + assertType('non-empty-array', array_keys($meters)); + assertType('non-empty-array', array_values($meters)); }; diff --git a/tests/PHPStan/Analyser/data/bug-4499.php b/tests/PHPStan/Analyser/data/bug-4499.php index 5c4b78f63f..046da4efa1 100644 --- a/tests/PHPStan/Analyser/data/bug-4499.php +++ b/tests/PHPStan/Analyser/data/bug-4499.php @@ -11,7 +11,7 @@ class Foo function thing(array $things) : void{ switch(count($things)){ case 1: - assertType('array&nonEmpty', $things); + assertType('non-empty-array', $things); assertType('int', array_shift($things)); } } diff --git a/tests/PHPStan/Analyser/data/bug-4558.php b/tests/PHPStan/Analyser/data/bug-4558.php index 89b250cc0b..1c4aba719c 100644 --- a/tests/PHPStan/Analyser/data/bug-4558.php +++ b/tests/PHPStan/Analyser/data/bug-4558.php @@ -15,7 +15,7 @@ class HelloWorld public function sayHello(): ?DateTime { while (count($this->suggestions) > 0) { - assertType('array&nonEmpty', $this->suggestions); + assertType('non-empty-array', $this->suggestions); assertType('int<1, max>', count($this->suggestions)); $try = array_shift($this->suggestions); diff --git a/tests/PHPStan/Analyser/data/bug-4587.php b/tests/PHPStan/Analyser/data/bug-4587.php index 880aef8d47..995bdfc7e3 100644 --- a/tests/PHPStan/Analyser/data/bug-4587.php +++ b/tests/PHPStan/Analyser/data/bug-4587.php @@ -27,11 +27,11 @@ public function b(): void $type = array_map(static function (array $result): array { assertType('array(\'a\' => int)', $result); $result['a'] = (string) $result['a']; - assertType('array(\'a\' => string&numeric)', $result); + assertType('array(\'a\' => numeric-string)', $result); return $result; }, $results); - assertType('array string&numeric)>', $type); + assertType('array numeric-string)>', $type); } } diff --git a/tests/PHPStan/Analyser/data/bug-4650.php b/tests/PHPStan/Analyser/data/bug-4650.php index f378375869..c00352dc1e 100644 --- a/tests/PHPStan/Analyser/data/bug-4650.php +++ b/tests/PHPStan/Analyser/data/bug-4650.php @@ -12,7 +12,7 @@ class Foo * @phpstan-param non-empty-array $idx */ function doFoo(array $idx): void { - assertType('array&nonEmpty', $idx); + assertType('non-empty-array', $idx); assertNativeType('array', $idx); assertType('array()', []); diff --git a/tests/PHPStan/Analyser/data/bug-4711.php b/tests/PHPStan/Analyser/data/bug-4711.php index f074b57db1..8d25957907 100644 --- a/tests/PHPStan/Analyser/data/bug-4711.php +++ b/tests/PHPStan/Analyser/data/bug-4711.php @@ -12,8 +12,8 @@ function x(string $string): void { return; } - assertType('array&nonEmpty', explode($string, '')); - assertType('array&nonEmpty', explode($string[0], '')); + assertType('non-empty-array', explode($string, '')); + assertType('non-empty-array', explode($string[0], '')); } } diff --git a/tests/PHPStan/Analyser/data/bug-5017.php b/tests/PHPStan/Analyser/data/bug-5017.php index 94ac0efcf1..bb7decee09 100644 --- a/tests/PHPStan/Analyser/data/bug-5017.php +++ b/tests/PHPStan/Analyser/data/bug-5017.php @@ -12,7 +12,7 @@ public function doFoo() $items = [0, 1, 2, 3, 4]; while ($items) { - assertType('array<0|1|2|3|4, 0|1|2|3|4>&nonEmpty', $items); + assertType('non-empty-array<0|1|2|3|4, 0|1|2|3|4>', $items); $batch = array_splice($items, 0, 2); assertType('array<0|1|2|3|4, 0|1|2|3|4>', $items); assertType('array<0|1|2|3|4, 0|1|2|3|4>', $batch); @@ -25,7 +25,7 @@ public function doFoo() public function doBar($items) { while ($items) { - assertType('array&nonEmpty', $items); + assertType('non-empty-array', $items); $batch = array_splice($items, 0, 2); assertType('array', $items); assertType('array', $batch); diff --git a/tests/PHPStan/Analyser/data/bug-5219.php b/tests/PHPStan/Analyser/data/bug-5219.php index ab75d306a5..698c122090 100644 --- a/tests/PHPStan/Analyser/data/bug-5219.php +++ b/tests/PHPStan/Analyser/data/bug-5219.php @@ -12,7 +12,7 @@ protected function foo(string $message): void $header = sprintf('%s-%s', '', implode('-', ['x'])); assertType('non-empty-string', $header); - assertType('array&nonEmpty', [$header => $message]); + assertType('non-empty-array', [$header => $message]); } protected function bar(string $message): void diff --git a/tests/PHPStan/Analyser/data/cast-to-numeric-string.php b/tests/PHPStan/Analyser/data/cast-to-numeric-string.php index e9e2259cb7..5014f87302 100644 --- a/tests/PHPStan/Analyser/data/cast-to-numeric-string.php +++ b/tests/PHPStan/Analyser/data/cast-to-numeric-string.php @@ -13,13 +13,13 @@ * @param 1 $constantInt */ function foo(int $a, float $b, $numeric, $numeric2, $number, $positive, $negative, $constantInt): void { - assertType('string&numeric', (string)$a); - assertType('string&numeric', (string)$b); - assertType('string&numeric', (string)$numeric); - assertType('string&numeric', (string)$numeric2); - assertType('string&numeric', (string)$number); - assertType('string&numeric', (string)$positive); - assertType('string&numeric', (string)$negative); + assertType('numeric-string', (string)$a); + assertType('numeric-string', (string)$b); + assertType('numeric-string', (string)$numeric); + assertType('numeric-string', (string)$numeric2); + assertType('numeric-string', (string)$number); + assertType('numeric-string', (string)$positive); + assertType('numeric-string', (string)$negative); assertType("'1'", (string)$constantInt); } @@ -32,30 +32,30 @@ function foo(int $a, float $b, $numeric, $numeric2, $number, $positive, $negativ * @param 1 $constantInt */ function concatEmptyString(int $a, float $b, $numeric, $numeric2, $number, $positive, $negative, $constantInt): void { - assertType('string&numeric', '' . $a); - assertType('string&numeric', '' . $b); - assertType('string&numeric', '' . $numeric); - assertType('string&numeric', '' . $numeric2); - assertType('string&numeric', '' . $number); - assertType('string&numeric', '' . $positive); - assertType('string&numeric', '' . $negative); + assertType('numeric-string', '' . $a); + assertType('numeric-string', '' . $b); + assertType('numeric-string', '' . $numeric); + assertType('numeric-string', '' . $numeric2); + assertType('numeric-string', '' . $number); + assertType('numeric-string', '' . $positive); + assertType('numeric-string', '' . $negative); assertType("'1'", '' . $constantInt); - assertType('string&numeric', $a . ''); - assertType('string&numeric', $b . ''); - assertType('string&numeric', $numeric . ''); - assertType('string&numeric', $numeric2 . ''); - assertType('string&numeric', $number . ''); - assertType('string&numeric', $positive . ''); - assertType('string&numeric', $negative . ''); + assertType('numeric-string', $a . ''); + assertType('numeric-string', $b . ''); + assertType('numeric-string', $numeric . ''); + assertType('numeric-string', $numeric2 . ''); + assertType('numeric-string', $number . ''); + assertType('numeric-string', $positive . ''); + assertType('numeric-string', $negative . ''); assertType("'1'", $constantInt . ''); } function concatAssignEmptyString(int $i, float $f) { $i .= ''; - assertType('string&numeric', $i); + assertType('numeric-string', $i); $s = ''; $s .= $f; - assertType('string&numeric', $s); + assertType('numeric-string', $s); } diff --git a/tests/PHPStan/Analyser/data/conditional-non-empty-array.php b/tests/PHPStan/Analyser/data/conditional-non-empty-array.php index e13d22e3a0..75afc82e17 100644 --- a/tests/PHPStan/Analyser/data/conditional-non-empty-array.php +++ b/tests/PHPStan/Analyser/data/conditional-non-empty-array.php @@ -19,7 +19,7 @@ public function doFoo(array $a): void assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); if (count($a) > 0) { - assertType('array&nonEmpty', $a); + assertType('non-empty-array', $a); assertVariableCertainty(TrinaryLogic::createYes(), $foo); } else { assertType('array()', $a); diff --git a/tests/PHPStan/Analyser/data/generics.php b/tests/PHPStan/Analyser/data/generics.php index 2f0afe2259..6de2d75b6f 100644 --- a/tests/PHPStan/Analyser/data/generics.php +++ b/tests/PHPStan/Analyser/data/generics.php @@ -147,7 +147,7 @@ function f($a, $b) */ function testF($arrayOfInt, $callableOrNull) { - assertType('Closure(int): string&numeric', function (int $a): string { + assertType('Closure(int): numeric-string', function (int $a): string { return (string)$a; }); assertType('array', f($arrayOfInt, function (int $a): string { @@ -224,7 +224,7 @@ function testArrayMap(array $listOfIntegers) return (string) $int; }, $listOfIntegers); - assertType('array', $strings); + assertType('array', $strings); } /** diff --git a/tests/PHPStan/Analyser/data/list-type.php b/tests/PHPStan/Analyser/data/list-type.php index 8fccd9f831..592814aa9f 100644 --- a/tests/PHPStan/Analyser/data/list-type.php +++ b/tests/PHPStan/Analyser/data/list-type.php @@ -37,7 +37,7 @@ public function withoutGenerics(): void $list[] = '1'; $list[] = true; $list[] = new \stdClass(); - assertType('array&nonEmpty', $list); + assertType('non-empty-array', $list); } @@ -48,7 +48,7 @@ public function withMixedType(): void $list[] = '1'; $list[] = true; $list[] = new \stdClass(); - assertType('array&nonEmpty', $list); + assertType('non-empty-array', $list); } public function withObjectType(): void @@ -56,7 +56,7 @@ public function withObjectType(): void /** @var list<\DateTime> $list */ $list = []; $list[] = new \DateTime(); - assertType('array&nonEmpty', $list); + assertType('non-empty-array', $list); } /** @return list */ @@ -66,7 +66,7 @@ public function withScalarGoodContent(): void $list = []; $list[] = '1'; $list[] = true; - assertType('array&nonEmpty', $list); + assertType('non-empty-array', $list); } public function withNumericKey(): void @@ -75,7 +75,7 @@ public function withNumericKey(): void $list = []; $list[] = '1'; $list['1'] = true; - assertType('array&nonEmpty', $list); + assertType('non-empty-array', $list); } public function withFullListFunctionality(): void @@ -91,7 +91,7 @@ public function withFullListFunctionality(): void /** @var list $list2 */ $list2 = []; $list2[2] = '1';//Most likely to create a gap in indexes - assertType('array&nonEmpty', $list2); + assertType('non-empty-array', $list2); } } diff --git a/tests/PHPStan/Analyser/data/native-types.php b/tests/PHPStan/Analyser/data/native-types.php index 56da164362..1327192ace 100644 --- a/tests/PHPStan/Analyser/data/native-types.php +++ b/tests/PHPStan/Analyser/data/native-types.php @@ -83,8 +83,8 @@ public function doForeach(array $array): void assertNativeType('array', $array); foreach ($array as $key => $value) { - assertType('array&nonEmpty', $array); - assertNativeType('array&nonEmpty', $array); + assertType('non-empty-array', $array); + assertNativeType('non-empty-array', $array); assertType('string', $key); assertNativeType('(int|string)', $key); @@ -124,8 +124,8 @@ public function doForeachArrayDestructuring(array $array) assertType('array', $array); assertNativeType('array', $array); foreach ($array as $key => [$i, $s]) { - assertType('array&nonEmpty', $array); - assertNativeType('array&nonEmpty', $array); + assertType('non-empty-array', $array); + assertNativeType('non-empty-array', $array); assertType('string', $key); assertNativeType('(int|string)', $key); diff --git a/tests/PHPStan/Analyser/data/non-empty-array-key-type.php b/tests/PHPStan/Analyser/data/non-empty-array-key-type.php index 90ae50bcd5..479fe40846 100644 --- a/tests/PHPStan/Analyser/data/non-empty-array-key-type.php +++ b/tests/PHPStan/Analyser/data/non-empty-array-key-type.php @@ -15,7 +15,7 @@ public function doFoo(array $items) assertType('array', $items); if (count($items) > 0) { - assertType('array&nonEmpty', $items); + assertType('non-empty-array', $items); foreach ($items as $i => $val) { assertType('(int|string)', $i); assertType('stdClass', $val); diff --git a/tests/PHPStan/Analyser/data/non-empty-array.php b/tests/PHPStan/Analyser/data/non-empty-array.php index 316620e1af..2e3a24f305 100644 --- a/tests/PHPStan/Analyser/data/non-empty-array.php +++ b/tests/PHPStan/Analyser/data/non-empty-array.php @@ -25,11 +25,11 @@ public function doFoo( $invalidList2 ): void { - assertType('array&nonEmpty', $array); - assertType('array&nonEmpty', $list); - assertType('array&nonEmpty', $arrayOfStrings); - assertType('array&nonEmpty', $listOfStd); - assertType('array&nonEmpty', $listOfStd2); + assertType('non-empty-array', $array); + assertType('non-empty-array', $list); + assertType('non-empty-array', $arrayOfStrings); + assertType('non-empty-array', $listOfStd); + assertType('non-empty-array', $listOfStd2); assertType('array', $invalidList); assertType('mixed', $invalidList2); } @@ -41,15 +41,15 @@ public function doFoo( */ public function arrayFunctions($array, $list, $stringArray): void { - assertType('array&nonEmpty', array_combine($array, $array)); - assertType('array&nonEmpty', array_combine($list, $list)); + assertType('non-empty-array', array_combine($array, $array)); + assertType('non-empty-array', array_combine($list, $list)); - assertType('array&nonEmpty', array_merge($array)); - assertType('array&nonEmpty', array_merge([], $array)); - assertType('array&nonEmpty', array_merge($array, [])); - assertType('array&nonEmpty', array_merge($array, $array)); + assertType('non-empty-array', array_merge($array)); + assertType('non-empty-array', array_merge([], $array)); + assertType('non-empty-array', array_merge($array, [])); + assertType('non-empty-array', array_merge($array, $array)); - assertType('array&nonEmpty', array_flip($array)); - assertType('array&nonEmpty', array_flip($stringArray)); + assertType('non-empty-array', array_flip($array)); + assertType('non-empty-array', array_flip($stringArray)); } } diff --git a/tests/PHPStan/Analyser/data/non-empty-string.php b/tests/PHPStan/Analyser/data/non-empty-string.php index c9b1b8cfa0..192289f6f5 100644 --- a/tests/PHPStan/Analyser/data/non-empty-string.php +++ b/tests/PHPStan/Analyser/data/non-empty-string.php @@ -103,7 +103,7 @@ public function doFoo3(string $s): void */ public function doFoo4(string $s): void { - assertType('array&nonEmpty', explode($s, 'foo')); + assertType('non-empty-array', explode($s, 'foo')); } /** @@ -115,7 +115,7 @@ public function doWithNumeric(string $s): void return; } - assertType('non-empty-string', $s); + assertType('non-empty-string&numeric-string', $s); } public function doEmpty(string $s): void @@ -282,7 +282,7 @@ public function doFoo(array $a, string $s): void $a[$s] = 2; // there might be non-empty-string that becomes a number instead - assertType('array&nonEmpty', $a); + assertType('non-empty-array', $a); } /** @@ -292,7 +292,7 @@ public function doFoo(array $a, string $s): void public function doFoo2(array $a, string $s): void { $a[''] = 2; - assertType('array&nonEmpty', $a); + assertType('non-empty-array', $a); } } diff --git a/tests/PHPStan/Analyser/data/number_format.php b/tests/PHPStan/Analyser/data/number_format.php index 271e501825..eb4d2a81ca 100644 --- a/tests/PHPStan/Analyser/data/number_format.php +++ b/tests/PHPStan/Analyser/data/number_format.php @@ -12,7 +12,7 @@ assertType('string', number_format(1002.7, 3, 'b', null)); assertType('string', number_format(1002.7, 3, 'b', '')); -assertType('string&numeric', number_format(1002.7, 3, '.', '')); -assertType('string&numeric', number_format(1002.7, 3, null, '')); -assertType('string&numeric', number_format(1002.7, 3, '', '')); +assertType('numeric-string', number_format(1002.7, 3, '.', '')); +assertType('numeric-string', number_format(1002.7, 3, null, '')); +assertType('numeric-string', number_format(1002.7, 3, '', '')); diff --git a/tests/PHPStan/Analyser/data/phpdoc-pseudotype-global.php b/tests/PHPStan/Analyser/data/phpdoc-pseudotype-global.php index 75ed167f90..b8d6c138de 100644 --- a/tests/PHPStan/Analyser/data/phpdoc-pseudotype-global.php +++ b/tests/PHPStan/Analyser/data/phpdoc-pseudotype-global.php @@ -22,7 +22,7 @@ function () { $double = doFoo(); assertType('float|int', $number); - assertType('float|int|(string&numeric)', $numeric); + assertType('float|int|numeric-string', $numeric); assertType('bool', $boolean); assertType('resource', $resource); assertType('*NEVER*', $never); diff --git a/tests/PHPStan/Analyser/data/sizeof.php b/tests/PHPStan/Analyser/data/sizeof.php index f569d8902a..1a080946c4 100644 --- a/tests/PHPStan/Analyser/data/sizeof.php +++ b/tests/PHPStan/Analyser/data/sizeof.php @@ -32,7 +32,7 @@ function doFoo2(array $ints): string function doFoo3(array $arr): string { if (0 != count($arr)) { - assertType('array&nonEmpty', $arr); + assertType('non-empty-array', $arr); } return ""; } @@ -40,7 +40,7 @@ function doFoo3(array $arr): string function doFoo4(array $arr): string { if (0 != sizeof($arr)) { - assertType('array&nonEmpty', $arr); + assertType('non-empty-array', $arr); } return ""; } @@ -48,7 +48,7 @@ function doFoo4(array $arr): string function doFoo5(array $arr): void { if ([] != $arr) { - assertType('array&nonEmpty', $arr); + assertType('non-empty-array', $arr); } assertType('array', $arr); } @@ -56,7 +56,7 @@ function doFoo5(array $arr): void function doFoo6(array $arr): void { if ($arr != []) { - assertType('array&nonEmpty', $arr); + assertType('non-empty-array', $arr); } assertType('array', $arr); } diff --git a/tests/PHPStan/Analyser/data/strval.php b/tests/PHPStan/Analyser/data/strval.php index d85d37eec1..f6fa62fe93 100644 --- a/tests/PHPStan/Analyser/data/strval.php +++ b/tests/PHPStan/Analyser/data/strval.php @@ -17,9 +17,9 @@ function strvalTest(string $string, string $class): void assertType('\'1\'', strval(true)); assertType('\'\'|\'1\'', strval(rand(0, 1) === 0)); assertType('\'42\'', strval(42)); - assertType('string&numeric', strval(rand())); - assertType('string&numeric', strval(rand() * 0.1)); - assertType('string&numeric', strval(strval(rand()))); + assertType('numeric-string', strval(rand())); + assertType('numeric-string', strval(rand() * 0.1)); + assertType('numeric-string', strval(strval(rand()))); assertType('class-string', strval($class)); assertType('string', strval(new \Exception())); assertType('*ERROR*', strval(new \stdClass())); diff --git a/tests/PHPStan/Levels/data/acceptTypes-5.json b/tests/PHPStan/Levels/data/acceptTypes-5.json index 78f19787d7..6810f8b1d2 100644 --- a/tests/PHPStan/Levels/data/acceptTypes-5.json +++ b/tests/PHPStan/Levels/data/acceptTypes-5.json @@ -180,22 +180,22 @@ "ignorable": true }, { - "message": "Parameter #1 $numericString of method Levels\\AcceptTypes\\NumericStrings::doBar() expects string&numeric, 'foo' given.", + "message": "Parameter #1 $numericString of method Levels\\AcceptTypes\\NumericStrings::doBar() expects numeric-string, 'foo' given.", "line": 707, "ignorable": true }, { - "message": "Parameter #1 $numericString of method Levels\\AcceptTypes\\NumericStrings::doBar() expects string&numeric, string given.", + "message": "Parameter #1 $numericString of method Levels\\AcceptTypes\\NumericStrings::doBar() expects numeric-string, string given.", "line": 708, "ignorable": true }, { - "message": "Parameter #1 $nonEmpty of method Levels\\AcceptTypes\\AcceptNonEmpty::doBar() expects array&nonEmpty, array() given.", + "message": "Parameter #1 $nonEmpty of method Levels\\AcceptTypes\\AcceptNonEmpty::doBar() expects non-empty-array, array() given.", "line": 733, "ignorable": true }, { - "message": "Parameter #1 $nonEmpty of method Levels\\AcceptTypes\\AcceptNonEmpty::doBar() expects array&nonEmpty, array given.", + "message": "Parameter #1 $nonEmpty of method Levels\\AcceptTypes\\AcceptNonEmpty::doBar() expects non-empty-array, array given.", "line": 735, "ignorable": true }, @@ -204,4 +204,4 @@ "line": 763, "ignorable": true } -] \ No newline at end of file +] diff --git a/tests/PHPStan/PhpDoc/TypeDescriptionTest.php b/tests/PHPStan/PhpDoc/TypeDescriptionTest.php index 5aef8fe030..f62b8b624a 100644 --- a/tests/PHPStan/PhpDoc/TypeDescriptionTest.php +++ b/tests/PHPStan/PhpDoc/TypeDescriptionTest.php @@ -5,9 +5,15 @@ use PHPStan\Testing\PHPStanTestCase; use PHPStan\Type\Accessory\AccessoryLiteralStringType; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; +use PHPStan\Type\Accessory\AccessoryNumericStringType; +use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\ArrayType; +use PHPStan\Type\ClassStringType; +use PHPStan\Type\Generic\GenericClassStringType; +use PHPStan\Type\IntegerType; use PHPStan\Type\IntersectionType; use PHPStan\Type\MixedType; +use PHPStan\Type\ObjectType; use PHPStan\Type\StringType; use PHPStan\Type\Type; use PHPStan\Type\VerbosityLevel; @@ -21,6 +27,12 @@ public function dataTest(): iterable yield ['array', new ArrayType(new MixedType(), new MixedType())]; yield ['literal-string', new IntersectionType([new StringType(), new AccessoryLiteralStringType()])]; yield ['non-empty-string', new IntersectionType([new StringType(), new AccessoryNonEmptyStringType()])]; + yield ['numeric-string', new IntersectionType([new StringType(), new AccessoryNumericStringType()])]; + yield ['literal-string&non-empty-string', new IntersectionType([new StringType(), new AccessoryLiteralStringType(), new AccessoryNonEmptyStringType()])]; + yield ['non-empty-array', new IntersectionType([new ArrayType(new MixedType(), new MixedType()), new NonEmptyArrayType()])]; + yield ['non-empty-array', new IntersectionType([new ArrayType(new IntegerType(), new StringType()), new NonEmptyArrayType()])]; + yield ['class-string&literal-string', new IntersectionType([new ClassStringType(), new AccessoryLiteralStringType()])]; + yield ['class-string&literal-string', new IntersectionType([new GenericClassStringType(new ObjectType('Foo')), new AccessoryLiteralStringType()])]; } /** diff --git a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php index 6ab3efd944..a572b8c6a1 100644 --- a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php @@ -83,7 +83,7 @@ public function testStrictComparison(): void 130, ], [ - 'Strict comparison using === between array&nonEmpty and null will always evaluate to false.', + 'Strict comparison using === between non-empty-array and null will always evaluate to false.', 140, ], [ @@ -91,7 +91,7 @@ public function testStrictComparison(): void 154, ], [ - 'Strict comparison using === between array&nonEmpty and null will always evaluate to false.', + 'Strict comparison using === between non-empty-array and null will always evaluate to false.', 164, ], [ @@ -281,11 +281,11 @@ public function testStrictComparisonWithoutAlwaysTrue(): void 98, ], [ - 'Strict comparison using === between array&nonEmpty and null will always evaluate to false.', + 'Strict comparison using === between non-empty-array and null will always evaluate to false.', 140, ], [ - 'Strict comparison using === between array&nonEmpty and null will always evaluate to false.', + 'Strict comparison using === between non-empty-array and null will always evaluate to false.', 164, ], [ diff --git a/tests/PHPStan/Rules/Debug/DumpTypeRuleTest.php b/tests/PHPStan/Rules/Debug/DumpTypeRuleTest.php index f172d91e98..d611afb9ef 100644 --- a/tests/PHPStan/Rules/Debug/DumpTypeRuleTest.php +++ b/tests/PHPStan/Rules/Debug/DumpTypeRuleTest.php @@ -20,7 +20,7 @@ public function testRuleInPhpStanNamespace(): void { $this->analyse([__DIR__ . '/data/dump-type.php'], [ [ - 'Dumped type: array&nonEmpty', + 'Dumped type: non-empty-array', 10, ], [ @@ -34,7 +34,7 @@ public function testRuleInDifferentNamespace(): void { $this->analyse([__DIR__ . '/data/dump-type-ns.php'], [ [ - 'Dumped type: array&nonEmpty', + 'Dumped type: non-empty-array', 10, ], ]); @@ -44,11 +44,11 @@ public function testRuleInUse(): void { $this->analyse([__DIR__ . '/data/dump-type-use.php'], [ [ - 'Dumped type: array&nonEmpty', + 'Dumped type: non-empty-array', 12, ], [ - 'Dumped type: array&nonEmpty', + 'Dumped type: non-empty-array', 13, ], ]); diff --git a/tests/PHPStan/Rules/Debug/data/file-asserts.php b/tests/PHPStan/Rules/Debug/data/file-asserts.php index 5f9fb9cc8d..abd8e54d07 100644 --- a/tests/PHPStan/Rules/Debug/data/file-asserts.php +++ b/tests/PHPStan/Rules/Debug/data/file-asserts.php @@ -24,7 +24,7 @@ public function doFoo(array $a): void */ public function doBar(array $a): void { - assertType('array&nonEmpty', $a); + assertType('non-empty-array', $a); assertNativeType('array', $a); assertType('false', $a === []); diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 6fea88f460..630f370f5b 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -483,11 +483,11 @@ public function testCallMethods(): void 1589, ], [ - 'Parameter #1 $test of method Test\NumericStringParam::sayHello() expects string&numeric, 123 given.', + 'Parameter #1 $test of method Test\NumericStringParam::sayHello() expects numeric-string, 123 given.', 1657, ], [ - 'Parameter #1 $test of method Test\NumericStringParam::sayHello() expects string&numeric, \'abc\' given.', + 'Parameter #1 $test of method Test\NumericStringParam::sayHello() expects numeric-string, \'abc\' given.', 1658, ], [ @@ -770,11 +770,11 @@ public function testCallMethodsOnThisOnly(): void 1589, ], [ - 'Parameter #1 $test of method Test\NumericStringParam::sayHello() expects string&numeric, 123 given.', + 'Parameter #1 $test of method Test\NumericStringParam::sayHello() expects numeric-string, 123 given.', 1657, ], [ - 'Parameter #1 $test of method Test\NumericStringParam::sayHello() expects string&numeric, \'abc\' given.', + 'Parameter #1 $test of method Test\NumericStringParam::sayHello() expects numeric-string, \'abc\' given.', 1658, ], [ diff --git a/tests/PHPStan/Rules/Variables/IssetRuleTest.php b/tests/PHPStan/Rules/Variables/IssetRuleTest.php index 8989023c5e..5ea863b812 100644 --- a/tests/PHPStan/Rules/Variables/IssetRuleTest.php +++ b/tests/PHPStan/Rules/Variables/IssetRuleTest.php @@ -229,7 +229,7 @@ public function testBug4671(): void { $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-4671.php'], [[ - 'Offset string&numeric on array in isset() does not exist.', + 'Offset numeric-string on array in isset() does not exist.', 13, ]]); } diff --git a/tests/PHPStan/Type/TypeCombinatorTest.php b/tests/PHPStan/Type/TypeCombinatorTest.php index c418c4148c..380dd85c72 100644 --- a/tests/PHPStan/Type/TypeCombinatorTest.php +++ b/tests/PHPStan/Type/TypeCombinatorTest.php @@ -2489,7 +2489,7 @@ public function dataIntersect(): array new NonEmptyArrayType(), ], IntersectionType::class, - 'array&nonEmpty', + 'non-empty-array', ], [ [ @@ -2508,7 +2508,7 @@ public function dataIntersect(): array new NonEmptyArrayType(), ], IntersectionType::class, - 'array&nonEmpty', + 'non-empty-array', ], [ [ @@ -3029,7 +3029,7 @@ public function dataIntersect(): array new AccessoryNumericStringType(), ], IntersectionType::class, - 'string&numeric', + 'numeric-string', ], [ [ @@ -3376,7 +3376,7 @@ public function dataRemove(): array new ArrayType(new MixedType(), new MixedType()), new ConstantArrayType([], []), IntersectionType::class, - 'array&nonEmpty', + 'non-empty-array', ], [ TypeCombinator::union( diff --git a/tests/PHPStan/Type/UnionTypeTest.php b/tests/PHPStan/Type/UnionTypeTest.php index 2226b18e20..9ba73d7e06 100644 --- a/tests/PHPStan/Type/UnionTypeTest.php +++ b/tests/PHPStan/Type/UnionTypeTest.php @@ -677,7 +677,7 @@ public function dataDescribe(): array new AccessoryNumericStringType(), ]) ), - 'int|(string&numeric)', + 'int|numeric-string', 'int|string', ], ]; From 054b12b1bffd680c3d59debff90ac17023c1f9db Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 12 Oct 2021 16:28:22 +0200 Subject: [PATCH 0438/1284] Fix tests --- tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 8c58d5a4db..971981a5e2 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -5550,7 +5550,7 @@ public function dataFunctions(): array '$gettimeofdayBenevolent', ], [ - PHP_VERSION_ID < 80000 ? '(non-empty-array)|false' : 'non-empty-array', + PHP_VERSION_ID < 80000 ? 'non-empty-array|false' : 'non-empty-array', '$strSplitConstantStringWithoutDefinedParameters', ], [ @@ -5574,7 +5574,7 @@ public function dataFunctions(): array '$strSplitConstantStringWithFailureSplitLength', ], [ - PHP_VERSION_ID < 80000 ? '(non-empty-array)|false' : 'non-empty-array', + PHP_VERSION_ID < 80000 ? 'non-empty-array|false' : 'non-empty-array', '$strSplitConstantStringWithInvalidSplitLengthType', ], [ @@ -5582,7 +5582,7 @@ public function dataFunctions(): array '$strSplitConstantStringWithVariableStringAndConstantSplitLength', ], [ - PHP_VERSION_ID < 80000 ? '(non-empty-array)|false' : 'non-empty-array', + PHP_VERSION_ID < 80000 ? 'non-empty-array|false' : 'non-empty-array', '$strSplitConstantStringWithVariableStringAndVariableSplitLength', ], // parse_url @@ -7490,11 +7490,11 @@ public function dataExplode(): array '$sureFalse', ], [ - PHP_VERSION_ID < 80000 ? '(non-empty-array)|false' : 'non-empty-array', + PHP_VERSION_ID < 80000 ? 'non-empty-array|false' : 'non-empty-array', '$arrayOrFalse', ], [ - PHP_VERSION_ID < 80000 ? '(non-empty-array)|false' : 'non-empty-array', + PHP_VERSION_ID < 80000 ? 'non-empty-array|false' : 'non-empty-array', '$anotherArrayOrFalse', ], [ From 0bca40294c8b02e07a363f770c2995d224fa60e3 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 12 Oct 2021 16:49:44 +0200 Subject: [PATCH 0439/1284] Fix --- tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 971981a5e2..44d3ccba52 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -9129,7 +9129,7 @@ public function dataPhp74Functions(): array '$mbStrSplitConstantStringWithVariableStringAndConstantSplitLength', ], [ - PHP_VERSION_ID < 80000 ? 'non-empty-|false' : 'non-empty-array', + PHP_VERSION_ID < 80000 ? 'non-empty-array|false' : 'non-empty-array', '$mbStrSplitConstantStringWithVariableStringAndVariableSplitLength', ], [ From 2b3ee60ad530919c6c77342233657487df607d13 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 13 Oct 2021 13:26:50 +0200 Subject: [PATCH 0440/1284] TypeStringResolver::resolve() is covered by BC promise --- src/PhpDoc/TypeStringResolver.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/PhpDoc/TypeStringResolver.php b/src/PhpDoc/TypeStringResolver.php index 91e8ec4df0..bbc426249d 100644 --- a/src/PhpDoc/TypeStringResolver.php +++ b/src/PhpDoc/TypeStringResolver.php @@ -24,6 +24,7 @@ public function __construct(Lexer $typeLexer, TypeParser $typeParser, TypeNodeRe $this->typeNodeResolver = $typeNodeResolver; } + /** @api */ public function resolve(string $typeString, ?NameScope $nameScope = null): Type { $tokens = new TokenIterator($this->typeLexer->tokenize($typeString)); From 51d7431dbe802827bac61907d7bcb58a168dff81 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 12 Oct 2021 16:11:25 +0200 Subject: [PATCH 0441/1284] Unify array shape description with PHPDoc type syntax --- phpstan-baseline.neon | 2 +- src/Type/Constant/ConstantArrayType.php | 13 +- .../Analyser/LegacyNodeScopeResolverTest.php | 258 +++++++++--------- tests/PHPStan/Analyser/ScopeTest.php | 4 +- tests/PHPStan/Analyser/TypeSpecifierTest.php | 8 +- .../data/array-shapes-keys-strings.php | 4 +- .../Analyser/data/array_map_multiple.php | 10 +- .../Analyser/data/arrow-function-types.php | 14 +- .../Analyser/data/assign-nested-arrays.php | 4 +- tests/PHPStan/Analyser/data/bug-1283.php | 2 +- tests/PHPStan/Analyser/data/bug-1861.php | 2 +- tests/PHPStan/Analyser/data/bug-1924.php | 2 +- tests/PHPStan/Analyser/data/bug-2001.php | 14 +- tests/PHPStan/Analyser/data/bug-2232.php | 2 +- tests/PHPStan/Analyser/data/bug-2378.php | 4 +- tests/PHPStan/Analyser/data/bug-2677.php | 2 +- tests/PHPStan/Analyser/data/bug-2733.php | 2 +- tests/PHPStan/Analyser/data/bug-3009.php | 8 +- tests/PHPStan/Analyser/data/bug-3269.php | 4 +- tests/PHPStan/Analyser/data/bug-3276.php | 4 +- tests/PHPStan/Analyser/data/bug-3351.php | 2 +- tests/PHPStan/Analyser/data/bug-3558.php | 4 +- tests/PHPStan/Analyser/data/bug-3991.php | 2 +- tests/PHPStan/Analyser/data/bug-4099.php | 12 +- tests/PHPStan/Analyser/data/bug-4213.php | 2 +- tests/PHPStan/Analyser/data/bug-4504.php | 2 +- tests/PHPStan/Analyser/data/bug-4558.php | 2 +- tests/PHPStan/Analyser/data/bug-4587.php | 10 +- tests/PHPStan/Analyser/data/bug-4606.php | 4 +- tests/PHPStan/Analyser/data/bug-4650.php | 4 +- tests/PHPStan/Analyser/data/bug-4700.php | 8 +- tests/PHPStan/Analyser/data/bug-4814.php | 2 +- tests/PHPStan/Analyser/data/bug-5017.php | 2 +- tests/PHPStan/Analyser/data/bug-5219.php | 2 +- tests/PHPStan/Analyser/data/bug-5584.php | 2 +- .../PHPStan/Analyser/data/bug-empty-array.php | 8 +- tests/PHPStan/Analyser/data/bug-pr-339.php | 8 +- .../Analyser/data/closure-return-type.php | 2 +- tests/PHPStan/Analyser/data/closure-types.php | 14 +- tests/PHPStan/Analyser/data/compact.php | 8 +- .../data/conditional-non-empty-array.php | 2 +- .../Analyser/data/empty-array-shape.php | 2 +- tests/PHPStan/Analyser/data/generics.php | 18 +- tests/PHPStan/Analyser/data/minmax-arrays.php | 10 +- ...missing-closure-native-return-typehint.php | 2 +- tests/PHPStan/Analyser/data/native-types.php | 4 +- tests/PHPStan/Analyser/data/preg_split.php | 4 +- .../PHPStan/Analyser/data/proc_get_status.php | 2 +- tests/PHPStan/Analyser/data/type-aliases.php | 2 +- tests/PHPStan/Levels/data/acceptTypes-5.json | 14 +- tests/PHPStan/Levels/data/acceptTypes-7.json | 8 +- .../Levels/data/arrayDestructuring-3.json | 2 +- .../Levels/data/arrayDimFetches-3.json | 4 +- .../Levels/data/arrayDimFetches-7.json | 8 +- .../PHPStan/Levels/data/stubs-methods-4.json | 14 +- tests/PHPStan/PhpDoc/TypeDescriptionTest.php | 33 +++ .../Arrays/AppendedArrayItemTypeRuleTest.php | 6 +- .../Arrays/ArrayDestructuringRuleTest.php | 6 +- ...nexistentOffsetInArrayDimFetchRuleTest.php | 24 +- .../Arrays/OffsetAccessAssignmentRuleTest.php | 4 +- tests/PHPStan/Rules/Cast/EchoRuleTest.php | 6 +- tests/PHPStan/Rules/Cast/PrintRuleTest.php | 6 +- ...mpossibleCheckTypeFunctionCallRuleTest.php | 32 +-- ...rictComparisonOfDifferentTypesRuleTest.php | 8 +- .../Rules/Functions/CallCallablesRuleTest.php | 12 +- .../Functions/ClosureReturnTypeRuleTest.php | 2 +- .../Generators/YieldFromTypeRuleTest.php | 2 +- .../Rules/Generators/YieldTypeRuleTest.php | 2 +- .../Rules/Methods/CallMethodsRuleTest.php | 20 +- .../Methods/CallStaticMethodsRuleTest.php | 2 +- .../Rules/Methods/ReturnTypeRuleTest.php | 6 +- .../InvalidUnaryOperationRuleTest.php | 2 +- .../PHPStan/Rules/Variables/EmptyRuleTest.php | 12 +- .../PHPStan/Rules/Variables/IssetRuleTest.php | 24 +- .../Rules/Variables/NullCoalesceRuleTest.php | 20 +- tests/PHPStan/Type/TypeCombinatorTest.php | 44 +-- tests/PHPStan/Type/UnionTypeTest.php | 10 +- tests/e2e/ResultCacheEndToEndTest.php | 2 +- 78 files changed, 443 insertions(+), 401 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 8078e4d048..f04295337a 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -235,7 +235,7 @@ parameters: path: src/Rules/Api/ApiTraitUseRule.php - - message: "#^Binary operation \"\\+\" between array\\(class\\-string\\\\) and array\\\\|false results in an error\\.$#" + message: "#^Binary operation \"\\+\" between array{class\\-string\\} and array\\\\|false results in an error\\.$#" count: 1 path: src/Rules/Registry.php diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 7a800fc291..cf4d7daa57 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -728,7 +728,16 @@ public function describe(VerbosityLevel $level): string $exportValuesOnly = false; } - $items[] = sprintf('%s%s => %s', $isOptional ? '?' : '', var_export($keyType->getValue(), true), $valueType->describe($level)); + $keyDescription = $keyType->getValue(); + if (is_string($keyDescription)) { + if (strpos($keyDescription, '"') !== false) { + $keyDescription = sprintf('\'%s\'', $keyDescription); + } elseif (strpos($keyDescription, '\'') !== false) { + $keyDescription = sprintf('"%s"', $keyDescription); + } + } + + $items[] = sprintf('%s%s: %s', $keyDescription, $isOptional ? '?' : '', $valueType->describe($level)); $values[] = $valueType->describe($level); } @@ -740,7 +749,7 @@ public function describe(VerbosityLevel $level): string } return sprintf( - 'array(%s%s)', + 'array{%s%s}', implode(', ', $exportValuesOnly ? $values : $items), $append ); diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 44d3ccba52..d72df9742b 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -165,7 +165,7 @@ public function dataUnionAndIntersection(): array 'self::IPSUM_CONSTANT', ], [ - 'array(1, 2, 3)', + 'array{1, 2, 3}', 'parent::PARENT_CONSTANT', ], [ @@ -252,19 +252,19 @@ public function dataAssignInIf(): array $testScope, 'arrOne', TrinaryLogic::createYes(), - 'array(\'one\')', + 'array{\'one\'}', ], [ $testScope, 'arrTwo', TrinaryLogic::createYes(), - 'array(\'test\' => \'two\', 0 => Foo)', + 'array{test: \'two\', 0: Foo}', ], [ $testScope, 'arrThree', TrinaryLogic::createYes(), - 'array(\'three\')', + 'array{\'three\'}', ], [ $testScope, @@ -300,7 +300,7 @@ public function dataAssignInIf(): array $testScope, 'anotherArray', TrinaryLogic::createYes(), - 'array(\'test\' => array(\'another\'))', + 'array{test: array{\'another\'}}', ], [ $testScope, @@ -485,13 +485,13 @@ public function dataAssignInIf(): array $testScope, 'nullableIntegers', TrinaryLogic::createYes(), - 'array(1, 2, 3, null)', + 'array{1, 2, 3, null}', ], [ $testScope, 'union', TrinaryLogic::createYes(), - 'array(1, 2, 3, \'foo\')', + 'array{1, 2, 3, \'foo\'}', '1|2|3|\'foo\'', ], [ @@ -659,7 +659,7 @@ public function dataAssignInIf(): array $testScope, 'arrayOfIntegers', TrinaryLogic::createYes(), - 'array(1, 2, 3)', + 'array{1, 2, 3}', ], [ $testScope, @@ -783,7 +783,7 @@ public function dataConstantTypes(): array [ $testScope, 'literalArray', - 'array(\'a\' => 2, \'b\' => 4, \'c\' => 2, \'d\' => 4)', + 'array{a: 2, b: 4, c: 2, d: 4}', ], [ $testScope, @@ -823,7 +823,7 @@ public function dataConstantTypes(): array [ $testScope, 'arrayOverwrittenInForLoop', - 'array(\'a\' => int, \'b\' => \'bar\'|\'foo\')', + 'array{a: int, b: \'bar\'|\'foo\'}', ], [ $testScope, @@ -1560,31 +1560,31 @@ public function dataCasts(): array '(float) $str', ], [ - 'array(\'\' . "\0" . \'TypesNamespaceCasts\\\\Foo\' . "\0" . \'foo\' => TypesNamespaceCasts\Foo, \'\' . "\0" . \'TypesNamespaceCasts\\\\Foo\' . "\0" . \'int\' => int, \'\' . "\0" . \'*\' . "\0" . \'protectedInt\' => int, \'publicInt\' => int, \'\' . "\0" . \'TypesNamespaceCasts\\\\Bar\' . "\0" . \'barProperty\' => TypesNamespaceCasts\Bar)', + "array{\0TypesNamespaceCasts\\Foo\0foo: TypesNamespaceCasts\\Foo, \0TypesNamespaceCasts\\Foo\0int: int, \0*\0protectedInt: int, publicInt: int, \0TypesNamespaceCasts\\Bar\0barProperty: TypesNamespaceCasts\\Bar}", '(array) $foo', ], [ - 'array(1, 2, 3)', + 'array{1, 2, 3}', '(array) [1, 2, 3]', ], [ - 'array(1)', + 'array{1}', '(array) 1', ], [ - 'array(1.0)', + 'array{1.0}', '(array) 1.0', ], [ - 'array(true)', + 'array{true}', '(array) true', ], [ - 'array(\'blabla\')', + 'array{\'blabla\'}', '(array) "blabla"', ], [ - 'array(int)', + 'array{int}', '(array) $castedInteger', ], [ @@ -1691,7 +1691,7 @@ public function dataDeductedTypes(): array '$newStatic', ], [ - 'array()', + 'array{}', '$arrayLiteral', ], [ @@ -1723,7 +1723,7 @@ public function dataDeductedTypes(): array 'self::STRING_CONSTANT', ], [ - 'array()', + 'array{}', 'self::ARRAY_CONSTANT', ], [ @@ -1747,7 +1747,7 @@ public function dataDeductedTypes(): array '$foo::STRING_CONSTANT', ], [ - 'array()', + 'array{}', '$foo::ARRAY_CONSTANT', ], [ @@ -2382,7 +2382,7 @@ public function dataBinaryOperations(): array 'min([1, 2, 3])', ], [ - 'array(1, 2, 3)', + 'array{1, 2, 3}', 'min([1, 2, 3], [4, 5, 5])', ], [ @@ -2398,11 +2398,11 @@ public function dataBinaryOperations(): array 'min(0, ...[1, 2, 3])', ], [ - 'array(5, 6, 9)', + 'array{5, 6, 9}', 'max([1, 10, 8], [5, 6, 9])', ], [ - 'array(1, 1, 1, 1)', + 'array{1, 1, 1, 1}', 'max(array(2, 2, 2), array(1, 1, 1, 1))', ], [ @@ -2630,23 +2630,23 @@ public function dataBinaryOperations(): array '!empty($foo)', ], [ - 'array(int, int, int)', + 'array{int, int, int}', '$arrayOfIntegers + $arrayOfIntegers', ], [ - 'array(int, int, int)', + 'array{int, int, int}', '$arrayOfIntegers += $arrayOfIntegers', ], [ - 'array(0 => 1, 1 => 1, 2 => 1, 3 => 1|2, 4 => 1|3, ?5 => 2|3, ?6 => 3)', + 'array{0: 1, 1: 1, 2: 1, 3: 1|2, 4: 1|3, 5?: 2|3, 6?: 3}', '$conditionalArray + $unshiftedConditionalArray', ], [ - 'array(0 => \'lorem\', 1 => stdClass, 2 => 1, 3 => 1, 4 => 1, ?5 => 2|3, ?6 => 3)', + 'array{0: \'lorem\', 1: stdClass, 2: 1, 3: 1, 4: 1, 5?: 2|3, 6?: 3}', '$unshiftedConditionalArray + $conditionalArray', ], [ - 'array(int, int, int)', + 'array{int, int, int}', '$arrayOfIntegers += ["foo"]', ], [ @@ -2658,7 +2658,7 @@ public function dataBinaryOperations(): array '@count($arrayOfIntegers)', ], [ - 'array(int, int, int)', + 'array{int, int, int}', '$anotherArray = $arrayOfIntegers', ], [ @@ -2758,15 +2758,15 @@ public function dataBinaryOperations(): array '$preIncArray[3]', ], [ - 'array(1 => 1, 2 => 2)', + 'array{1: 1, 2: 2}', '$preIncArray', ], [ - 'array(0 => 1, 2 => 3)', + 'array{0: 1, 2: 3}', '$postIncArray', ], [ - 'array(0 => array(1 => array(2 => 3)), 4 => array(5 => array(6 => 7)))', + 'array{0: array{1: array{2: 3}}, 4: array{5: array{6: 7}}}', '$anotherPostIncArray', ], [ @@ -2826,7 +2826,7 @@ public function dataBinaryOperations(): array '1 + "blabla"', ], [ - 'array(1, 2, 3)', + 'array{1, 2, 3}', '[1, 2, 3] + [4, 5, 6]', ], [ @@ -2990,7 +2990,7 @@ public function dataBinaryOperations(): array '$arrToPush2', ], [ - 'array(0 => \'lorem\', 1 => 5, \'foo\' => stdClass, 2 => \'test\')', + 'array{0: \'lorem\', 1: 5, foo: stdClass, 2: \'test\'}', '$arrToUnshift', ], [ @@ -2998,11 +2998,11 @@ public function dataBinaryOperations(): array '$arrToUnshift2', ], [ - 'array(0 => \'lorem\', 1 => stdClass, 2 => 1, 3 => 1, 4 => 1, ?5 => 2|3, ?6 => 3)', + 'array{0: \'lorem\', 1: stdClass, 2: 1, 3: 1, 4: 1, 5?: 2|3, 6?: 3}', '$unshiftedConditionalArray', ], [ - 'array(\'dirname\' => string, \'basename\' => string, \'filename\' => string, ?\'extension\' => string)', + 'array{dirname: string, basename: string, filename: string, extension?: string}', 'pathinfo($string)', ], [ @@ -3106,11 +3106,11 @@ public function dataBinaryOperations(): array 'in_array(\'baz\', [\'foo\', \'bar\'], true)', ], [ - 'array(2, 3)', + 'array{2, 3}', '$arrToShift', ], [ - 'array(1, 2)', + 'array{1, 2}', '$arrToPop', ], [ @@ -3158,7 +3158,7 @@ public function dataBinaryOperations(): array "sprintf('%s %s', 'foo', 'bar')", ], [ - 'array()|array(0 => \'password\'|\'username\', ?1 => \'password\')', + 'array{}|array{0: \'password\'|\'username\', 1?: \'password\'}', '$coalesceArray', ], [ @@ -3315,7 +3315,7 @@ public function dataLiteralArrays(): array '$integers[0] >= $integers[1] - 1', ], [ - 'array(\'foo\' => array(\'foo\' => array(\'foo\' => \'bar\')), \'bar\' => array(), \'baz\' => array(\'lorem\' => array()))', + 'array{foo: array{foo: array{foo: \'bar\'}}, bar: array{}, baz: array{lorem: array{}}}', '$nestedArray', ], [ @@ -4615,7 +4615,7 @@ public function dataArrayFunctions(): array '$integers[0]', ], [ - 'array(string, string, string)', + 'array{string, string, string}', '$mappedStrings', ], [ @@ -4667,7 +4667,7 @@ public function dataArrayFunctions(): array 'array_combine($array, $array2)', ], [ - 'array(1 => 2)', + 'array{1: 2}', 'array_combine([1], [2])', ], [ @@ -4675,7 +4675,7 @@ public function dataArrayFunctions(): array 'array_combine([1, 2], [3])', ], [ - 'array(\'a\' => \'d\', \'b\' => \'e\', \'c\' => \'f\')', + 'array{a: \'d\', b: \'e\', c: \'f\'}', 'array_combine([\'a\', \'b\', \'c\'], [\'d\', \'e\', \'f\'])', ], [ @@ -4767,15 +4767,15 @@ public function dataArrayFunctions(): array 'array_uintersect($integers, [])', ], [ - 'array(1, 1, 1, 1, 1)', + 'array{1, 1, 1, 1, 1}', '$filledIntegers', ], [ - 'array()', + 'array{}', '$emptyFilled', ], [ - 'array(1)', + 'array{1}', '$filledIntegersWithKeys', ], [ @@ -4799,15 +4799,15 @@ public function dataArrayFunctions(): array '$filledByPositiveRange', ], [ - 'array(1, 2)', + 'array{1, 2}', 'array_keys($integerKeys)', ], [ - 'array(\'foo\', \'bar\')', + 'array{\'foo\', \'bar\'}', 'array_keys($stringKeys)', ], [ - 'array(\'foo\', 1)', + 'array{\'foo\', 1}', 'array_keys($stringOrIntegerKeys)', ], [ @@ -4815,7 +4815,7 @@ public function dataArrayFunctions(): array 'array_keys($generalStringKeys)', ], [ - 'array(\'foo\', stdClass)', + 'array{\'foo\', stdClass}', 'array_values($integerKeys)', ], [ @@ -4859,7 +4859,7 @@ public function dataArrayFunctions(): array '$mergedInts', ], [ - 'array(5 => \'banana\', 6 => \'banana\', 7 => \'banana\', 8 => \'banana\', 9 => \'banana\', 10 => \'banana\')', + 'array{5: \'banana\', 6: \'banana\', 7: \'banana\', 8: \'banana\', 9: \'banana\', 10: \'banana\'}', 'array_fill(5, 6, \'banana\')', ], [ @@ -4867,7 +4867,7 @@ public function dataArrayFunctions(): array 'array_fill(0, 101, \'apple\')', ], [ - 'array(-2 => \'pear\', 0 => \'pear\', 1 => \'pear\', 2 => \'pear\')', + 'array{-2: \'pear\', 0: \'pear\', 1: \'pear\', 2: \'pear\'}', 'array_fill(-2, 4, \'pear\')', ], [ @@ -4883,7 +4883,7 @@ public function dataArrayFunctions(): array 'array_fill_keys($generalStringKeys, new \stdClass())', ], [ - 'array(\'foo\' => \'banana\', 5 => \'banana\', 10 => \'banana\', \'bar\' => \'banana\')', + 'array{foo: \'banana\', 5: \'banana\', 10: \'banana\', bar: \'banana\'}', 'array_fill_keys([\'foo\', 5, 10, \'bar\'], \'banana\')', ], [ @@ -4903,11 +4903,11 @@ public function dataArrayFunctions(): array '$unknownArray', ], [ - 'array(\'foo\' => \'banana\', \'bar\' => \'banana\', ?\'baz\' => \'banana\', ?\'lorem\' => \'banana\')', + 'array{foo: \'banana\', bar: \'banana\', baz?: \'banana\', lorem?: \'banana\'}', 'array_fill_keys($conditionalArray, \'banana\')', ], [ - 'array(\'foo\' => stdClass, \'bar\' => stdClass, ?\'baz\' => stdClass, ?\'lorem\' => stdClass)', + 'array{foo: stdClass, bar: stdClass, baz?: stdClass, lorem?: stdClass}', 'array_map(function (): \stdClass {}, $conditionalKeysArray)', ], [ @@ -4943,11 +4943,11 @@ public function dataArrayFunctions(): array 'array_shift([])', ], [ - 'array(null, \'\', 1)', + 'array{null, \'\', 1}', '$constantArrayWithFalseyValues', ], [ - 'array(2 => 1)', + 'array{2: 1}', '$constantTruthyValues', ], [ @@ -4955,7 +4955,7 @@ public function dataArrayFunctions(): array '$falsey', ], [ - 'array()', + 'array{}', 'array_filter($falsey)', ], [ @@ -4967,11 +4967,11 @@ public function dataArrayFunctions(): array 'array_filter($withFalsey)', ], [ - 'array(\'a\' => 1)', + 'array{a: 1}', 'array_filter($union)', ], [ - 'array(?0 => true, ?1 => int|int<1, max>)', + 'array{0?: true, 1?: int|int<1, max>}', 'array_filter($withPossiblyFalsey)', ], [ @@ -5159,55 +5159,55 @@ public function dataArrayFunctions(): array 'array_slice($unknownArray, -2, 1, true)', ], [ - 'array(0 => bool, 1 => int, 2 => \'\', \'a\' => 0)', + 'array{0: bool, 1: int, 2: \'\', a: 0}', 'array_slice($withPossiblyFalsey, 0)', ], [ - 'array(0 => int, 1 => \'\', \'a\' => 0)', + 'array{0: int, 1: \'\', a: 0}', 'array_slice($withPossiblyFalsey, 1)', ], [ - 'array(1 => int, 2 => \'\', \'a\' => 0)', + 'array{1: int, 2: \'\', a: 0}', 'array_slice($withPossiblyFalsey, 1, null, true)', ], [ - 'array(0 => \'\', \'a\' => 0)', + 'array{0: \'\', a: 0}', 'array_slice($withPossiblyFalsey, 2, 3)', ], [ - 'array(2 => \'\', \'a\' => 0)', + 'array{2: \'\', a: 0}', 'array_slice($withPossiblyFalsey, 2, 3, true)', ], [ - 'array(int, \'\')', + 'array{int, \'\'}', 'array_slice($withPossiblyFalsey, 1, -1)', ], [ - 'array(1 => int, 2 => \'\')', + 'array{1: int, 2: \'\'}', 'array_slice($withPossiblyFalsey, 1, -1, true)', ], [ - 'array(0 => \'\', \'a\' => 0)', + 'array{0: \'\', a: 0}', 'array_slice($withPossiblyFalsey, -2, null)', ], [ - 'array(2 => \'\', \'a\' => 0)', + 'array{2: \'\', a: 0}', 'array_slice($withPossiblyFalsey, -2, null, true)', ], [ - 'array(\'baz\' => \'qux\')|array(0 => \'\', \'a\' => 0)', + 'array{0: \'\', a: 0}|array{baz: \'qux\'}', 'array_slice($unionArrays, 1)', ], [ - 'array(\'a\' => 0)|array(\'baz\' => \'qux\')', + 'array{a: 0}|array{baz: \'qux\'}', 'array_slice($unionArrays, -1, null, true)', ], [ - 'array(0 => \'foo\', 1 => \'bar\', \'baz\' => \'qux\', 2 => \'quux\', \'quuz\' => \'corge\', 3 => \'grault\')', + 'array{0: \'foo\', 1: \'bar\', baz: \'qux\', 2: \'quux\', quuz: \'corge\', 3: \'grault\'}', '$slicedOffset', ], [ - 'array(4 => \'foo\', 1 => \'bar\', \'baz\' => \'qux\', 0 => \'quux\', \'quuz\' => \'corge\', 5 => \'grault\')', + 'array{4: \'foo\', 1: \'bar\', baz: \'qux\', 0: \'quux\', quuz: \'corge\', 5: \'grault\'}', '$slicedOffsetWithKeys', ], [ @@ -5530,11 +5530,11 @@ public function dataFunctions(): array '$mbOrdWithUnknownEncoding', ], [ - 'array(\'sec\' => int, \'usec\' => int, \'minuteswest\' => int, \'dsttime\' => int)', + 'array{sec: int, usec: int, minuteswest: int, dsttime: int}', '$gettimeofdayArrayWithoutArg', ], [ - 'array(\'sec\' => int, \'usec\' => int, \'minuteswest\' => int, \'dsttime\' => int)', + 'array{sec: int, usec: int, minuteswest: int, dsttime: int}', '$gettimeofdayArray', ], [ @@ -5542,11 +5542,11 @@ public function dataFunctions(): array '$gettimeofdayFloat', ], [ - 'array(\'sec\' => int, \'usec\' => int, \'minuteswest\' => int, \'dsttime\' => int)|float', + 'array{sec: int, usec: int, minuteswest: int, dsttime: int}|float', '$gettimeofdayDefault', ], [ - '(array(\'sec\' => int, \'usec\' => int, \'minuteswest\' => int, \'dsttime\' => int)|float)', + '(array{sec: int, usec: int, minuteswest: int, dsttime: int}|float)', '$gettimeofdayBenevolent', ], [ @@ -5554,7 +5554,7 @@ public function dataFunctions(): array '$strSplitConstantStringWithoutDefinedParameters', ], [ - "array('a', 'b', 'c', 'd', 'e', 'f')", + 'array{\'a\', \'b\', \'c\', \'d\', \'e\', \'f\'}', '$strSplitConstantStringWithoutDefinedSplitLength', ], [ @@ -5562,11 +5562,11 @@ public function dataFunctions(): array '$strSplitStringWithoutDefinedSplitLength', ], [ - "array('a', 'b', 'c', 'd', 'e', 'f')", + 'array{\'a\', \'b\', \'c\', \'d\', \'e\', \'f\'}', '$strSplitConstantStringWithOneSplitLength', ], [ - "array('abcdef')", + 'array{\'abcdef\'}', '$strSplitConstantStringWithGreaterSplitLengthThanStringLength', ], [ @@ -5591,15 +5591,15 @@ public function dataFunctions(): array '$parseUrlWithoutParameters', ], [ - "array('scheme' => 'http', 'host' => 'abc.def')", + 'array{scheme: \'http\', host: \'abc.def\'}', '$parseUrlConstantUrlWithoutComponent1', ], [ - "array('scheme' => 'http', 'host' => 'def.abc')", + 'array{scheme: \'http\', host: \'def.abc\'}', '$parseUrlConstantUrlWithoutComponent2', ], [ - "array(?'scheme' => string, ?'host' => string, ?'port' => int, ?'user' => string, ?'pass' => string, ?'path' => string, ?'query' => string, ?'fragment' => string)|false", + 'array{scheme?: string, host?: string, port?: int, user?: string, pass?: string, path?: string, query?: string, fragment?: string}|false', '$parseUrlConstantUrlUnknownComponent', ], [ @@ -5623,11 +5623,11 @@ public function dataFunctions(): array '$parseUrlStringUrlWithComponentPort', ], [ - "array(?'scheme' => string, ?'host' => string, ?'port' => int, ?'user' => string, ?'pass' => string, ?'path' => string, ?'query' => string, ?'fragment' => string)|false", + 'array{scheme?: string, host?: string, port?: int, user?: string, pass?: string, path?: string, query?: string, fragment?: string}|false', '$parseUrlStringUrlWithoutComponent', ], [ - "array('path' => 'abc.def')", + 'array{path: \'abc.def\'}', "parse_url('abc.def')", ], [ @@ -5639,15 +5639,15 @@ public function dataFunctions(): array "parse_url('http://abc.def', PHP_URL_SCHEME)", ], [ - 'array(0 => int, 1 => int, 2 => int, 3 => int, 4 => int, 5 => int, 6 => int, 7 => int, 8 => int, 9 => int, 10 => int, 11 => int, 12 => int, \'dev\' => int, \'ino\' => int, \'mode\' => int, \'nlink\' => int, \'uid\' => int, \'gid\' => int, \'rdev\' => int, \'size\' => int, \'atime\' => int, \'mtime\' => int, \'ctime\' => int, \'blksize\' => int, \'blocks\' => int)|false', + 'array{0: int, 1: int, 2: int, 3: int, 4: int, 5: int, 6: int, 7: int, 8: int, 9: int, 10: int, 11: int, 12: int, dev: int, ino: int, mode: int, nlink: int, uid: int, gid: int, rdev: int, size: int, atime: int, mtime: int, ctime: int, blksize: int, blocks: int}|false', '$stat', ], [ - 'array(0 => int, 1 => int, 2 => int, 3 => int, 4 => int, 5 => int, 6 => int, 7 => int, 8 => int, 9 => int, 10 => int, 11 => int, 12 => int, \'dev\' => int, \'ino\' => int, \'mode\' => int, \'nlink\' => int, \'uid\' => int, \'gid\' => int, \'rdev\' => int, \'size\' => int, \'atime\' => int, \'mtime\' => int, \'ctime\' => int, \'blksize\' => int, \'blocks\' => int)|false', + 'array{0: int, 1: int, 2: int, 3: int, 4: int, 5: int, 6: int, 7: int, 8: int, 9: int, 10: int, 11: int, 12: int, dev: int, ino: int, mode: int, nlink: int, uid: int, gid: int, rdev: int, size: int, atime: int, mtime: int, ctime: int, blksize: int, blocks: int}|false', '$lstat', ], [ - 'array(0 => int, 1 => int, 2 => int, 3 => int, 4 => int, 5 => int, 6 => int, 7 => int, 8 => int, 9 => int, 10 => int, 11 => int, 12 => int, \'dev\' => int, \'ino\' => int, \'mode\' => int, \'nlink\' => int, \'uid\' => int, \'gid\' => int, \'rdev\' => int, \'size\' => int, \'atime\' => int, \'mtime\' => int, \'ctime\' => int, \'blksize\' => int, \'blocks\' => int)|false', + 'array{0: int, 1: int, 2: int, 3: int, 4: int, 5: int, 6: int, 7: int, 8: int, 9: int, 10: int, 11: int, 12: int, dev: int, ino: int, mode: int, nlink: int, uid: int, gid: int, rdev: int, size: int, atime: int, mtime: int, ctime: int, blksize: int, blocks: int}|false', '$fstat', ], [ @@ -5786,7 +5786,7 @@ public function dataDioFunctions(): array { return [ [ - 'array(\'device\' => int, \'inode\' => int, \'mode\' => int, \'nlink\' => int, \'uid\' => int, \'gid\' => int, \'device_type\' => int, \'size\' => int, \'blocksize\' => int, \'blocks\' => int, \'atime\' => int, \'mtime\' => int, \'ctime\' => int)|null', + 'array{device: int, inode: int, mode: int, nlink: int, uid: int, gid: int, device_type: int, size: int, blocksize: int, blocks: int, atime: int, mtime: int, ctime: int}|null', '$stat', ], ]; @@ -5816,7 +5816,7 @@ public function dataSsh2Functions(): array { return [ [ - 'array(0 => int, 1 => int, 2 => int, 3 => int, 4 => int, 5 => int, 6 => int, 7 => int, 8 => int, 9 => int, 10 => int, 11 => int, 12 => int, \'dev\' => int, \'ino\' => int, \'mode\' => int, \'nlink\' => int, \'uid\' => int, \'gid\' => int, \'rdev\' => int, \'size\' => int, \'atime\' => int, \'mtime\' => int, \'ctime\' => int, \'blksize\' => int, \'blocks\' => int)|false', + 'array{0: int, 1: int, 2: int, 3: int, 4: int, 5: int, 6: int, 7: int, 8: int, 9: int, 10: int, 11: int, 12: int, dev: int, ino: int, mode: int, nlink: int, uid: int, gid: int, rdev: int, size: int, atime: int, mtime: int, ctime: int, blksize: int, blocks: int}|false', '$ssh2SftpStat', ], ]; @@ -5843,19 +5843,19 @@ public function dataRangeFunction(): array { return [ [ - 'array(2, 3, 4, 5)', + 'array{2, 3, 4, 5}', 'range(2, 5)', ], [ - 'array(2, 4)', + 'array{2, 4}', 'range(2, 5, 2)', ], [ - 'array(2.0, 3.0, 4.0, 5.0)', + 'array{2.0, 3.0, 4.0, 5.0}', 'range(2, 5, 1.0)', ], [ - 'array(2.1, 3.1, 4.1)', + 'array{2.1, 3.1, 4.1}', 'range(2.1, 5)', ], [ @@ -5875,15 +5875,15 @@ public function dataRangeFunction(): array 'range($integer, $mixed)', ], [ - 'array(0 => 1, ?1 => 2)', + 'array{0: 1, 1?: 2}', 'range(1, doFoo() ? 1 : 2)', ], [ - 'array(0 => -1|1, ?1 => 0|2, ?2 => 1, ?3 => 2)', + 'array{0: -1|1, 1?: 0|2, 2?: 1, 3?: 2}', 'range(doFoo() ? -1 : 1, doFoo() ? 1 : 2)', ], [ - 'array(3, 2, 1, 0, -1)', + 'array{3, 2, 1, 0, -1}', 'range(3, -1)', ], [ @@ -7018,7 +7018,7 @@ public function dataResolveStatic(): array '\ResolveStatic\Bar::create()', ], [ - 'array(\'foo\' => ResolveStatic\Bar)', + 'array{foo: ResolveStatic\\Bar}', '$bar->returnConstantArray()', ], [ @@ -7608,15 +7608,15 @@ public function dataReplaceFunctions(): array '$anotherExpectedString', ], [ - 'array(\'a\' => string, \'b\' => string)', + 'array{a: string, b: string}', '$expectedArray', ], [ - 'array(\'a\' => string, \'b\' => string)|null', + 'array{a: string, b: string}|null', '$expectedArray2', ], [ - 'array(\'a\' => string, \'b\' => string)|null', + 'array{a: string, b: string}|null', '$anotherExpectedArray', ], [ @@ -7636,7 +7636,7 @@ public function dataReplaceFunctions(): array '$anotherExpectedArrayOrString', ], [ - 'array(\'a\' => string, \'b\' => string)|null', + 'array{a: string, b: string}|null', 'preg_replace_callback_array($callbacks, $array)', ], [ @@ -8193,7 +8193,7 @@ public function dataPassedByReference(): array { return [ [ - 'array(1, 2, 3)', + 'array{1, 2, 3}', '$arr', ], [ @@ -8275,7 +8275,7 @@ public function dataArrayKeysInBranches(): array { return [ [ - 'array(\'i\' => int, \'j\' => int, \'k\' => int, \'key\' => DateTimeImmutable, \'l\' => 1, \'m\' => 5, ?\'n\' => \'str\')', + 'array{i: int, j: int, k: int, key: DateTimeImmutable, l: 1, m: 5, n?: \'str\'}', '$array', ], [ @@ -8287,7 +8287,7 @@ public function dataArrayKeysInBranches(): array '$generalArray[\'key\']', ], [ - 'array(0 => \'foo\', 1 => \'bar\', ?2 => \'baz\')', + 'array{0: \'foo\', 1: \'bar\', 2?: \'baz\'}', '$arrayAppendedInIf', ], [ @@ -8680,15 +8680,15 @@ public function dataIsset(): array '$array[\'b\']', ], [ - 'array(\'a\' => 1|2|3, \'b\' => 2|3, ?\'c\' => 4)', + 'array{a: 1|2|3, b: 2|3, c?: 4}', '$array', ], [ - 'array(\'a\' => 1|2|3, \'b\' => 2|3|null, ?\'c\' => 4)', + 'array{a: 1|2|3, b: 2|3|null, c?: 4}', '$arrayCopy', ], [ - 'array(\'a\' => 1|2|3, ?\'c\' => 4)', + 'array{a: 1|2|3, c?: 4}', '$anotherArrayCopy', ], [ @@ -8764,7 +8764,7 @@ public function dataPropertyArrayAssignment(): array "'start'", ], [ - 'array()', + 'array{}', '$this->property', "'emptyArray'", ], @@ -8774,7 +8774,7 @@ public function dataPropertyArrayAssignment(): array "'emptyArray'", ], [ - 'array(\'foo\' => 1)', + 'array{foo: 1}', '$this->property', "'afterAssignment'", ], @@ -9055,11 +9055,11 @@ public function dataPhp73Functions(): array 'array_key_last($anotherLiteralArray)', ], [ - 'array(int, int)', + 'array{int, int}', '$hrtime1', ], [ - 'array(int, int)', + 'array{int, int}', '$hrtime2', ], [ @@ -9067,7 +9067,7 @@ public function dataPhp73Functions(): array '$hrtime3', ], [ - 'array(int, int)|float|int', + 'array{int, int}|float|int', '$hrtime4', ], ]; @@ -9101,7 +9101,7 @@ public function dataPhp74Functions(): array '$mbStrSplitConstantStringWithoutDefinedParameters', ], [ - "array('a', 'b', 'c', 'd', 'e', 'f')", + 'array{\'a\', \'b\', \'c\', \'d\', \'e\', \'f\'}', '$mbStrSplitConstantStringWithoutDefinedSplitLength', ], [ @@ -9109,11 +9109,11 @@ public function dataPhp74Functions(): array '$mbStrSplitStringWithoutDefinedSplitLength', ], [ - "array('a', 'b', 'c', 'd', 'e', 'f')", + 'array{\'a\', \'b\', \'c\', \'d\', \'e\', \'f\'}', '$mbStrSplitConstantStringWithOneSplitLength', ], [ - "array('abcdef')", + 'array{\'abcdef\'}', '$mbStrSplitConstantStringWithGreaterSplitLengthThanStringLength', ], [ @@ -9133,7 +9133,7 @@ public function dataPhp74Functions(): array '$mbStrSplitConstantStringWithVariableStringAndVariableSplitLength', ], [ - "array('a', 'b', 'c', 'd', 'e', 'f')", + "array{'a', 'b', 'c', 'd', 'e', 'f'}", '$mbStrSplitConstantStringWithOneSplitLengthAndValidEncoding', ], [ @@ -9145,7 +9145,7 @@ public function dataPhp74Functions(): array '$mbStrSplitConstantStringWithOneSplitLengthAndVariableEncoding', ], [ - "array('abcdef')", + "array{'abcdef'}", '$mbStrSplitConstantStringWithGreaterSplitLengthThanStringLengthAndValidEncoding', ], [ @@ -9320,7 +9320,7 @@ public function dataGeneralizeScope(): array { return [ [ - "array int, 'loadCount' => int, 'removeCount' => int, 'saveCount' => int)>>", + 'array>', '$statistics', ], ]; @@ -9347,7 +9347,7 @@ public function dataGeneralizeScopeRecursiveType(): array { return [ [ - 'array()|array(\'foo\' => array)', + 'array{}|array{foo: array}', '$data', ], ]; @@ -9374,15 +9374,15 @@ public function dataArrayShapesInPhpDoc(): array { return [ [ - 'array(0 => string, 1 => ArrayShapesInPhpDoc\Foo, \'foo\' => ArrayShapesInPhpDoc\Bar, 2 => ArrayShapesInPhpDoc\Baz)', + 'array{0: string, 1: ArrayShapesInPhpDoc\\Foo, foo: ArrayShapesInPhpDoc\\Bar, 2: ArrayShapesInPhpDoc\\Baz}', '$one', ], [ - 'array(0 => string, ?1 => ArrayShapesInPhpDoc\Foo, ?\'foo\' => ArrayShapesInPhpDoc\Bar)', + 'array{0: string, 1?: ArrayShapesInPhpDoc\\Foo, foo?: ArrayShapesInPhpDoc\\Bar}', '$two', ], [ - 'array(?0 => string, ?1 => ArrayShapesInPhpDoc\Foo, ?\'foo\' => ArrayShapesInPhpDoc\Bar)', + 'array{0?: string, 1?: ArrayShapesInPhpDoc\\Foo, foo?: ArrayShapesInPhpDoc\\Bar}', '$three', ], ]; @@ -9510,7 +9510,7 @@ public function dataArrowFunctions(): array '$x()', ], [ - 'array(\'a\' => 1, \'b\' => 2)', + 'array{a: 1, b: 2}', '$y()', ], ]; @@ -9598,11 +9598,11 @@ public function dataCoalesceAssign(): array '$arrayWithMaybeFoo[\'foo\'] ??= \'bar\'', ], [ - 'array(\'foo\' => \'foo\')', + 'array{foo: \'foo\'}', '$arrayAfterAssignment', ], [ - 'array(\'foo\' => \'foo\')', + 'array{foo: \'foo\'}', '$arrayWithFooAfterAssignment', ], [ @@ -9648,7 +9648,7 @@ public function dataArraySpread(): array '$integersTwo', ], [ - 'array(1, 2, 3, 4, 5, 6, 7)', + 'array{1, 2, 3, 4, 5, 6, 7}', '$integersThree', ], [ @@ -9660,11 +9660,11 @@ public function dataArraySpread(): array '$integersFive', ], [ - 'array(1, 2, 3, 4, 5, 6, 7)', + 'array{1, 2, 3, 4, 5, 6, 7}', '$integersSix', ], [ - 'array(1, 2, 3, 4, 5, 6, 7)', + 'array{1, 2, 3, 4, 5, 6, 7}', '$integersSeven', ], ]; diff --git a/tests/PHPStan/Analyser/ScopeTest.php b/tests/PHPStan/Analyser/ScopeTest.php index c6509673e0..7c016efeb3 100644 --- a/tests/PHPStan/Analyser/ScopeTest.php +++ b/tests/PHPStan/Analyser/ScopeTest.php @@ -106,7 +106,7 @@ public function dataGeneralize(): array ], [ new ConstantIntegerType(1), ]), - 'array(\'a\' => 1)', + 'array{a: 1}', ], [ new ConstantArrayType([ @@ -123,7 +123,7 @@ public function dataGeneralize(): array new ConstantIntegerType(2), new ConstantIntegerType(1), ]), - 'array(\'a\' => int, \'b\' => 1)', + 'array{a: int, b: 1}', ], [ new ConstantArrayType([ diff --git a/tests/PHPStan/Analyser/TypeSpecifierTest.php b/tests/PHPStan/Analyser/TypeSpecifierTest.php index fce28bca4f..a9aae5e04e 100644 --- a/tests/PHPStan/Analyser/TypeSpecifierTest.php +++ b/tests/PHPStan/Analyser/TypeSpecifierTest.php @@ -30,7 +30,7 @@ class TypeSpecifierTest extends \PHPStan\Testing\PHPStanTestCase { - private const FALSEY_TYPE_DESCRIPTION = '0|0.0|\'\'|\'0\'|array()|false|null'; + private const FALSEY_TYPE_DESCRIPTION = '0|0.0|\'\'|\'0\'|array{}|false|null'; private const TRUTHY_TYPE_DESCRIPTION = 'mixed~' . self::FALSEY_TYPE_DESCRIPTION; private const SURE_NOT_FALSEY = '~' . self::FALSEY_TYPE_DESCRIPTION; private const SURE_NOT_TRUTHY = '~' . self::TRUTHY_TYPE_DESCRIPTION; @@ -533,7 +533,7 @@ public function dataCondition(): array [ new Expr\BooleanNot(new Expr\Empty_(new Variable('stringOrNull'))), [ - '$stringOrNull' => '~0|0.0|\'\'|\'0\'|array()|false|null', + '$stringOrNull' => '~0|0.0|\'\'|\'0\'|array{}|false|null', ], [], ], @@ -552,13 +552,13 @@ public function dataCondition(): array new Expr\Empty_(new Variable('array')), [], [ - '$array' => '~0|0.0|\'\'|\'0\'|array()|false|null', + '$array' => '~0|0.0|\'\'|\'0\'|array{}|false|null', ], ], [ new BooleanNot(new Expr\Empty_(new Variable('array'))), [ - '$array' => '~0|0.0|\'\'|\'0\'|array()|false|null', + '$array' => '~0|0.0|\'\'|\'0\'|array{}|false|null', ], [], ], diff --git a/tests/PHPStan/Analyser/data/array-shapes-keys-strings.php b/tests/PHPStan/Analyser/data/array-shapes-keys-strings.php index 8d515c924b..c757be702d 100644 --- a/tests/PHPStan/Analyser/data/array-shapes-keys-strings.php +++ b/tests/PHPStan/Analyser/data/array-shapes-keys-strings.php @@ -17,8 +17,8 @@ class Foo */ public function doFoo(array $slash, array $dollar): void { - assertType("array('namespace/key' => string)", $slash); - assertType('array string)>', $dollar); + assertType('array{namespace/key: string}', $slash); + assertType('array', $dollar); } } diff --git a/tests/PHPStan/Analyser/data/array_map_multiple.php b/tests/PHPStan/Analyser/data/array_map_multiple.php index 381bccec47..ce73048a46 100644 --- a/tests/PHPStan/Analyser/data/array_map_multiple.php +++ b/tests/PHPStan/Analyser/data/array_map_multiple.php @@ -24,13 +24,13 @@ public function doFoo(int $i, string $s): void */ public function arrayMapNull(array $array, array $other): void { - assertType('array()', array_map(null, [])); - assertType('array(\'foo\' => true)', array_map(null, ['foo' => true])); - assertType('non-empty-array', array_map(null, [1, 2, 3], [4, 5, 6])); + assertType('array{}', array_map(null, [])); + assertType('array{foo: true}', array_map(null, ['foo' => true])); + assertType('non-empty-array', array_map(null, [1, 2, 3], [4, 5, 6])); assertType('non-empty-array', array_map(null, $array)); - assertType('non-empty-array', array_map(null, $array, $array)); - assertType('non-empty-array', array_map(null, $array, $other)); + assertType('non-empty-array', array_map(null, $array, $array)); + assertType('non-empty-array', array_map(null, $array, $other)); } } diff --git a/tests/PHPStan/Analyser/data/arrow-function-types.php b/tests/PHPStan/Analyser/data/arrow-function-types.php index 3ea7bf5f07..77cbe12cf8 100644 --- a/tests/PHPStan/Analyser/data/arrow-function-types.php +++ b/tests/PHPStan/Analyser/data/arrow-function-types.php @@ -12,33 +12,33 @@ class Foo public function doFoo(): void { - array_map(fn(array $a): array => assertType('array(\'foo\' => string, \'bar\' => int)', $a), $this->arrayShapes); + array_map(fn(array $a): array => assertType('array{foo: string, bar: int}', $a), $this->arrayShapes); $a = array_map(fn(array $a) => $a, $this->arrayShapes); - assertType('array string, \'bar\' => int)>', $a); + assertType('array', $a); - array_map(fn($b) => assertType('array(\'foo\' => string, \'bar\' => int)', $b), $this->arrayShapes); + array_map(fn($b) => assertType('array{foo: string, bar: int}', $b), $this->arrayShapes); $b = array_map(fn($b) => $b['foo'], $this->arrayShapes); assertType('array', $b); } public function doBar(): void { - usort($this->arrayShapes, fn(array $a, array $b): int => assertType('array(\'foo\' => string, \'bar\' => int)', $a)); + usort($this->arrayShapes, fn(array $a, array $b): int => assertType('array{foo: string, bar: int}', $a)); } public function doBar2(): void { - usort($this->arrayShapes, fn (array $a, array $b): int => assertType('array(\'foo\' => string, \'bar\' => int)', $b)); + usort($this->arrayShapes, fn (array $a, array $b): int => assertType('array{foo: string, bar: int}', $b)); } public function doBaz(): void { - usort($this->arrayShapes, fn ($a, $b): int => assertType('array(\'foo\' => string, \'bar\' => int)', $a)); + usort($this->arrayShapes, fn ($a, $b): int => assertType('array{foo: string, bar: int}', $a)); } public function doBaz2(): void { - usort($this->arrayShapes, fn ($a, $b): int => assertType('array(\'foo\' => string, \'bar\' => int)', $b)); + usort($this->arrayShapes, fn ($a, $b): int => assertType('array{foo: string, bar: int}', $b)); } } diff --git a/tests/PHPStan/Analyser/data/assign-nested-arrays.php b/tests/PHPStan/Analyser/data/assign-nested-arrays.php index 8463b19c6e..31cecaa4d1 100644 --- a/tests/PHPStan/Analyser/data/assign-nested-arrays.php +++ b/tests/PHPStan/Analyser/data/assign-nested-arrays.php @@ -14,7 +14,7 @@ public function doFoo(int $i) $array[$i]['bar'] = 1; $array[$i]['baz'] = 2; - assertType('non-empty-array 1, \'baz\' => 2)>', $array); + assertType('non-empty-array', $array); } public function doBar(int $i, int $j) @@ -27,7 +27,7 @@ public function doBar(int $i, int $j) echo $array[$i][$j]['bar']; echo $array[$i][$j]['baz']; - assertType('non-empty-array 1, \'baz\' => 2)>>', $array); + assertType('non-empty-array>', $array); } } diff --git a/tests/PHPStan/Analyser/data/bug-1283.php b/tests/PHPStan/Analyser/data/bug-1283.php index fb1e5a2e0e..058c739bb9 100644 --- a/tests/PHPStan/Analyser/data/bug-1283.php +++ b/tests/PHPStan/Analyser/data/bug-1283.php @@ -21,7 +21,7 @@ function (array $levels): void { throw new \UnexpectedValueException(sprintf('Unsupported level `%s`', $level)); } - assertType('array(0 => 1, ?1 => 3)', $allowedElements); + assertType('array{0: 1, 1?: 3}', $allowedElements); assertVariableCertainty(TrinaryLogic::createYes(), $allowedElements); } }; diff --git a/tests/PHPStan/Analyser/data/bug-1861.php b/tests/PHPStan/Analyser/data/bug-1861.php index c7bbcc29f7..4d5335a67c 100644 --- a/tests/PHPStan/Analyser/data/bug-1861.php +++ b/tests/PHPStan/Analyser/data/bug-1861.php @@ -15,7 +15,7 @@ public function isPath(): void { switch (count($this->children)) { case 0: - assertType('array()', $this->children); + assertType('array{}', $this->children); break; case 1: assertType('non-empty-array<' . self::class . '>', $this->children); diff --git a/tests/PHPStan/Analyser/data/bug-1924.php b/tests/PHPStan/Analyser/data/bug-1924.php index 048ccd3ff6..05c13dab62 100644 --- a/tests/PHPStan/Analyser/data/bug-1924.php +++ b/tests/PHPStan/Analyser/data/bug-1924.php @@ -18,7 +18,7 @@ function foo(): void 'a' => $this->getArrayOrNull(), 'b' => $this->getArrayOrNull(), ]; - assertType('array(\'a\' => array|null, \'b\' => array|null)', $arr); + assertType('array{a: array|null, b: array|null}', $arr); $cond = isset($arr['a']) && isset($arr['b']); assertType('bool', $cond); diff --git a/tests/PHPStan/Analyser/data/bug-2001.php b/tests/PHPStan/Analyser/data/bug-2001.php index 01ff5a4113..22db42086a 100644 --- a/tests/PHPStan/Analyser/data/bug-2001.php +++ b/tests/PHPStan/Analyser/data/bug-2001.php @@ -9,28 +9,28 @@ class HelloWorld public function parseUrl(string $url): string { $parsedUrl = parse_url(urldecode($url)); - assertType('array(?\'scheme\' => string, ?\'host\' => string, ?\'port\' => int, ?\'user\' => string, ?\'pass\' => string, ?\'path\' => string, ?\'query\' => string, ?\'fragment\' => string)|false', $parsedUrl); + assertType('array{scheme?: string, host?: string, port?: int, user?: string, pass?: string, path?: string, query?: string, fragment?: string}|false', $parsedUrl); if (array_key_exists('host', $parsedUrl)) { - assertType('array(?\'scheme\' => string, \'host\' => string, ?\'port\' => int, ?\'user\' => string, ?\'pass\' => string, ?\'path\' => string, ?\'query\' => string, ?\'fragment\' => string)', $parsedUrl); + assertType('array{scheme?: string, host: string, port?: int, user?: string, pass?: string, path?: string, query?: string, fragment?: string}', $parsedUrl); throw new \RuntimeException('Absolute URLs are prohibited for the redirectTo parameter.'); } - assertType('array(?\'scheme\' => string, ?\'port\' => int, ?\'user\' => string, ?\'pass\' => string, ?\'path\' => string, ?\'query\' => string, ?\'fragment\' => string)|false', $parsedUrl); + assertType('array{scheme?: string, port?: int, user?: string, pass?: string, path?: string, query?: string, fragment?: string}|false', $parsedUrl); $redirectUrl = $parsedUrl['path']; if (array_key_exists('query', $parsedUrl)) { - assertType('array(?\'scheme\' => string, ?\'port\' => int, ?\'user\' => string, ?\'pass\' => string, ?\'path\' => string, \'query\' => string, ?\'fragment\' => string)', $parsedUrl); + assertType('array{scheme?: string, port?: int, user?: string, pass?: string, path?: string, query: string, fragment?: string}', $parsedUrl); $redirectUrl .= '?' . $parsedUrl['query']; } if (array_key_exists('fragment', $parsedUrl)) { - assertType('array(?\'scheme\' => string, ?\'port\' => int, ?\'user\' => string, ?\'pass\' => string, ?\'path\' => string, ?\'query\' => string, \'fragment\' => string)', $parsedUrl); + assertType('array{scheme?: string, port?: int, user?: string, pass?: string, path?: string, query?: string, fragment: string}', $parsedUrl); $redirectUrl .= '#' . $parsedUrl['query']; } - assertType('array(?\'scheme\' => string, ?\'port\' => int, ?\'user\' => string, ?\'pass\' => string, ?\'path\' => string, ?\'query\' => string, ?\'fragment\' => string)|false', $parsedUrl); + assertType('array{scheme?: string, port?: int, user?: string, pass?: string, path?: string, query?: string, fragment?: string}|false', $parsedUrl); return $redirectUrl; } @@ -46,6 +46,6 @@ public function doFoo(int $i) $a = ['d' => $i]; } - assertType('array(\'a\' => int, ?\'b\' => int)|array(\'d\' => int)', $a); + assertType('array{a: int, b?: int}|array{d: int}', $a); } } diff --git a/tests/PHPStan/Analyser/data/bug-2232.php b/tests/PHPStan/Analyser/data/bug-2232.php index 3f1613c725..7464edf141 100644 --- a/tests/PHPStan/Analyser/data/bug-2232.php +++ b/tests/PHPStan/Analyser/data/bug-2232.php @@ -35,5 +35,5 @@ function () { $data['b5'] = "env"; } - assertType('array(\'a1\' => \'a\', \'a2\' => \'b\', \'a3\' => \'c\', \'a4\' => array(\'name\' => \'dsfs\', \'version\' => \'fdsfs\'), ?\'b1\' => \'hello\', ?\'b2\' => \'hello\', ?\'b3\' => \'hello\', ?\'b4\' => \'goodbye\', ?\'b5\' => \'env\')', $data); + assertType('array{a1: \'a\', a2: \'b\', a3: \'c\', a4: array{name: \'dsfs\', version: \'fdsfs\'}, b1?: \'hello\', b2?: \'hello\', b3?: \'hello\', b4?: \'goodbye\', b5?: \'env\'}', $data); }; diff --git a/tests/PHPStan/Analyser/data/bug-2378.php b/tests/PHPStan/Analyser/data/bug-2378.php index ab58f7a28c..d5984a6364 100644 --- a/tests/PHPStan/Analyser/data/bug-2378.php +++ b/tests/PHPStan/Analyser/data/bug-2378.php @@ -14,8 +14,8 @@ public function doFoo( float $f ): void { - assertType('array(\'a\', \'b\', \'c\', \'d\')', range('a', 'd')); - assertType('array(\'a\', \'c\', \'e\', \'g\', \'i\')', range('a', 'i', 2)); + assertType('array{\'a\', \'b\', \'c\', \'d\'}', range('a', 'd')); + assertType('array{\'a\', \'c\', \'e\', \'g\', \'i\'}', range('a', 'i', 2)); assertType('array', range($s, $s)); } diff --git a/tests/PHPStan/Analyser/data/bug-2677.php b/tests/PHPStan/Analyser/data/bug-2677.php index c191dd1f3d..22656ae4a3 100644 --- a/tests/PHPStan/Analyser/data/bug-2677.php +++ b/tests/PHPStan/Analyser/data/bug-2677.php @@ -41,7 +41,7 @@ function () O::class, P::class, ]; - assertType('array(\'Bug2677\\\\A\', \'Bug2677\\\\B\', \'Bug2677\\\\C\', \'Bug2677\\\\D\', \'Bug2677\\\\E\', \'Bug2677\\\\F\', \'Bug2677\\\\G\', \'Bug2677\\\\H\', \'Bug2677\\\\I\', \'Bug2677\\\\J\', \'Bug2677\\\\K\', \'Bug2677\\\\L\', \'Bug2677\\\\M\', \'Bug2677\\\\N\', \'Bug2677\\\\O\', \'Bug2677\\\\P\')', $classes); + assertType('array{\'Bug2677\\\\A\', \'Bug2677\\\\B\', \'Bug2677\\\\C\', \'Bug2677\\\\D\', \'Bug2677\\\\E\', \'Bug2677\\\\F\', \'Bug2677\\\\G\', \'Bug2677\\\\H\', \'Bug2677\\\\I\', \'Bug2677\\\\J\', \'Bug2677\\\\K\', \'Bug2677\\\\L\', \'Bug2677\\\\M\', \'Bug2677\\\\N\', \'Bug2677\\\\O\', \'Bug2677\\\\P\'}', $classes); foreach ($classes as $class) { assertType('\'Bug2677\\\\A\'|\'Bug2677\\\\B\'|\'Bug2677\\\\C\'|\'Bug2677\\\\D\'|\'Bug2677\\\\E\'|\'Bug2677\\\\F\'|\'Bug2677\\\\G\'|\'Bug2677\\\\H\'|\'Bug2677\\\\I\'|\'Bug2677\\\\J\'|\'Bug2677\\\\K\'|\'Bug2677\\\\L\'|\'Bug2677\\\\M\'|\'Bug2677\\\\N\'|\'Bug2677\\\\O\'|\'Bug2677\\\\P\'', $class); diff --git a/tests/PHPStan/Analyser/data/bug-2733.php b/tests/PHPStan/Analyser/data/bug-2733.php index d40acdc032..f18f5053d9 100644 --- a/tests/PHPStan/Analyser/data/bug-2733.php +++ b/tests/PHPStan/Analyser/data/bug-2733.php @@ -18,7 +18,7 @@ public function doSomething(array $data): void } } - assertType('array(\'id\' => int, \'name\' => string)', $data); + assertType('array{id: int, name: string}', $data); } } diff --git a/tests/PHPStan/Analyser/data/bug-3009.php b/tests/PHPStan/Analyser/data/bug-3009.php index de9f96ca4c..a0808efd6b 100644 --- a/tests/PHPStan/Analyser/data/bug-3009.php +++ b/tests/PHPStan/Analyser/data/bug-3009.php @@ -11,18 +11,18 @@ public function createRedirectRequest(string $redirectUri): ?string { $redirectUrlParts = parse_url($redirectUri); if (false === is_array($redirectUrlParts) || true === array_key_exists('host', $redirectUrlParts)) { - assertType('array(?\'scheme\' => string, ?\'host\' => string, ?\'port\' => int, ?\'user\' => string, ?\'pass\' => string, ?\'path\' => string, ?\'query\' => string, ?\'fragment\' => string)|false', $redirectUrlParts); + assertType('array{scheme?: string, host?: string, port?: int, user?: string, pass?: string, path?: string, query?: string, fragment?: string}|false', $redirectUrlParts); return null; } - assertType('array(?\'scheme\' => string, ?\'host\' => string, ?\'port\' => int, ?\'user\' => string, ?\'pass\' => string, ?\'path\' => string, ?\'query\' => string, ?\'fragment\' => string)', $redirectUrlParts); + assertType('array{scheme?: string, host?: string, port?: int, user?: string, pass?: string, path?: string, query?: string, fragment?: string}', $redirectUrlParts); if (true === array_key_exists('query', $redirectUrlParts)) { - assertType('array(?\'scheme\' => string, ?\'host\' => string, ?\'port\' => int, ?\'user\' => string, ?\'pass\' => string, ?\'path\' => string, \'query\' => string, ?\'fragment\' => string)', $redirectUrlParts); + assertType('array{scheme?: string, host?: string, port?: int, user?: string, pass?: string, path?: string, query: string, fragment?: string}', $redirectUrlParts); $redirectServer['QUERY_STRING'] = $redirectUrlParts['query']; } - assertType('array(?\'scheme\' => string, ?\'host\' => string, ?\'port\' => int, ?\'user\' => string, ?\'pass\' => string, ?\'path\' => string, ?\'query\' => string, ?\'fragment\' => string)', $redirectUrlParts); + assertType('array{scheme?: string, host?: string, port?: int, user?: string, pass?: string, path?: string, query?: string, fragment?: string}', $redirectUrlParts); return 'foo'; } diff --git a/tests/PHPStan/Analyser/data/bug-3269.php b/tests/PHPStan/Analyser/data/bug-3269.php index b3cfdbea68..f7f5a2bce6 100644 --- a/tests/PHPStan/Analyser/data/bug-3269.php +++ b/tests/PHPStan/Analyser/data/bug-3269.php @@ -20,10 +20,10 @@ public static function bar(array $intervalGroups): void } } - assertType('array string, \'operator\' => string, \'side\' => \'end\'|\'start\')>', $borders); + assertType('array', $borders); foreach ($borders as $border) { - assertType('array(\'version\' => string, \'operator\' => string, \'side\' => \'end\'|\'start\')', $border); + assertType('array{version: string, operator: string, side: \'end\'|\'start\'}', $border); assertType('\'end\'|\'start\'', $border['side']); } } diff --git a/tests/PHPStan/Analyser/data/bug-3276.php b/tests/PHPStan/Analyser/data/bug-3276.php index dccb79c5fc..ece368c9b1 100644 --- a/tests/PHPStan/Analyser/data/bug-3276.php +++ b/tests/PHPStan/Analyser/data/bug-3276.php @@ -13,7 +13,7 @@ class Foo public function doFoo(array $settings): void { $settings['name'] ??= 'unknown'; - assertType('array(\'name\' => string)', $settings); + assertType('array{name: string}', $settings); } /** @@ -22,7 +22,7 @@ public function doFoo(array $settings): void public function doBar(array $settings): void { $settings['name'] = 'unknown'; - assertType('array(\'name\' => \'unknown\')', $settings); + assertType('array{name: \'unknown\'}', $settings); } } diff --git a/tests/PHPStan/Analyser/data/bug-3351.php b/tests/PHPStan/Analyser/data/bug-3351.php index 83198c9c1d..2de71cddc2 100644 --- a/tests/PHPStan/Analyser/data/bug-3351.php +++ b/tests/PHPStan/Analyser/data/bug-3351.php @@ -12,7 +12,7 @@ public function sayHello(): void $c = $this->combine($a, $b); assertType('array|false', $c); - assertType('array(\'a\' => 1, \'b\' => 2, \'c\' => 3)', array_combine($a, $b)); + assertType('array{a: 1, b: 2, c: 3}', array_combine($a, $b)); } /** diff --git a/tests/PHPStan/Analyser/data/bug-3558.php b/tests/PHPStan/Analyser/data/bug-3558.php index 49dd89b06c..b473c961bb 100644 --- a/tests/PHPStan/Analyser/data/bug-3558.php +++ b/tests/PHPStan/Analyser/data/bug-3558.php @@ -14,7 +14,7 @@ function (): void { } if(count($idGroups) > 0){ - assertType('array(array(1, 2), array(1, 2), array(1, 2))', $idGroups); + assertType('array{array{1, 2}, array{1, 2}, array{1, 2}}', $idGroups); } }; @@ -28,6 +28,6 @@ function (): void { } if(count($idGroups) > 1){ - assertType('array(0 => 1, ?1 => array(1, 2), ?2 => array(1, 2), ?3 => array(1, 2))', $idGroups); + 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-3991.php b/tests/PHPStan/Analyser/data/bug-3991.php index aee3f87ac1..5ce1de9324 100644 --- a/tests/PHPStan/Analyser/data/bug-3991.php +++ b/tests/PHPStan/Analyser/data/bug-3991.php @@ -22,7 +22,7 @@ public static function email($config = null) assertType('array|stdClass|null', $config); $config = new \stdClass(); } elseif (! (is_array($config) || $config instanceof \stdClass)) { - assertNativeType('mixed~0|0.0|\'\'|\'0\'|array()|stdClass|false|null', $config); + assertNativeType('mixed~0|0.0|\'\'|\'0\'|array{}|stdClass|false|null', $config); assertType('*NEVER*', $config); } diff --git a/tests/PHPStan/Analyser/data/bug-4099.php b/tests/PHPStan/Analyser/data/bug-4099.php index 571dfb3476..2e49179969 100644 --- a/tests/PHPStan/Analyser/data/bug-4099.php +++ b/tests/PHPStan/Analyser/data/bug-4099.php @@ -13,7 +13,7 @@ class Foo */ function arrayHint(array $arr): void { - assertType('array(\'key\' => array(\'inner\' => mixed))', $arr); + assertType('array{key: array{inner: mixed}}', $arr); assertNativeType('array', $arr); if (!array_key_exists('key', $arr)) { @@ -21,21 +21,21 @@ function arrayHint(array $arr): void assertNativeType('array', $arr); throw new \Exception('no key "key" found.'); } - assertType('array(\'key\' => array(\'inner\' => mixed))', $arr); + assertType('array{key: array{inner: mixed}}', $arr); assertNativeType('array&hasOffset(\'key\')', $arr); - assertType('array(\'inner\' => mixed)', $arr['key']); + assertType('array{inner: mixed}', $arr['key']); assertNativeType('mixed', $arr['key']); if (!array_key_exists('inner', $arr['key'])) { - assertType('array(\'key\' => *NEVER*)', $arr); + assertType('array{key: *NEVER*}', $arr); //assertNativeType('array(\'key\' => mixed)', $arr); assertType('*NEVER*', $arr['key']); //assertNativeType('mixed', $arr['key']); throw new \Exception('need key.inner'); } - assertType('array(\'key\' => array(\'inner\' => mixed))', $arr); - assertNativeType('array(\'key\' => array(\'inner\' => mixed))', $arr); + assertType('array{key: array{inner: mixed}}', $arr); + assertNativeType('array{key: array{inner: mixed}}', $arr); } } diff --git a/tests/PHPStan/Analyser/data/bug-4213.php b/tests/PHPStan/Analyser/data/bug-4213.php index bc9742be9a..538bacbb79 100644 --- a/tests/PHPStan/Analyser/data/bug-4213.php +++ b/tests/PHPStan/Analyser/data/bug-4213.php @@ -37,7 +37,7 @@ public function setEnumsWithoutSplat(array $enums): void { function (): void { assertType('Bug4213\Enum', Enum::get('test')); - assertType('array(Bug4213\Enum)', array_map([Enum::class, 'get'], ['test'])); + assertType('array{Bug4213\\Enum}', array_map([Enum::class, 'get'], ['test'])); }; diff --git a/tests/PHPStan/Analyser/data/bug-4504.php b/tests/PHPStan/Analyser/data/bug-4504.php index 50ba802671..f70d3c9567 100644 --- a/tests/PHPStan/Analyser/data/bug-4504.php +++ b/tests/PHPStan/Analyser/data/bug-4504.php @@ -14,7 +14,7 @@ public function sayHello($models): void assertType('Bug4504TypeInference\A', $v); } - assertType('array()|Iterator', $models); + assertType('array{}|Iterator', $models); } } diff --git a/tests/PHPStan/Analyser/data/bug-4558.php b/tests/PHPStan/Analyser/data/bug-4558.php index 1c4aba719c..a40e33581a 100644 --- a/tests/PHPStan/Analyser/data/bug-4558.php +++ b/tests/PHPStan/Analyser/data/bug-4558.php @@ -31,7 +31,7 @@ public function sayHello(): ?DateTime // we might be out of suggested days, so load some more if (count($this->suggestions) === 0) { - assertType('array()', $this->suggestions); + assertType('array{}', $this->suggestions); assertType('0', count($this->suggestions)); $this->createSuggestions(); } diff --git a/tests/PHPStan/Analyser/data/bug-4587.php b/tests/PHPStan/Analyser/data/bug-4587.php index 995bdfc7e3..b0643a055e 100644 --- a/tests/PHPStan/Analyser/data/bug-4587.php +++ b/tests/PHPStan/Analyser/data/bug-4587.php @@ -12,11 +12,11 @@ public function a(): void $results = []; $type = array_map(static function (array $result): array { - assertType('array(\'a\' => int)', $result); + assertType('array{a: int}', $result); return $result; }, $results); - assertType('array int)>', $type); + assertType('array', $type); } public function b(): void @@ -25,13 +25,13 @@ public function b(): void $results = []; $type = array_map(static function (array $result): array { - assertType('array(\'a\' => int)', $result); + assertType('array{a: int}', $result); $result['a'] = (string) $result['a']; - assertType('array(\'a\' => numeric-string)', $result); + assertType('array{a: numeric-string}', $result); return $result; }, $results); - assertType('array numeric-string)>', $type); + assertType('array', $type); } } diff --git a/tests/PHPStan/Analyser/data/bug-4606.php b/tests/PHPStan/Analyser/data/bug-4606.php index d2d6a3ab45..1cf9cf4a32 100644 --- a/tests/PHPStan/Analyser/data/bug-4606.php +++ b/tests/PHPStan/Analyser/data/bug-4606.php @@ -11,7 +11,7 @@ */ assertType(Foo::class, $this); -assertType('array', $assigned); +assertType('array', $assigned); /** @@ -20,4 +20,4 @@ */ $foo = doFoo(); -assertType('array(stdClass, int)', $foo); +assertType('array{stdClass, int}', $foo); diff --git a/tests/PHPStan/Analyser/data/bug-4650.php b/tests/PHPStan/Analyser/data/bug-4650.php index c00352dc1e..f51b260c26 100644 --- a/tests/PHPStan/Analyser/data/bug-4650.php +++ b/tests/PHPStan/Analyser/data/bug-4650.php @@ -15,8 +15,8 @@ function doFoo(array $idx): void { assertType('non-empty-array', $idx); assertNativeType('array', $idx); - assertType('array()', []); - assertNativeType('array()', []); + assertType('array{}', []); + assertNativeType('array{}', []); assertType('false', $idx === []); assertNativeType('bool', $idx === []); diff --git a/tests/PHPStan/Analyser/data/bug-4700.php b/tests/PHPStan/Analyser/data/bug-4700.php index bf73f23a09..e50cce6f7c 100644 --- a/tests/PHPStan/Analyser/data/bug-4700.php +++ b/tests/PHPStan/Analyser/data/bug-4700.php @@ -19,10 +19,10 @@ function(array $array, int $count): void { if (isset($array['e'])) $a[] = $array['e']; if (count($a) >= $count) { assertType('1|2|3|4|5', count($a)); - assertType('array(0 => mixed~null, ?1 => mixed~null, ?2 => mixed~null, ?3 => mixed~null, ?4 => mixed~null)', $a); + assertType('array{0: mixed~null, 1?: mixed~null, 2?: mixed~null, 3?: mixed~null, 4?: mixed~null}', $a); } else { assertType('0|1|2|3|4|5', count($a)); - assertType('array()|array(0 => mixed~null, ?1 => mixed~null, ?2 => mixed~null, ?3 => mixed~null, ?4 => mixed~null)', $a); + assertType('array{}|array{0: mixed~null, 1?: mixed~null, 2?: mixed~null, 3?: mixed~null, 4?: mixed~null}', $a); } }; @@ -41,9 +41,9 @@ function(array $array, int $count): void { if (isset($array['e'])) $a[] = $array['e']; if (count($a) > $count) { assertType('2|3|4|5', count($a)); - assertType('array(0 => mixed~null, ?1 => mixed~null, ?2 => mixed~null, ?3 => mixed~null, ?4 => mixed~null)', $a); + assertType('array{0: mixed~null, 1?: mixed~null, 2?: mixed~null, 3?: mixed~null, 4?: mixed~null}', $a); } else { assertType('0|1|2|3|4|5', count($a)); - assertType('array()|array(0 => mixed~null, ?1 => mixed~null, ?2 => mixed~null, ?3 => mixed~null, ?4 => mixed~null)', $a); + assertType('array{}|array{0: mixed~null, 1?: mixed~null, 2?: mixed~null, 3?: mixed~null, 4?: mixed~null}', $a); } }; diff --git a/tests/PHPStan/Analyser/data/bug-4814.php b/tests/PHPStan/Analyser/data/bug-4814.php index 4e8607a793..2fcaf231f4 100644 --- a/tests/PHPStan/Analyser/data/bug-4814.php +++ b/tests/PHPStan/Analyser/data/bug-4814.php @@ -33,7 +33,7 @@ public function doFoo() $decodedResponseBody = json_decode($body, true, 512, JSON_THROW_ON_ERROR); } catch (\Throwable $exception) { assertType('string|null', $body); - assertType('array()', $decodedResponseBody); + assertType('array{}', $decodedResponseBody); } } diff --git a/tests/PHPStan/Analyser/data/bug-5017.php b/tests/PHPStan/Analyser/data/bug-5017.php index bb7decee09..fa1abc5b46 100644 --- a/tests/PHPStan/Analyser/data/bug-5017.php +++ b/tests/PHPStan/Analyser/data/bug-5017.php @@ -35,7 +35,7 @@ public function doBar($items) public function doBar2() { $items = [0, 1, 2, 3, 4]; - assertType('array(0, 1, 2, 3, 4)', $items); + assertType('array{0, 1, 2, 3, 4}', $items); $batch = array_splice($items, 0, 2); assertType('array<0|1|2|3|4, 0|1|2|3|4>', $items); assertType('array<0|1|2|3|4, 0|1|2|3|4>', $batch); diff --git a/tests/PHPStan/Analyser/data/bug-5219.php b/tests/PHPStan/Analyser/data/bug-5219.php index 698c122090..cf7e4c0485 100644 --- a/tests/PHPStan/Analyser/data/bug-5219.php +++ b/tests/PHPStan/Analyser/data/bug-5219.php @@ -20,6 +20,6 @@ protected function bar(string $message): void $header = sprintf('%s-%s', '', ''); assertType('\'-\'', $header); - assertType('array(\'-\' => string)', [$header => $message]); + assertType('array{-: string}', [$header => $message]); } } diff --git a/tests/PHPStan/Analyser/data/bug-5584.php b/tests/PHPStan/Analyser/data/bug-5584.php index 76632898e7..45e6efeaa3 100644 --- a/tests/PHPStan/Analyser/data/bug-5584.php +++ b/tests/PHPStan/Analyser/data/bug-5584.php @@ -19,6 +19,6 @@ public function unionSum(): void $b = ['b' => 6]; } - assertType("array()|array(?'b' => 6, ?'a' => 5)", $a + $b); + assertType('array{}|array{b?: 6, a?: 5}', $a + $b); } } diff --git a/tests/PHPStan/Analyser/data/bug-empty-array.php b/tests/PHPStan/Analyser/data/bug-empty-array.php index c50df67513..91a6b8bb00 100644 --- a/tests/PHPStan/Analyser/data/bug-empty-array.php +++ b/tests/PHPStan/Analyser/data/bug-empty-array.php @@ -14,9 +14,9 @@ public function doFoo(): void { assertType('array', $this->comments); $this->comments = []; - assertType('array()', $this->comments); + assertType('array{}', $this->comments); if ($this->comments === []) { - assertType('array()', $this->comments); + assertType('array{}', $this->comments); return; } else { assertType('*NEVER*', $this->comments); @@ -29,9 +29,9 @@ public function doBar(): void { assertType('array', $this->comments); $this->comments = []; - assertType('array()', $this->comments); + assertType('array{}', $this->comments); if ([] === $this->comments) { - assertType('array()', $this->comments); + assertType('array{}', $this->comments); return; } else { assertType('*NEVER*', $this->comments); diff --git a/tests/PHPStan/Analyser/data/bug-pr-339.php b/tests/PHPStan/Analyser/data/bug-pr-339.php index 01e3822e96..768efbc147 100644 --- a/tests/PHPStan/Analyser/data/bug-pr-339.php +++ b/tests/PHPStan/Analyser/data/bug-pr-339.php @@ -17,17 +17,17 @@ assertType('mixed', $a); assertType('mixed', $c); if ($a) { - assertType("mixed~0|0.0|''|'0'|array()|false|null", $a); + assertType("mixed~0|0.0|''|'0'|array{}|false|null", $a); assertType('mixed', $c); assertVariableCertainty(TrinaryLogic::createYes(), $a); } if ($c) { assertType('mixed', $a); - assertType("mixed~0|0.0|''|'0'|array()|false|null", $c); + assertType("mixed~0|0.0|''|'0'|array{}|false|null", $c); assertVariableCertainty(TrinaryLogic::createYes(), $c); } } else { - assertType("0|0.0|''|'0'|array()|false|null", $a); - assertType("0|0.0|''|'0'|array()|false|null", $c); + assertType("0|0.0|''|'0'|array{}|false|null", $a); + assertType("0|0.0|''|'0'|array{}|false|null", $c); } diff --git a/tests/PHPStan/Analyser/data/closure-return-type.php b/tests/PHPStan/Analyser/data/closure-return-type.php index c06880defa..f71b056a55 100644 --- a/tests/PHPStan/Analyser/data/closure-return-type.php +++ b/tests/PHPStan/Analyser/data/closure-return-type.php @@ -27,7 +27,7 @@ public function doFoo(int $i): void $f = function (): array { return ['foo' => 'bar']; }; - assertType('array(\'foo\' => \'bar\')', $f()); + assertType('array{foo: \'bar\'}', $f()); $f = function (string $s) { return $s; diff --git a/tests/PHPStan/Analyser/data/closure-types.php b/tests/PHPStan/Analyser/data/closure-types.php index a0f60a0680..72adfe762a 100644 --- a/tests/PHPStan/Analyser/data/closure-types.php +++ b/tests/PHPStan/Analyser/data/closure-types.php @@ -13,14 +13,14 @@ class Foo public function doFoo(): void { $a = array_map(function (array $a): array { - assertType('array(\'foo\' => string, \'bar\' => int)', $a); + assertType('array{foo: string, bar: int}', $a); return $a; }, $this->arrayShapes); - assertType('array string, \'bar\' => int)>', $a); + assertType('array', $a); $b = array_map(function ($b) { - assertType('array(\'foo\' => string, \'bar\' => int)', $b); + assertType('array{foo: string, bar: int}', $b); return $b['foo']; }, $this->arrayShapes); @@ -30,8 +30,8 @@ public function doFoo(): void public function doBar(): void { usort($this->arrayShapes, function (array $a, array $b): int { - assertType('array(\'foo\' => string, \'bar\' => int)', $a); - assertType('array(\'foo\' => string, \'bar\' => int)', $b); + assertType('array{foo: string, bar: int}', $a); + assertType('array{foo: string, bar: int}', $b); return 1; }); @@ -40,8 +40,8 @@ public function doBar(): void public function doBaz(): void { usort($this->arrayShapes, function ($a, $b): int { - assertType('array(\'foo\' => string, \'bar\' => int)', $a); - assertType('array(\'foo\' => string, \'bar\' => int)', $b); + assertType('array{foo: string, bar: int}', $a); + assertType('array{foo: string, bar: int}', $b); return 1; }); diff --git a/tests/PHPStan/Analyser/data/compact.php b/tests/PHPStan/Analyser/data/compact.php index 4c29db6781..b15f2f5eb4 100644 --- a/tests/PHPStan/Analyser/data/compact.php +++ b/tests/PHPStan/Analyser/data/compact.php @@ -4,7 +4,7 @@ use function PHPStan\Testing\assertType; -assertType('array(?\'bar\' => mixed)', compact(['foo' => 'bar'])); +assertType('array{bar?: mixed}', compact(['foo' => 'bar'])); function (string $dolor): void { $foo = 'bar'; @@ -12,11 +12,11 @@ function (string $dolor): void { if (rand(0, 1)) { $lorem = 'ipsum'; } - assertType('array(\'foo\' => \'bar\', \'bar\' => \'baz\')', compact('foo', ['bar'])); - assertType('array(\'foo\' => \'bar\', \'bar\' => \'baz\', ?\'lorem\' => \'ipsum\')', compact([['foo']], 'bar', 'lorem')); + assertType('array{foo: \'bar\', bar: \'baz\'}', compact('foo', ['bar'])); + assertType('array{foo: \'bar\', bar: \'baz\', lorem?: \'ipsum\'}', compact([['foo']], 'bar', 'lorem')); assertType('array', compact($dolor)); assertType('array', compact([$dolor])); - assertType('array()', compact([])); + assertType('array{}', compact([])); }; diff --git a/tests/PHPStan/Analyser/data/conditional-non-empty-array.php b/tests/PHPStan/Analyser/data/conditional-non-empty-array.php index 75afc82e17..7f3ede3f30 100644 --- a/tests/PHPStan/Analyser/data/conditional-non-empty-array.php +++ b/tests/PHPStan/Analyser/data/conditional-non-empty-array.php @@ -22,7 +22,7 @@ public function doFoo(array $a): void assertType('non-empty-array', $a); assertVariableCertainty(TrinaryLogic::createYes(), $foo); } else { - assertType('array()', $a); + assertType('array{}', $a); assertVariableCertainty(TrinaryLogic::createNo(), $foo); } } diff --git a/tests/PHPStan/Analyser/data/empty-array-shape.php b/tests/PHPStan/Analyser/data/empty-array-shape.php index 4a19bf0f59..35d3dcece9 100644 --- a/tests/PHPStan/Analyser/data/empty-array-shape.php +++ b/tests/PHPStan/Analyser/data/empty-array-shape.php @@ -10,7 +10,7 @@ class Foo /** @param array{} $array */ public function doFoo(array $array): void { - assertType('array()', $array); + assertType('array{}', $array); } } diff --git a/tests/PHPStan/Analyser/data/generics.php b/tests/PHPStan/Analyser/data/generics.php index 6de2d75b6f..b203267334 100644 --- a/tests/PHPStan/Analyser/data/generics.php +++ b/tests/PHPStan/Analyser/data/generics.php @@ -96,8 +96,8 @@ function testD($int, $float, $intFloat) assertType('float|int', d($int, $float)); assertType('DateTime|int', d($int, new \DateTime())); assertType('DateTime|float|int', d($intFloat, new \DateTime())); - assertType('array()|DateTime', d([], new \DateTime())); - assertType('array(\'blabla\' => string)|DateTime', d(['blabla' => 'barrrr'], new \DateTime())); + assertType('array{}|DateTime', d([], new \DateTime())); + assertType('array{blabla: string}|DateTime', d(['blabla' => 'barrrr'], new \DateTime())); } /** @@ -763,7 +763,7 @@ function testClasses() $factory = new Factory(new \DateTime(), new A(1)); assertType( - 'array(DateTime, PHPStan\Generics\FunctionsAssertType\A, string, PHPStan\Generics\FunctionsAssertType\A)', + 'array{DateTime, PHPStan\\Generics\\FunctionsAssertType\\A, string, PHPStan\\Generics\\FunctionsAssertType\\A}', $factory->create(new \DateTime(), '', new A(new \DateTime())) ); } @@ -1403,7 +1403,7 @@ function (\Throwable $e): void { function (): void { $array = ['a' => 1, 'b' => 2]; - assertType('array(\'a\' => int, \'b\' => int)', a($array)); + assertType('array{a: int, b: int}', a($array)); }; @@ -1539,11 +1539,11 @@ function arrayBound5(array $a): array } function (): void { - assertType('array(1 => true)', arrayBound1([1 => true])); - assertType("array('a', 'b', 'c')", arrayBound2(range('a', 'c'))); + assertType('array{1: true}', arrayBound1([1 => true])); + assertType('array{\'a\', \'b\', \'c\'}', arrayBound2(range('a', 'c'))); assertType('array', arrayBound2([1, 2, 3])); - assertType('array(true, false, true)', arrayBound3([true, false, true])); - assertType("array(array('a' => 'a'), array('b' => 'b'), array('c' => 'c'))", arrayBound4([['a' => 'a'], ['b' => 'b'], ['c' => 'c']])); + assertType('array{true, false, true}', arrayBound3([true, false, true])); + assertType('array{array{a: \'a\'}, array{b: \'b\'}, array{c: \'c\'}}', arrayBound4([['a' => 'a'], ['b' => 'b'], ['c' => 'c']])); assertType('array', arrayBound5(range('a', 'c'))); }; @@ -1558,5 +1558,5 @@ function constantArrayBound(array $a): array } function (): void { - assertType("array('string', true)", constantArrayBound(['string', true])); + assertType('array{\'string\', true}', constantArrayBound(['string', true])); }; diff --git a/tests/PHPStan/Analyser/data/minmax-arrays.php b/tests/PHPStan/Analyser/data/minmax-arrays.php index 0f9ca3aede..cffe5de439 100644 --- a/tests/PHPStan/Analyser/data/minmax-arrays.php +++ b/tests/PHPStan/Analyser/data/minmax-arrays.php @@ -101,22 +101,22 @@ function dummy3(array $ints): void function dummy4(\DateTimeInterface $dateA, ?\DateTimeInterface $dateB): void { - assertType('array(0 => DateTimeInterface, ?1 => DateTimeInterface)', array_filter([$dateA, $dateB])); + assertType('array{0: DateTimeInterface, 1?: DateTimeInterface}', array_filter([$dateA, $dateB])); assertType('DateTimeInterface', min(array_filter([$dateA, $dateB]))); assertType('DateTimeInterface', max(array_filter([$dateA, $dateB]))); - assertType('array(?0 => DateTimeInterface)', array_filter([$dateB])); + assertType('array{0?: DateTimeInterface}', array_filter([$dateB])); assertType('DateTimeInterface|false', min(array_filter([$dateB]))); assertType('DateTimeInterface|false', max(array_filter([$dateB]))); } function dummy5(int $i, int $j): void { - assertType('array(?0 => int|int<1, max>, ?1 => int|int<1, max>)', array_filter([$i, $j])); - assertType('array(1 => true)', array_filter([false, true])); + assertType('array{0?: int|int<1, max>, 1?: int|int<1, max>}', array_filter([$i, $j])); + assertType('array{1: true}', array_filter([false, true])); } function dummy6(string $s, string $t): void { - assertType('array(?0 => non-empty-string, ?1 => non-empty-string)', array_filter([$s, $t])); + assertType('array{0?: non-empty-string, 1?: non-empty-string}', array_filter([$s, $t])); } class HelloWorld diff --git a/tests/PHPStan/Analyser/data/missing-closure-native-return-typehint.php b/tests/PHPStan/Analyser/data/missing-closure-native-return-typehint.php index 43b55bae18..d516f89f23 100644 --- a/tests/PHPStan/Analyser/data/missing-closure-native-return-typehint.php +++ b/tests/PHPStan/Analyser/data/missing-closure-native-return-typehint.php @@ -43,7 +43,7 @@ public function doFoo() } })()); - \PHPStan\Testing\assertType('array(\'foo\' => \'bar\')', (function () { + \PHPStan\Testing\assertType('array{foo: \'bar\'}', (function () { $array = [ 'foo' => 'bar', ]; diff --git a/tests/PHPStan/Analyser/data/native-types.php b/tests/PHPStan/Analyser/data/native-types.php index 1327192ace..86f0ad7cd1 100644 --- a/tests/PHPStan/Analyser/data/native-types.php +++ b/tests/PHPStan/Analyser/data/native-types.php @@ -121,10 +121,10 @@ public function doCatch($foo): void */ public function doForeachArrayDestructuring(array $array) { - assertType('array', $array); + assertType('array', $array); assertNativeType('array', $array); foreach ($array as $key => [$i, $s]) { - assertType('non-empty-array', $array); + assertType('non-empty-array', $array); assertNativeType('non-empty-array', $array); assertType('string', $key); diff --git a/tests/PHPStan/Analyser/data/preg_split.php b/tests/PHPStan/Analyser/data/preg_split.php index 84a7223dc8..d7a63fe676 100644 --- a/tests/PHPStan/Analyser/data/preg_split.php +++ b/tests/PHPStan/Analyser/data/preg_split.php @@ -4,5 +4,5 @@ assertType('array|false', preg_split('/-/', '1-2-3')); assertType('array|false', preg_split('/-/', '1-2-3', -1, PREG_SPLIT_NO_EMPTY)); -assertType('array|false', preg_split('/-/', '1-2-3', -1, PREG_SPLIT_OFFSET_CAPTURE)); -assertType('array|false', preg_split('/-/', '1-2-3', -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE)); +assertType('array|false', preg_split('/-/', '1-2-3', -1, PREG_SPLIT_OFFSET_CAPTURE)); +assertType('array|false', preg_split('/-/', '1-2-3', -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE)); diff --git a/tests/PHPStan/Analyser/data/proc_get_status.php b/tests/PHPStan/Analyser/data/proc_get_status.php index 449b4fd0ff..54f63e8094 100644 --- a/tests/PHPStan/Analyser/data/proc_get_status.php +++ b/tests/PHPStan/Analyser/data/proc_get_status.php @@ -11,5 +11,5 @@ function ($r): void { return; } - assertType('array(\'command\' => string, \'pid\' => int, \'running\' => bool, \'signaled\' => bool, \'stopped\' => bool, \'exitcode\' => int, \'termsig\' => int, \'stopsig\' => int)', $status); + assertType('array{command: string, pid: int, running: bool, signaled: bool, stopped: bool, exitcode: int, termsig: int, stopsig: int}', $status); }; diff --git a/tests/PHPStan/Analyser/data/type-aliases.php b/tests/PHPStan/Analyser/data/type-aliases.php index 9ad1e39918..a6342387ab 100644 --- a/tests/PHPStan/Analyser/data/type-aliases.php +++ b/tests/PHPStan/Analyser/data/type-aliases.php @@ -181,7 +181,7 @@ class UsesTrait1 /** @param Test $a */ public function doBar($a) { - assertType('array(string, int)', $a); + assertType('array{string, int}', $a); assertType(Test::class, $this->doFoo()); } diff --git a/tests/PHPStan/Levels/data/acceptTypes-5.json b/tests/PHPStan/Levels/data/acceptTypes-5.json index 6810f8b1d2..a3eb3cd67a 100644 --- a/tests/PHPStan/Levels/data/acceptTypes-5.json +++ b/tests/PHPStan/Levels/data/acceptTypes-5.json @@ -140,27 +140,27 @@ "ignorable": true }, { - "message": "Parameter #1 $one of method Levels\\AcceptTypes\\ArrayShapes::doBar() expects array('foo' => callable(): mixed), array given.", + "message": "Parameter #1 $one of method Levels\\AcceptTypes\\ArrayShapes::doBar() expects array{foo: callable(): mixed}, array given.", "line": 579, "ignorable": true }, { - "message": "Parameter #1 $one of method Levels\\AcceptTypes\\ArrayShapes::doBar() expects array('foo' => callable(): mixed), array() given.", + "message": "Parameter #1 $one of method Levels\\AcceptTypes\\ArrayShapes::doBar() expects array{foo: callable(): mixed}, array{} given.", "line": 580, "ignorable": true }, { - "message": "Parameter #1 $one of method Levels\\AcceptTypes\\ArrayShapes::doBar() expects array('foo' => callable(): mixed), array('foo' => 1) given.", + "message": "Parameter #1 $one of method Levels\\AcceptTypes\\ArrayShapes::doBar() expects array{foo: callable(): mixed}, array{foo: 1} given.", "line": 582, "ignorable": true }, { - "message": "Parameter #1 $one of method Levels\\AcceptTypes\\ArrayShapes::doBar() expects array('foo' => callable(): mixed), array('foo' => 'nonexistent') given.", + "message": "Parameter #1 $one of method Levels\\AcceptTypes\\ArrayShapes::doBar() expects array{foo: callable(): mixed}, array{foo: 'nonexistent'} given.", "line": 584, "ignorable": true }, { - "message": "Parameter #1 $one of method Levels\\AcceptTypes\\ArrayShapes::doBar() expects array('foo' => callable(): mixed), array('bar' => 'date') given.", + "message": "Parameter #1 $one of method Levels\\AcceptTypes\\ArrayShapes::doBar() expects array{foo: callable(): mixed}, array{bar: 'date'} given.", "line": 585, "ignorable": true }, @@ -190,7 +190,7 @@ "ignorable": true }, { - "message": "Parameter #1 $nonEmpty of method Levels\\AcceptTypes\\AcceptNonEmpty::doBar() expects non-empty-array, array() given.", + "message": "Parameter #1 $nonEmpty of method Levels\\AcceptTypes\\AcceptNonEmpty::doBar() expects non-empty-array, array{} given.", "line": 733, "ignorable": true }, @@ -204,4 +204,4 @@ "line": 763, "ignorable": true } -] +] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/acceptTypes-7.json b/tests/PHPStan/Levels/data/acceptTypes-7.json index 4dd170abf9..31ce9d362b 100644 --- a/tests/PHPStan/Levels/data/acceptTypes-7.json +++ b/tests/PHPStan/Levels/data/acceptTypes-7.json @@ -100,22 +100,22 @@ "ignorable": true }, { - "message": "Parameter #1 $one of method Levels\\AcceptTypes\\ArrayShapes::doBar() expects array('foo' => callable(): mixed), array given.", + "message": "Parameter #1 $one of method Levels\\AcceptTypes\\ArrayShapes::doBar() expects array{foo: callable(): mixed}, array given.", "line": 577, "ignorable": true }, { - "message": "Parameter #1 $one of method Levels\\AcceptTypes\\ArrayShapes::doBar() expects array('foo' => callable(): mixed), array given.", + "message": "Parameter #1 $one of method Levels\\AcceptTypes\\ArrayShapes::doBar() expects array{foo: callable(): mixed}, array given.", "line": 578, "ignorable": true }, { - "message": "Parameter #1 $one of method Levels\\AcceptTypes\\ArrayShapes::doBar() expects array('foo' => callable(): mixed), array()|array('foo' => 'date') given.", + "message": "Parameter #1 $one of method Levels\\AcceptTypes\\ArrayShapes::doBar() expects array{foo: callable(): mixed}, array{}|array{foo: 'date'} given.", "line": 596, "ignorable": true }, { - "message": "Parameter #1 $one of method Levels\\AcceptTypes\\ArrayShapes::doBar() expects array('foo' => callable(): mixed), iterable given.", + "message": "Parameter #1 $one of method Levels\\AcceptTypes\\ArrayShapes::doBar() expects array{foo: callable(): mixed}, iterable given.", "line": 597, "ignorable": true }, diff --git a/tests/PHPStan/Levels/data/arrayDestructuring-3.json b/tests/PHPStan/Levels/data/arrayDestructuring-3.json index 710133680d..a97c71f0f5 100644 --- a/tests/PHPStan/Levels/data/arrayDestructuring-3.json +++ b/tests/PHPStan/Levels/data/arrayDestructuring-3.json @@ -5,7 +5,7 @@ "ignorable": true }, { - "message": "Offset 3 does not exist on array('a', 'b', 'c').", + "message": "Offset 3 does not exist on array{'a', 'b', 'c'}.", "line": 30, "ignorable": true } diff --git a/tests/PHPStan/Levels/data/arrayDimFetches-3.json b/tests/PHPStan/Levels/data/arrayDimFetches-3.json index a1f09d578f..73c0481c0e 100644 --- a/tests/PHPStan/Levels/data/arrayDimFetches-3.json +++ b/tests/PHPStan/Levels/data/arrayDimFetches-3.json @@ -1,7 +1,7 @@ [ { - "message": "Offset 'b' does not exist on array('a' => 1).", + "message": "Offset 'b' does not exist on array{a: 1}.", "line": 21, "ignorable": true } -] +] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/arrayDimFetches-7.json b/tests/PHPStan/Levels/data/arrayDimFetches-7.json index 15e551e874..25e275b28e 100644 --- a/tests/PHPStan/Levels/data/arrayDimFetches-7.json +++ b/tests/PHPStan/Levels/data/arrayDimFetches-7.json @@ -5,17 +5,17 @@ "ignorable": true }, { - "message": "Cannot access offset 'a' on array('a' => 1)|stdClass.", + "message": "Cannot access offset 'a' on array{a: 1}|stdClass.", "line": 27, "ignorable": true }, { - "message": "Cannot access offset 'b' on array('a' => 1)|stdClass.", + "message": "Cannot access offset 'b' on array{a: 1}|stdClass.", "line": 28, "ignorable": true }, { - "message": "Offset 'b' does not exist on array('a' => 1, ?'b' => 1).", + "message": "Offset 'b' does not exist on array{a: 1, b?: 1}.", "line": 40, "ignorable": true }, @@ -29,4 +29,4 @@ "line": 58, "ignorable": true } -] +] \ No newline at end of file diff --git a/tests/PHPStan/Levels/data/stubs-methods-4.json b/tests/PHPStan/Levels/data/stubs-methods-4.json index ab8a491ae3..eb0d8a3325 100644 --- a/tests/PHPStan/Levels/data/stubs-methods-4.json +++ b/tests/PHPStan/Levels/data/stubs-methods-4.json @@ -1,36 +1,36 @@ [ { - "message": "Strict comparison using === between int and array() will always evaluate to false.", + "message": "Strict comparison using === between int and array{} will always evaluate to false.", "line": 47, "ignorable": true }, { - "message": "Strict comparison using === between int and array() will always evaluate to false.", + "message": "Strict comparison using === between int and array{} will always evaluate to false.", "line": 58, "ignorable": true }, { - "message": "Strict comparison using === between int and array() will always evaluate to false.", + "message": "Strict comparison using === between int and array{} will always evaluate to false.", "line": 89, "ignorable": true }, { - "message": "Strict comparison using === between int and array() will always evaluate to false.", + "message": "Strict comparison using === between int and array{} will always evaluate to false.", "line": 108, "ignorable": true }, { - "message": "Strict comparison using === between int and array() will always evaluate to false.", + "message": "Strict comparison using === between int and array{} will always evaluate to false.", "line": 144, "ignorable": true }, { - "message": "Strict comparison using === between int and array() will always evaluate to false.", + "message": "Strict comparison using === between int and array{} will always evaluate to false.", "line": 158, "ignorable": true }, { - "message": "Strict comparison using === between int and array() will always evaluate to false.", + "message": "Strict comparison using === between int and array{} will always evaluate to false.", "line": 175, "ignorable": true } diff --git a/tests/PHPStan/PhpDoc/TypeDescriptionTest.php b/tests/PHPStan/PhpDoc/TypeDescriptionTest.php index f62b8b624a..7ecaba543d 100644 --- a/tests/PHPStan/PhpDoc/TypeDescriptionTest.php +++ b/tests/PHPStan/PhpDoc/TypeDescriptionTest.php @@ -9,6 +9,8 @@ use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\ArrayType; use PHPStan\Type\ClassStringType; +use PHPStan\Type\Constant\ConstantArrayTypeBuilder; +use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\Generic\GenericClassStringType; use PHPStan\Type\IntegerType; use PHPStan\Type\IntersectionType; @@ -33,6 +35,37 @@ public function dataTest(): iterable yield ['non-empty-array', new IntersectionType([new ArrayType(new IntegerType(), new StringType()), new NonEmptyArrayType()])]; yield ['class-string&literal-string', new IntersectionType([new ClassStringType(), new AccessoryLiteralStringType()])]; yield ['class-string&literal-string', new IntersectionType([new GenericClassStringType(new ObjectType('Foo')), new AccessoryLiteralStringType()])]; + + $builder = ConstantArrayTypeBuilder::createEmpty(); + $builder->setOffsetValueType(new ConstantStringType('foo'), new IntegerType()); + yield ['array{foo: int}', $builder->getArray()]; + + $builder = ConstantArrayTypeBuilder::createEmpty(); + $builder->setOffsetValueType(new ConstantStringType('foo'), new IntegerType(), true); + yield ['array{foo?: int}', $builder->getArray()]; + + $builder = ConstantArrayTypeBuilder::createEmpty(); + $builder->setOffsetValueType(new ConstantStringType('foo'), new IntegerType(), true); + $builder->setOffsetValueType(new ConstantStringType('bar'), new StringType()); + yield ['array{foo?: int, bar: string}', $builder->getArray()]; + + $builder = ConstantArrayTypeBuilder::createEmpty(); + $builder->setOffsetValueType(null, new IntegerType()); + $builder->setOffsetValueType(null, new StringType()); + yield ['array{int, string}', $builder->getArray()]; + + $builder = ConstantArrayTypeBuilder::createEmpty(); + $builder->setOffsetValueType(null, new IntegerType()); + $builder->setOffsetValueType(null, new StringType(), true); + yield ['array{0: int, 1?: string}', $builder->getArray()]; + + $builder = ConstantArrayTypeBuilder::createEmpty(); + $builder->setOffsetValueType(new ConstantStringType('\'foo\''), new IntegerType()); + yield ['array{"\'foo\'": int}', $builder->getArray()]; + + $builder = ConstantArrayTypeBuilder::createEmpty(); + $builder->setOffsetValueType(new ConstantStringType('"foo"'), new IntegerType()); + yield ['array{\'"foo"\': int}', $builder->getArray()]; } /** diff --git a/tests/PHPStan/Rules/Arrays/AppendedArrayItemTypeRuleTest.php b/tests/PHPStan/Rules/Arrays/AppendedArrayItemTypeRuleTest.php index d6c27680be..6413a397ad 100644 --- a/tests/PHPStan/Rules/Arrays/AppendedArrayItemTypeRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/AppendedArrayItemTypeRuleTest.php @@ -29,15 +29,15 @@ public function testAppendedArrayItemType(): void 18, ], [ - 'Array (array) does not accept array(1, 2, 3).', + 'Array (array) does not accept array{1, 2, 3}.', 20, ], [ - 'Array (array) does not accept array(\'AppendedArrayItem\\\\Foo\', \'classMethod\').', + 'Array (array) does not accept array{\'AppendedArrayItem\\\\Foo\', \'classMethod\'}.', 23, ], [ - 'Array (array) does not accept array(\'Foo\', \'Hello world\').', + 'Array (array) does not accept array{\'Foo\', \'Hello world\'}.', 25, ], [ diff --git a/tests/PHPStan/Rules/Arrays/ArrayDestructuringRuleTest.php b/tests/PHPStan/Rules/Arrays/ArrayDestructuringRuleTest.php index e40f6d605e..4707547ea1 100644 --- a/tests/PHPStan/Rules/Arrays/ArrayDestructuringRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/ArrayDestructuringRuleTest.php @@ -30,7 +30,7 @@ public function testRule(): void 11, ], [ - 'Offset 0 does not exist on array().', + 'Offset 0 does not exist on array{}.', 12, ], [ @@ -38,11 +38,11 @@ public function testRule(): void 13, ], [ - 'Offset 2 does not exist on array(1, 2).', + 'Offset 2 does not exist on array{1, 2}.', 15, ], [ - 'Offset \'a\' does not exist on array(\'b\' => 1).', + 'Offset \'a\' does not exist on array{b: 1}.', 22, ], ]); diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index d473d9cc8b..62ec8af52c 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -28,15 +28,15 @@ public function testRule(): void { $this->analyse([__DIR__ . '/data/nonexistent-offset.php'], [ [ - 'Offset \'b\' does not exist on array(\'a\' => stdClass, 0 => 2).', + 'Offset \'b\' does not exist on array{a: stdClass, 0: 2}.', 17, ], [ - 'Offset 1 does not exist on array(\'a\' => stdClass, 0 => 2).', + 'Offset 1 does not exist on array{a: stdClass, 0: 2}.', 18, ], [ - 'Offset \'a\' does not exist on array(\'b\' => 1).', + 'Offset \'a\' does not exist on array{b: 1}.', 55, ], [ @@ -82,23 +82,23 @@ public function testRule(): void 145, ], [ - 'Offset \'c\' does not exist on array(\'c\' => bool)|array(\'e\' => true).', + 'Offset \'c\' does not exist on array{c: bool}|array{e: true}.', 171, ], [ - 'Offset int does not exist on array()|array(1 => 1, 2 => 2)|array(3 => 3, 4 => 4).', + 'Offset int does not exist on array{}|array{1: 1, 2: 2}|array{3: 3, 4: 4}.', 190, ], [ - 'Offset int does not exist on array()|array(1 => 1, 2 => 2)|array(3 => 3, 4 => 4).', + 'Offset int does not exist on array{}|array{1: 1, 2: 2}|array{3: 3, 4: 4}.', 193, ], [ - 'Offset \'b\' does not exist on array(\'a\' => \'blabla\').', + 'Offset \'b\' does not exist on array{a: \'blabla\'}.', 225, ], [ - 'Offset \'b\' does not exist on array(\'a\' => \'blabla\').', + 'Offset \'b\' does not exist on array{a: \'blabla\'}.', 228, ], [ @@ -110,7 +110,7 @@ public function testRule(): void 253, ], [ - 'Cannot access offset \'a\' on array(\'a\' => 1, \'b\' => 1)|(Closure(): void).', + 'Cannot access offset \'a\' on array{a: 1, b: 1}|(Closure(): void).', 258, ], [ @@ -126,7 +126,7 @@ public function testRule(): void 312, ], [ - 'Offset \'baz\' does not exist on array(\'bar\' => 1, ?\'baz\' => 2).', + 'Offset \'baz\' does not exist on array{bar: 1, baz?: 2}.', 344, ], [ @@ -190,7 +190,7 @@ public function testAssignOp(): void { $this->analyse([__DIR__ . '/data/offset-access-assignop.php'], [ [ - 'Offset \'foo\' does not exist on array().', + 'Offset \'foo\' does not exist on array{}.', 4, ], [ @@ -340,7 +340,7 @@ public function testRuleWithNullsafeVariant(): void $this->analyse([__DIR__ . '/data/nonexistent-offset-nullsafe.php'], [ [ - 'Offset 1 does not exist on array(\'a\' => int).', + 'Offset 1 does not exist on array{a: int}.', 18, ], ]); diff --git a/tests/PHPStan/Rules/Arrays/OffsetAccessAssignmentRuleTest.php b/tests/PHPStan/Rules/Arrays/OffsetAccessAssignmentRuleTest.php index 665b9ec8fa..9faef229f4 100644 --- a/tests/PHPStan/Rules/Arrays/OffsetAccessAssignmentRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/OffsetAccessAssignmentRuleTest.php @@ -58,7 +58,7 @@ public function testOffsetAccessAssignmentToScalar(): void 68, ], [ - 'Cannot assign offset array(1, 2, 3) to SplObjectStorage.', + 'Cannot assign offset array{1, 2, 3} to SplObjectStorage.', 72, ], [ @@ -100,7 +100,7 @@ public function testOffsetAccessAssignmentToScalarWithoutMaybes(): void 68, ], [ - 'Cannot assign offset array(1, 2, 3) to SplObjectStorage.', + 'Cannot assign offset array{1, 2, 3} to SplObjectStorage.', 72, ], [ diff --git a/tests/PHPStan/Rules/Cast/EchoRuleTest.php b/tests/PHPStan/Rules/Cast/EchoRuleTest.php index b4d5bf6b54..29f4b7b7b3 100644 --- a/tests/PHPStan/Rules/Cast/EchoRuleTest.php +++ b/tests/PHPStan/Rules/Cast/EchoRuleTest.php @@ -23,7 +23,7 @@ public function testEchoRule(): void { $this->analyse([__DIR__ . '/data/echo.php'], [ [ - 'Parameter #1 (array()) of echo cannot be converted to string.', + 'Parameter #1 (array{}) of echo cannot be converted to string.', 7, ], [ @@ -31,7 +31,7 @@ public function testEchoRule(): void 9, ], [ - 'Parameter #1 (array()) of echo cannot be converted to string.', + 'Parameter #1 (array{}) of echo cannot be converted to string.', 11, ], [ @@ -43,7 +43,7 @@ public function testEchoRule(): void 13, ], [ - 'Parameter #1 (\'string\'|array(\'string\')) of echo cannot be converted to string.', + 'Parameter #1 (\'string\'|array{\'string\'}) of echo cannot be converted to string.', 17, ], ]); diff --git a/tests/PHPStan/Rules/Cast/PrintRuleTest.php b/tests/PHPStan/Rules/Cast/PrintRuleTest.php index dc5e0429cf..878286f45f 100644 --- a/tests/PHPStan/Rules/Cast/PrintRuleTest.php +++ b/tests/PHPStan/Rules/Cast/PrintRuleTest.php @@ -23,7 +23,7 @@ public function testPrintRule(): void { $this->analyse([__DIR__ . '/data/print.php'], [ [ - 'Parameter array() of print cannot be converted to string.', + 'Parameter array{} of print cannot be converted to string.', 5, ], [ @@ -35,7 +35,7 @@ public function testPrintRule(): void 9, ], [ - 'Parameter array() of print cannot be converted to string.', + 'Parameter array{} of print cannot be converted to string.', 13, ], [ @@ -47,7 +47,7 @@ public function testPrintRule(): void 17, ], [ - 'Parameter \'string\'|array(\'string\') of print cannot be converted to string.', + 'Parameter \'string\'|array{\'string\'} of print cannot be converted to string.', 21, ], ]); diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php index 8e7633a5df..c15decc0a7 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php @@ -102,43 +102,43 @@ public function testImpossibleCheckTypeFunctionCall(): void 212, ], [ - 'Call to function in_array() with arguments int, array(\'foo\', \'bar\') and true will always evaluate to false.', + 'Call to function in_array() with arguments int, array{\'foo\', \'bar\'} and true will always evaluate to false.', 235, ], [ - 'Call to function in_array() with arguments \'bar\'|\'foo\', array(\'baz\', \'lorem\') and true will always evaluate to false.', + 'Call to function in_array() with arguments \'bar\'|\'foo\', array{\'baz\', \'lorem\'} and true will always evaluate to false.', 244, ], [ - 'Call to function in_array() with arguments \'bar\'|\'foo\', array(\'foo\', \'bar\') and true will always evaluate to true.', + 'Call to function in_array() with arguments \'bar\'|\'foo\', array{\'foo\', \'bar\'} and true will always evaluate to true.', 248, ], [ - 'Call to function in_array() with arguments \'foo\', array(\'foo\') and true will always evaluate to true.', + 'Call to function in_array() with arguments \'foo\', array{\'foo\'} and true will always evaluate to true.', 252, ], [ - 'Call to function in_array() with arguments \'foo\', array(\'foo\', \'bar\') and true will always evaluate to true.', + 'Call to function in_array() with arguments \'foo\', array{\'foo\', \'bar\'} and true will always evaluate to true.', 256, ], [ - 'Call to function in_array() with arguments \'bar\', array()|array(\'foo\') and true will always evaluate to false.', + 'Call to function in_array() with arguments \'bar\', array{}|array{\'foo\'} and true will always evaluate to false.', 320, ], [ - 'Call to function in_array() with arguments \'baz\', array(0 => \'bar\', ?1 => \'foo\') and true will always evaluate to false.', + 'Call to function in_array() with arguments \'baz\', array{0: \'bar\', 1?: \'foo\'} and true will always evaluate to false.', 336, ], [ - 'Call to function in_array() with arguments \'foo\', array() and true will always evaluate to false.', + 'Call to function in_array() with arguments \'foo\', array{} and true will always evaluate to false.', 343, ], [ - 'Call to function array_key_exists() with \'a\' and array(\'a\' => 1, ?\'b\' => 2) will always evaluate to true.', + 'Call to function array_key_exists() with \'a\' and array{a: 1, b?: 2} will always evaluate to true.', 360, ], [ - 'Call to function array_key_exists() with \'c\' and array(\'a\' => 1, ?\'b\' => 2) will always evaluate to false.', + 'Call to function array_key_exists() with \'c\' and array{a: 1, b?: 2} will always evaluate to false.', 366, ], [ @@ -279,27 +279,27 @@ public function testImpossibleCheckTypeFunctionCallWithoutAlwaysTrue(): void 212, ], [ - 'Call to function in_array() with arguments int, array(\'foo\', \'bar\') and true will always evaluate to false.', + 'Call to function in_array() with arguments int, array{\'foo\', \'bar\'} and true will always evaluate to false.', 235, ], [ - 'Call to function in_array() with arguments \'bar\'|\'foo\', array(\'baz\', \'lorem\') and true will always evaluate to false.', + 'Call to function in_array() with arguments \'bar\'|\'foo\', array{\'baz\', \'lorem\'} and true will always evaluate to false.', 244, ], [ - 'Call to function in_array() with arguments \'bar\', array()|array(\'foo\') and true will always evaluate to false.', + 'Call to function in_array() with arguments \'bar\', array{}|array{\'foo\'} and true will always evaluate to false.', 320, ], [ - 'Call to function in_array() with arguments \'baz\', array(0 => \'bar\', ?1 => \'foo\') and true will always evaluate to false.', + 'Call to function in_array() with arguments \'baz\', array{0: \'bar\', 1?: \'foo\'} and true will always evaluate to false.', 336, ], [ - 'Call to function in_array() with arguments \'foo\', array() and true will always evaluate to false.', + 'Call to function in_array() with arguments \'foo\', array{} and true will always evaluate to false.', 343, ], [ - 'Call to function array_key_exists() with \'c\' and array(\'a\' => 1, ?\'b\' => 2) will always evaluate to false.', + 'Call to function array_key_exists() with \'c\' and array{a: 1, b?: 2} will always evaluate to false.', 366, ], [ diff --git a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php index a572b8c6a1..b92346d98f 100644 --- a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php @@ -111,11 +111,11 @@ public function testStrictComparison(): void 284, ], [ - 'Strict comparison using === between array(\'X\' => 1) and array(\'X\' => 2) will always evaluate to false.', + 'Strict comparison using === between array{X: 1} and array{X: 2} will always evaluate to false.', 292, ], [ - 'Strict comparison using === between array(\'X\' => 1, \'Y\' => 2) and array(\'X\' => 2, \'Y\' => 1) will always evaluate to false.', + 'Strict comparison using === between array{X: 1, Y: 2} and array{X: 2, Y: 1} will always evaluate to false.', 300, ], [ @@ -293,11 +293,11 @@ public function testStrictComparisonWithoutAlwaysTrue(): void 284, ], [ - 'Strict comparison using === between array(\'X\' => 1) and array(\'X\' => 2) will always evaluate to false.', + 'Strict comparison using === between array{X: 1} and array{X: 2} will always evaluate to false.', 292, ], [ - 'Strict comparison using === between array(\'X\' => 1, \'Y\' => 2) and array(\'X\' => 2, \'Y\' => 1) will always evaluate to false.', + 'Strict comparison using === between array{X: 1, Y: 2} and array{X: 2, Y: 1} will always evaluate to false.', 300, ], [ diff --git a/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php b/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php index 5353534e56..59c8dfda09 100644 --- a/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php @@ -55,11 +55,11 @@ public function testRule(): void 25, ], [ - 'Parameter #1 $i of callable array($this(CallCallables\Foo), \'doBar\') expects int, string given.', + 'Parameter #1 $i of callable array{$this(CallCallables\\Foo}, \'doBar\') expects int, string given.', 33, ], [ - 'Callable array(\'CallCallables\\\\Foo\', \'doStaticBaz\') invoked with 1 parameter, 0 required.', + 'Callable array{\'CallCallables\\\\Foo\', \'doStaticBaz\'} invoked with 1 parameter, 0 required.', 39, ], [ @@ -108,7 +108,7 @@ public function testRule(): void 113, ], [ - 'Trying to invoke array(object, \'bar\') but it might not be a callable.', + 'Trying to invoke array{object, \'bar\'} but it might not be a callable.', 131, ], [ @@ -124,15 +124,15 @@ public function testRule(): void 148, ], [ - 'Trying to invoke array(object, \'yo\') but it might not be a callable.', + 'Trying to invoke array{object, \'yo\'} but it might not be a callable.', 163, ], [ - 'Trying to invoke array(object, \'yo\') but it might not be a callable.', + 'Trying to invoke array{object, \'yo\'} but it might not be a callable.', 167, ], [ - 'Trying to invoke array(\'CallCallables\\\\CallableInForeach\', \'bar\'|\'foo\') but it might not be a callable.', + 'Trying to invoke array{\'CallCallables\\\\CallableInForeach\', \'bar\'|\'foo\'} but it might not be a callable.', 179, ], ]); diff --git a/tests/PHPStan/Rules/Functions/ClosureReturnTypeRuleTest.php b/tests/PHPStan/Rules/Functions/ClosureReturnTypeRuleTest.php index addb8aaf1a..90848f210b 100644 --- a/tests/PHPStan/Rules/Functions/ClosureReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ClosureReturnTypeRuleTest.php @@ -40,7 +40,7 @@ public function testClosureReturnTypeRule(): void 46, ], [ - 'Anonymous function should return array()|null but empty return statement found.', + 'Anonymous function should return array{}|null but empty return statement found.', 88, ], [ diff --git a/tests/PHPStan/Rules/Generators/YieldFromTypeRuleTest.php b/tests/PHPStan/Rules/Generators/YieldFromTypeRuleTest.php index 7a949cdc35..b70e2508d8 100644 --- a/tests/PHPStan/Rules/Generators/YieldFromTypeRuleTest.php +++ b/tests/PHPStan/Rules/Generators/YieldFromTypeRuleTest.php @@ -37,7 +37,7 @@ public function testRule(): void 41, ], [ - 'Generator expects value type array(DateTime, DateTime, stdClass, DateTimeImmutable), array(0 => DateTime, 1 => DateTime, 2 => stdClass, 4 => DateTimeImmutable) given.', + 'Generator expects value type array{DateTime, DateTime, stdClass, DateTimeImmutable}, array{0: DateTime, 1: DateTime, 2: stdClass, 4: DateTimeImmutable} given.', 74, ], [ diff --git a/tests/PHPStan/Rules/Generators/YieldTypeRuleTest.php b/tests/PHPStan/Rules/Generators/YieldTypeRuleTest.php index 58799d2296..c03a5b5987 100644 --- a/tests/PHPStan/Rules/Generators/YieldTypeRuleTest.php +++ b/tests/PHPStan/Rules/Generators/YieldTypeRuleTest.php @@ -45,7 +45,7 @@ public function testRule(): void 17, ], [ - 'Generator expects value type array(0 => DateTime, 1 => DateTime, 2 => stdClass, 4 => DateTimeImmutable), array(DateTime, DateTime, stdClass, DateTimeImmutable) given.', + 'Generator expects value type array{0: DateTime, 1: DateTime, 2: stdClass, 4: DateTimeImmutable}, array{DateTime, DateTime, stdClass, DateTimeImmutable} given.', 25, ], [ diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 630f370f5b..d03a9fbb00 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -390,15 +390,15 @@ public function testCallMethods(): void 914, ], [ - 'Parameter #1 $callable of method Test\MethodExists::doBar() expects callable(): mixed, array(class-string|object, \'foo\') given.', + 'Parameter #1 $callable of method Test\\MethodExists::doBar() expects callable(): mixed, array{class-string|object, \'foo\'} given.', 915, ], [ - 'Parameter #1 $callable of method Test\MethodExists::doBar() expects callable(): mixed, array(class-string|object, \'bar\') given.', + 'Parameter #1 $callable of method Test\\MethodExists::doBar() expects callable(): mixed, array{class-string|object, \'bar\'} given.', 916, ], [ - 'Parameter #1 $callable of method Test\MethodExists::doBar() expects callable(): mixed, array(object, \'bar\') given.', + 'Parameter #1 $callable of method Test\\MethodExists::doBar() expects callable(): mixed, array{object, \'bar\'} given.', 921, ], [ @@ -475,11 +475,11 @@ public function testCallMethods(): void 'See: https://phpstan.org/blog/solving-phpstan-error-unable-to-resolve-template-type', ], [ - 'Parameter #1 $a of method Test\CallableWithMixedArray::doBar() expects callable(array): array, Closure(array): array(\'foo\')|null given.', + 'Parameter #1 $a of method Test\\CallableWithMixedArray::doBar() expects callable(array): array, Closure(array): array{\'foo\'}|null given.', 1533, ], [ - 'Parameter #1 $members of method Test\ParameterTypeCheckVerbosity::doBar() expects array string, \'code\' => string)>, array string)> given.', + 'Parameter #1 $members of method Test\\ParameterTypeCheckVerbosity::doBar() expects array, array given.', 1589, ], [ @@ -701,15 +701,15 @@ public function testCallMethodsOnThisOnly(): void 867, ], [ - 'Parameter #1 $callable of method Test\MethodExists::doBar() expects callable(): mixed, array(class-string|object, \'foo\') given.', + 'Parameter #1 $callable of method Test\\MethodExists::doBar() expects callable(): mixed, array{class-string|object, \'foo\'} given.', 915, ], [ - 'Parameter #1 $callable of method Test\MethodExists::doBar() expects callable(): mixed, array(class-string|object, \'bar\') given.', + 'Parameter #1 $callable of method Test\\MethodExists::doBar() expects callable(): mixed, array{class-string|object, \'bar\'} given.', 916, ], [ - 'Parameter #1 $callable of method Test\MethodExists::doBar() expects callable(): mixed, array(object, \'bar\') given.', + 'Parameter #1 $callable of method Test\\MethodExists::doBar() expects callable(): mixed, array{object, \'bar\'} given.', 921, ], [ @@ -762,11 +762,11 @@ public function testCallMethodsOnThisOnly(): void 'See: https://phpstan.org/blog/solving-phpstan-error-unable-to-resolve-template-type', ], [ - 'Parameter #1 $a of method Test\CallableWithMixedArray::doBar() expects callable(array): array, Closure(array): array(\'foo\')|null given.', + 'Parameter #1 $a of method Test\\CallableWithMixedArray::doBar() expects callable(array): array, Closure(array): array{\'foo\'}|null given.', 1533, ], [ - 'Parameter #1 $members of method Test\ParameterTypeCheckVerbosity::doBar() expects array string, \'code\' => string)>, array string)> given.', + 'Parameter #1 $members of method Test\\ParameterTypeCheckVerbosity::doBar() expects array, array given.', 1589, ], [ diff --git a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php index b667ebcd78..9366509dcb 100644 --- a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php @@ -433,7 +433,7 @@ public function testBug1971(): void $this->checkThisOnly = false; $this->analyse([__DIR__ . '/data/bug-1971.php'], [ [ - 'Parameter #1 $callback of static method Closure::fromCallable() expects callable(): mixed, array(class-string, \'sayHello2\') given.', + 'Parameter #1 $callback of static method Closure::fromCallable() expects callable(): mixed, array{class-string, \'sayHello2\'} given.', 16, ], ]); diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index 13fe986ae1..982a5cd157 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -429,15 +429,15 @@ public function testBug4590(): void { $this->analyse([__DIR__ . '/data/bug-4590.php'], [ [ - 'Method Bug4590\Controller::test1() should return Bug4590\OkResponse> but returns Bug4590\OkResponse string)>.', + 'Method Bug4590\\Controller::test1() should return Bug4590\\OkResponse> but returns Bug4590\\OkResponse.', 39, ], [ - 'Method Bug4590\Controller::test2() should return Bug4590\OkResponse> but returns Bug4590\OkResponse.', + 'Method Bug4590\\Controller::test2() should return Bug4590\\OkResponse> but returns Bug4590\\OkResponse.', 47, ], [ - 'Method Bug4590\Controller::test3() should return Bug4590\OkResponse> but returns Bug4590\OkResponse.', + 'Method Bug4590\\Controller::test3() should return Bug4590\\OkResponse> but returns Bug4590\\OkResponse.', 55, ], ]); diff --git a/tests/PHPStan/Rules/Operators/InvalidUnaryOperationRuleTest.php b/tests/PHPStan/Rules/Operators/InvalidUnaryOperationRuleTest.php index 77bb4fe9e3..7dd8c33e28 100644 --- a/tests/PHPStan/Rules/Operators/InvalidUnaryOperationRuleTest.php +++ b/tests/PHPStan/Rules/Operators/InvalidUnaryOperationRuleTest.php @@ -33,7 +33,7 @@ public function testRule(): void 20, ], [ - 'Unary operation "~" on array() results in an error.', + 'Unary operation "~" on array{} results in an error.', 24, ], ]); diff --git a/tests/PHPStan/Rules/Variables/EmptyRuleTest.php b/tests/PHPStan/Rules/Variables/EmptyRuleTest.php index 5a300c8cda..795efe943e 100644 --- a/tests/PHPStan/Rules/Variables/EmptyRuleTest.php +++ b/tests/PHPStan/Rules/Variables/EmptyRuleTest.php @@ -36,27 +36,27 @@ public function testRule(): void $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/empty-rule.php'], [ [ - 'Offset \'nonexistent\' on array(?0 => bool, ?1 => false, 2 => bool, 3 => false, 4 => true) in empty() does not exist.', + 'Offset \'nonexistent\' on array{0?: bool, 1?: false, 2: bool, 3: false, 4: true} in empty() does not exist.', 22, ], [ - 'Offset 3 on array(?0 => bool, ?1 => false, 2 => bool, 3 => false, 4 => true) in empty() always exists and is always falsy.', + 'Offset 3 on array{0?: bool, 1?: false, 2: bool, 3: false, 4: true} in empty() always exists and is always falsy.', 24, ], [ - 'Offset 4 on array(?0 => bool, ?1 => false, 2 => bool, 3 => false, 4 => true) in empty() always exists and is not falsy.', + 'Offset 4 on array{0?: bool, 1?: false, 2: bool, 3: false, 4: true} in empty() always exists and is not falsy.', 25, ], [ - 'Offset 0 on array(\'\', \'0\', \'foo\', \'\'|\'foo\') in empty() always exists and is always falsy.', + 'Offset 0 on array{\'\', \'0\', \'foo\', \'\'|\'foo\'} in empty() always exists and is always falsy.', 36, ], [ - 'Offset 1 on array(\'\', \'0\', \'foo\', \'\'|\'foo\') in empty() always exists and is always falsy.', + 'Offset 1 on array{\'\', \'0\', \'foo\', \'\'|\'foo\'} in empty() always exists and is always falsy.', 37, ], [ - 'Offset 2 on array(\'\', \'0\', \'foo\', \'\'|\'foo\') in empty() always exists and is not falsy.', + 'Offset 2 on array{\'\', \'0\', \'foo\', \'\'|\'foo\'} in empty() always exists and is not falsy.', 38, ], [ diff --git a/tests/PHPStan/Rules/Variables/IssetRuleTest.php b/tests/PHPStan/Rules/Variables/IssetRuleTest.php index 5ea863b812..e67fb91361 100644 --- a/tests/PHPStan/Rules/Variables/IssetRuleTest.php +++ b/tests/PHPStan/Rules/Variables/IssetRuleTest.php @@ -45,11 +45,11 @@ public function testRule(): void 41, ], [ - 'Offset \'string\' on array(1, 2, 3) in isset() does not exist.', + 'Offset \'string\' on array{1, 2, 3} in isset() does not exist.', 45, ], [ - 'Offset \'string\' on array(array(1), array(2), array(3)) in isset() does not exist.', + 'Offset \'string\' on array{array{1}, array{2}, array{3}} in isset() does not exist.', 49, ], [ @@ -57,15 +57,15 @@ public function testRule(): void 51, ], [ - 'Offset \'dim\' on array(\'dim\' => 1, \'dim-null\' => 1|null, \'dim-null-offset\' => array(\'a\' => true|null), \'dim-empty\' => array()) in isset() always exists and is not nullable.', + 'Offset \'dim\' on array{dim: 1, dim-null: 1|null, dim-null-offset: array{a: true|null}, dim-empty: array{}} in isset() always exists and is not nullable.', 67, ], [ - 'Offset \'dim-null-not-set\' on array(\'dim\' => 1, \'dim-null\' => 1|null, \'dim-null-offset\' => array(\'a\' => true|null), \'dim-empty\' => array()) in isset() does not exist.', + 'Offset \'dim-null-not-set\' on array{dim: 1, dim-null: 1|null, dim-null-offset: array{a: true|null}, dim-empty: array{}} in isset() does not exist.', 73, ], [ - 'Offset \'b\' on array() in isset() does not exist.', + 'Offset \'b\' on array{} in isset() does not exist.', 79, ], [ @@ -109,11 +109,11 @@ public function testRule(): void 124, ], [ - "Offset 'foo' on array('foo' => string) in isset() always exists and is not nullable.", + 'Offset \'foo\' on array{foo: string} in isset() always exists and is not nullable.', 170, ], [ - "Offset 'bar' on array('bar' => 1) in isset() always exists and is not nullable.", + 'Offset \'bar\' on array{bar: 1} in isset() always exists and is not nullable.', 173, ], ]); @@ -132,11 +132,11 @@ public function testRuleWithoutTreatPhpDocTypesAsCertain(): void 41, ], [ - 'Offset \'string\' on array(1, 2, 3) in isset() does not exist.', + 'Offset \'string\' on array{1, 2, 3} in isset() does not exist.', 45, ], [ - 'Offset \'string\' on array(array(1), array(2), array(3)) in isset() does not exist.', + 'Offset \'string\' on array{array{1}, array{2}, array{3}} in isset() does not exist.', 49, ], [ @@ -144,15 +144,15 @@ public function testRuleWithoutTreatPhpDocTypesAsCertain(): void 51, ], [ - 'Offset \'dim\' on array(\'dim\' => 1, \'dim-null\' => 1|null, \'dim-null-offset\' => array(\'a\' => true|null), \'dim-empty\' => array()) in isset() always exists and is not nullable.', + 'Offset \'dim\' on array{dim: 1, dim-null: 1|null, dim-null-offset: array{a: true|null}, dim-empty: array{}} in isset() always exists and is not nullable.', 67, ], [ - 'Offset \'dim-null-not-set\' on array(\'dim\' => 1, \'dim-null\' => 1|null, \'dim-null-offset\' => array(\'a\' => true|null), \'dim-empty\' => array()) in isset() does not exist.', + 'Offset \'dim-null-not-set\' on array{dim: 1, dim-null: 1|null, dim-null-offset: array{a: true|null}, dim-empty: array{}} in isset() does not exist.', 73, ], [ - 'Offset \'b\' on array() in isset() does not exist.', + 'Offset \'b\' on array{} in isset() does not exist.', 79, ], [ diff --git a/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php b/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php index 60778f3b38..8399fe241d 100644 --- a/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php +++ b/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php @@ -43,11 +43,11 @@ public function testCoalesceRule(): void 41, ], [ - 'Offset \'string\' on array(1, 2, 3) on left side of ?? does not exist.', + 'Offset \'string\' on array{1, 2, 3} on left side of ?? does not exist.', 45, ], [ - 'Offset \'string\' on array(array(1), array(2), array(3)) on left side of ?? does not exist.', + 'Offset \'string\' on array{array{1}, array{2}, array{3}} on left side of ?? does not exist.', 49, ], [ @@ -55,15 +55,15 @@ public function testCoalesceRule(): void 51, ], [ - 'Offset \'dim\' on array(\'dim\' => 1, \'dim-null\' => 1|null, \'dim-null-offset\' => array(\'a\' => true|null), \'dim-empty\' => array()) on left side of ?? always exists and is not nullable.', + 'Offset \'dim\' on array{dim: 1, dim-null: 1|null, dim-null-offset: array{a: true|null}, dim-empty: array{}} on left side of ?? always exists and is not nullable.', 67, ], [ - 'Offset \'dim-null-not-set\' on array(\'dim\' => 1, \'dim-null\' => 1|null, \'dim-null-offset\' => array(\'a\' => true|null), \'dim-empty\' => array()) on left side of ?? does not exist.', + 'Offset \'dim-null-not-set\' on array{dim: 1, dim-null: 1|null, dim-null-offset: array{a: true|null}, dim-empty: array{}} on left side of ?? does not exist.', 73, ], [ - 'Offset \'b\' on array() on left side of ?? does not exist.', + 'Offset \'b\' on array{} on left side of ?? does not exist.', 79, ], [ @@ -150,11 +150,11 @@ public function testCoalesceAssignRule(): void 41, ], [ - 'Offset \'string\' on array(1, 2, 3) on left side of ??= does not exist.', + 'Offset \'string\' on array{1, 2, 3} on left side of ??= does not exist.', 45, ], [ - 'Offset \'string\' on array(array(1), array(2), array(3)) on left side of ??= does not exist.', + 'Offset \'string\' on array{array{1}, array{2}, array{3}} on left side of ??= does not exist.', 49, ], [ @@ -162,15 +162,15 @@ public function testCoalesceAssignRule(): void 51, ], [ - 'Offset \'dim\' on array(\'dim\' => 1, \'dim-null\' => 1|null, \'dim-null-offset\' => array(\'a\' => true|null), \'dim-empty\' => array()) on left side of ??= always exists and is not nullable.', + 'Offset \'dim\' on array{dim: 1, dim-null: 1|null, dim-null-offset: array{a: true|null}, dim-empty: array{}} on left side of ??= always exists and is not nullable.', 67, ], [ - 'Offset \'dim-null-not-set\' on array(\'dim\' => 1, \'dim-null\' => 0|1, \'dim-null-offset\' => array(\'a\' => true|null), \'dim-empty\' => array()) on left side of ??= does not exist.', + 'Offset \'dim-null-not-set\' on array{dim: 1, dim-null: 0|1, dim-null-offset: array{a: true|null}, dim-empty: array{}} on left side of ??= does not exist.', 73, ], [ - 'Offset \'b\' on array() on left side of ??= does not exist.', + 'Offset \'b\' on array{} on left side of ??= does not exist.', 79, ], [ diff --git a/tests/PHPStan/Type/TypeCombinatorTest.php b/tests/PHPStan/Type/TypeCombinatorTest.php index 380dd85c72..fd6a26e093 100644 --- a/tests/PHPStan/Type/TypeCombinatorTest.php +++ b/tests/PHPStan/Type/TypeCombinatorTest.php @@ -693,7 +693,7 @@ public function dataUnion(): array ]), ], ConstantArrayType::class, - 'array(\'foo\' => DateTimeImmutable|null, \'bar\' => int|string)', + 'array{foo: DateTimeImmutable|null, bar: int|string}', ], [ [ @@ -711,7 +711,7 @@ public function dataUnion(): array ]), ], ConstantArrayType::class, - 'array(\'foo\' => DateTimeImmutable|null, ?\'bar\' => int)', + 'array{foo: DateTimeImmutable|null, bar?: int}', ], [ [ @@ -733,7 +733,7 @@ public function dataUnion(): array ]), ], ConstantArrayType::class, - 'array(\'foo\' => DateTimeImmutable|null, \'bar\' => int|string, ?\'baz\' => int)', + 'array{foo: DateTimeImmutable|null, bar: int|string, baz?: int}', ], [ [ @@ -902,7 +902,7 @@ public function dataUnion(): array ]), ], IntersectionType::class, - 'array(object, \'foo\')&callable(): mixed', + 'array{object, \'foo\'}&callable(): mixed', ], [ [ @@ -1650,7 +1650,7 @@ public function dataUnion(): array ]), ], UnionType::class, - 'array()|array(string)', + 'array{}|array{string}', ], [ [ @@ -1662,7 +1662,7 @@ public function dataUnion(): array ], 1, [0]), ], UnionType::class, - 'array()|array(?0 => string)', + 'array{}|array{0?: string}', ], [ [ @@ -1682,7 +1682,7 @@ public function dataUnion(): array ]), ], UnionType::class, - 'array(\'a\' => int, \'b\' => int)|array(\'c\' => int, \'d\' => int)', + 'array{a: int, b: int}|array{c: int, d: int}', ], [ [ @@ -1700,7 +1700,7 @@ public function dataUnion(): array ]), ], ConstantArrayType::class, - 'array(\'a\' => int, ?\'b\' => int)', + 'array{a: int, b?: int}', ], [ [ @@ -1720,7 +1720,7 @@ public function dataUnion(): array ]), ], UnionType::class, - 'array(\'a\' => int, \'b\' => int)|array(\'b\' => int, \'c\' => int)', + 'array{a: int, b: int}|array{b: int, c: int}', ], [ [ @@ -1744,7 +1744,7 @@ public function dataUnion(): array StaticTypeFactory::falsey(), ], UnionType::class, - '0|0.0|\'\'|\'0\'|array()|false|null', + '0|0.0|\'\'|\'0\'|array{}|false|null', ], [ [ @@ -1752,7 +1752,7 @@ public function dataUnion(): array StaticTypeFactory::truthy(), ], MixedType::class, - 'mixed~0|0.0|\'\'|\'0\'|array()|false|null=implicit', + 'mixed~0|0.0|\'\'|\'0\'|array{}|false|null=implicit', ], [ [ @@ -2396,7 +2396,7 @@ public function dataIntersect(): array new HasOffsetType(new ConstantStringType('a')), ], ConstantArrayType::class, - 'array(\'a\' => \'foo\')', + 'array{a: \'foo\'}', ], [ [ @@ -2432,7 +2432,7 @@ public function dataIntersect(): array new HasOffsetType(new ConstantStringType('b')), ], ConstantArrayType::class, - 'array(\'b\' => \'foo\')', + 'array{b: \'foo\'}', ], [ [ @@ -2446,7 +2446,7 @@ public function dataIntersect(): array new HasOffsetType(new ConstantStringType('a')), ], ConstantArrayType::class, - 'array(\'a\' => \'foo\')', + 'array{a: \'foo\'}', ], [ [ @@ -2523,7 +2523,7 @@ public function dataIntersect(): array new NonEmptyArrayType(), ], ConstantArrayType::class, - 'array(string)', + 'array{string}', ], [ [ @@ -2989,7 +2989,7 @@ public function dataIntersect(): array new HasOffsetType(new ConstantStringType('a')), ], ConstantArrayType::class, - 'array(\'a\' => int, \'b\' => int)', + 'array{a: int, b: int}', ], [ [ @@ -3255,13 +3255,13 @@ public function dataRemove(): array StaticTypeFactory::truthy(), StaticTypeFactory::falsey(), MixedType::class, - 'mixed~0|0.0|\'\'|\'0\'|array()|false|null', + 'mixed~0|0.0|\'\'|\'0\'|array{}|false|null', ], [ StaticTypeFactory::falsey(), StaticTypeFactory::truthy(), UnionType::class, - '0|0.0|\'\'|\'0\'|array()|false|null', + '0|0.0|\'\'|\'0\'|array{}|false|null', ], [ new BooleanType(), @@ -3389,7 +3389,7 @@ public function dataRemove(): array ), new ConstantArrayType([], []), ConstantArrayType::class, - 'array(string)', + 'array{string}', ], [ new IntersectionType([ @@ -3404,7 +3404,7 @@ public function dataRemove(): array new ArrayType(new MixedType(), new MixedType()), new NonEmptyArrayType(), ConstantArrayType::class, - 'array()', + 'array{}', ], [ new ArrayType(new MixedType(), new MixedType()), @@ -3578,7 +3578,7 @@ public function dataRemove(): array ], 2, [1]), new HasOffsetType(new ConstantIntegerType(1)), ConstantArrayType::class, - 'array(string)', + 'array{string}', ], [ new ConstantArrayType([ @@ -3648,7 +3648,7 @@ public function testSpecificUnionConstantArray(): void } $resultType = TypeCombinator::union(...$arrays); $this->assertInstanceOf(ConstantArrayType::class, $resultType); - $this->assertSame('array(0 => string, ?\'test\' => string, ?1 => string, ?2 => string, ?3 => string, ?4 => string)', $resultType->describe(VerbosityLevel::precise())); + $this->assertSame('array{0: string, test?: string, 1?: string, 2?: string, 3?: string, 4?: string}', $resultType->describe(VerbosityLevel::precise())); } } diff --git a/tests/PHPStan/Type/UnionTypeTest.php b/tests/PHPStan/Type/UnionTypeTest.php index 9ba73d7e06..44ee94356e 100644 --- a/tests/PHPStan/Type/UnionTypeTest.php +++ b/tests/PHPStan/Type/UnionTypeTest.php @@ -592,7 +592,7 @@ public function dataDescribe(): array ]), new ConstantStringType('aaa') ), - '\'aaa\'|array(\'a\' => int|string, \'b\' => bool|float)', + '\'aaa\'|array{a: int|string, b: bool|float}', 'array|string', ], [ @@ -613,7 +613,7 @@ public function dataDescribe(): array ]), new ConstantStringType('aaa') ), - '\'aaa\'|array(\'a\' => string, \'b\' => bool)|array(\'b\' => int, \'c\' => float)', + '\'aaa\'|array{a: string, b: bool}|array{b: int, c: float}', 'array|string', ], [ @@ -634,7 +634,7 @@ public function dataDescribe(): array ]), new ConstantStringType('aaa') ), - '\'aaa\'|array(\'a\' => string, \'b\' => bool)|array(\'c\' => int, \'d\' => float)', + '\'aaa\'|array{a: string, b: bool}|array{c: int, d: float}', 'array|string', ], [ @@ -654,7 +654,7 @@ public function dataDescribe(): array new FloatType(), ]) ), - 'array(0 => int|string, ?1 => bool, ?2 => float)', + 'array{0: int|string, 1?: bool, 2?: float}', 'array', ], [ @@ -666,7 +666,7 @@ public function dataDescribe(): array new ConstantStringType('barrr'), ]) ), - 'array()|array(\'foooo\' => \'barrr\')', + 'array{}|array{foooo: \'barrr\'}', 'array', ], [ diff --git a/tests/e2e/ResultCacheEndToEndTest.php b/tests/e2e/ResultCacheEndToEndTest.php index d9a12e7a4a..e689bb4f6f 100644 --- a/tests/e2e/ResultCacheEndToEndTest.php +++ b/tests/e2e/ResultCacheEndToEndTest.php @@ -87,7 +87,7 @@ private function runPhpstanWithErrors(): void $this->assertSame('Parameter #1 $source of function token_get_all expects string, PhpParser\Node\Expr\MethodCall given.', $result['files'][$fileHelper->normalizePath(__DIR__ . '/PHP-Parser/lib/PhpParser/Lexer.php')]['messages'][0]['message']); $this->assertSame('Parameter #1 $code of method PhpParser\Lexer::startLexing() expects PhpParser\Node\Expr\MethodCall, string given.', $result['files'][$fileHelper->normalizePath(__DIR__ . '/PHP-Parser/lib/PhpParser/ParserAbstract.php')]['messages'][0]['message']); - $this->assertSame('Parameter #1 (array(\'foo\')) of echo cannot be converted to string.', $result['files'][$fileHelper->normalizePath(__DIR__ . '/PHP-Parser/lib/bootstrap.php')]['messages'][0]['message']); + $this->assertSame('Parameter #1 (array{\'foo\'}) of echo cannot be converted to string.', $result['files'][$fileHelper->normalizePath(__DIR__ . '/PHP-Parser/lib/bootstrap.php')]['messages'][0]['message']); $this->assertResultCache(__DIR__ . '/resultCache_2.php'); } From b8445adebed303aea6611e73ce04e389157f1eb7 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 14 Oct 2021 09:24:08 +0200 Subject: [PATCH 0442/1284] Fix baseline --- phpstan-baseline.neon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index f04295337a..1a4f865252 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -235,7 +235,7 @@ parameters: path: src/Rules/Api/ApiTraitUseRule.php - - message: "#^Binary operation \"\\+\" between array{class\\-string\\} and array\\\\|false results in an error\\.$#" + message: "#^Binary operation \"\\+\" between array\\{class\\-string\\\\} and array\\\\|false results in an error\\.$#" count: 1 path: src/Rules/Registry.php From a61962e805e3bc33a0a6b7d2d6cd1f79fe65be50 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 14 Oct 2021 09:25:06 +0200 Subject: [PATCH 0443/1284] Fix test --- tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php b/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php index 59c8dfda09..0bcb356abe 100644 --- a/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php @@ -55,7 +55,7 @@ public function testRule(): void 25, ], [ - 'Parameter #1 $i of callable array{$this(CallCallables\\Foo}, \'doBar\') expects int, string given.', + 'Parameter #1 $i of callable array{$this(CallCallables\\Foo), \'doBar\'} expects int, string given.', 33, ], [ From 6b1e80b924630d90ad06b91383be54f7f43d0311 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 13 Oct 2021 20:00:15 +0200 Subject: [PATCH 0444/1284] `exit()` status should be in the range 0 to 254 --- resources/functionMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index a79cc24a97..01f5222db1 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -2635,7 +2635,7 @@ 'exif_read_data' => ['array|false', 'filename'=>'string|resource', 'sections_needed='=>'string', 'sub_arrays='=>'bool', 'read_thumbnail='=>'bool'], 'exif_tagname' => ['string|false', 'index'=>'int'], 'exif_thumbnail' => ['string|false', 'filename'=>'string', '&w_width='=>'int', '&w_height='=>'int', '&w_imagetype='=>'int'], -'exit' => ['', 'status'=>'string|int'], +'exit' => ['', 'status'=>'string|int<0,254>'], 'exp' => ['float', 'number'=>'float'], 'expect_expectl' => ['int', 'expect'=>'resource', 'cases'=>'array', 'match='=>'array'], 'expect_popen' => ['resource|false', 'command'=>'string'], From 14d72b698961782f18055c19dd1e51e4d5270a08 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Thu, 14 Oct 2021 14:06:52 +0100 Subject: [PATCH 0445/1284] functionMap: fix callable signature for sapi_windows_set_ctrl_handler() --- resources/functionMap_php74delta.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/functionMap_php74delta.php b/resources/functionMap_php74delta.php index 73832288f7..3a831fc9ef 100644 --- a/resources/functionMap_php74delta.php +++ b/resources/functionMap_php74delta.php @@ -45,7 +45,7 @@ 'password_needs_rehash' => ['bool', 'hash'=>'string', 'algo'=>'string|null', 'options='=>'array'], 'preg_replace_callback' => ['string|array|null', 'regex'=>'string|array', 'callback'=>'callable', 'subject'=>'string|array', 'limit='=>'int', '&w_count='=>'int', 'flags='=>'int'], 'preg_replace_callback_array' => ['string|array|null', 'pattern'=>'array', 'subject'=>'string|array', 'limit='=>'int', '&w_count='=>'int', 'flags='=>'int'], - 'sapi_windows_set_ctrl_handler' => ['bool', 'callable'=>'callable', 'add='=>'bool'], + 'sapi_windows_set_ctrl_handler' => ['bool', 'callable'=>'callable(int):void', 'add='=>'bool'], 'ReflectionProperty::getType' => ['?ReflectionType'], 'ReflectionProperty::hasType' => ['bool'], 'ReflectionProperty::isInitialized' => ['bool', 'object='=>'?object'], From c5ce0b1bbbc522a1c88555fe8e90e91cfd56c9fa Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 15 Oct 2021 12:29:48 +0200 Subject: [PATCH 0446/1284] Autowire parser instance that doesn't truncate function bodies --- conf/config.neon | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index da89b51574..66e44ef7d4 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -387,6 +387,7 @@ services: - class: PHPStan\Analyser\FileAnalyser arguments: + parser: @defaultAnalysisParser reportUnmatchedIgnoredErrors: %reportUnmatchedIgnoredErrors% - @@ -405,6 +406,7 @@ services: - class: PHPStan\Analyser\NodeScopeResolver arguments: + parser: @defaultAnalysisParser classReflector: @nodeScopeResolverClassReflector polluteScopeWithLoopInitialAssignments: %polluteScopeWithLoopInitialAssignments% polluteScopeWithAlwaysIterableForeach: %polluteScopeWithAlwaysIterableForeach% @@ -466,6 +468,8 @@ services: - class: PHPStan\Dependency\ExportedNodeFetcher + arguments: + parser: @defaultAnalysisParser - class: PHPStan\Dependency\ExportedNodeResolver @@ -574,12 +578,6 @@ services: maximumNumberOfProcesses: %parallel.maximumNumberOfProcesses% minimumNumberOfJobsPerProcess: %parallel.minimumNumberOfJobsPerProcess% - - - class: PHPStan\Parser\CachedParser - arguments: - originalParser: @pathRoutingParser - cachedNodesByStringCountMax: %cache.nodesByStringCountMax% - - class: PHPStan\Parser\FunctionCallStatementFinder @@ -588,6 +586,8 @@ services: - implement: PHPStan\Reflection\FunctionReflectionFactory + arguments: + parser: @defaultAnalysisParser - class: PHPStan\Reflection\Annotations\AnnotationsMethodsClassReflectionExtension @@ -600,6 +600,8 @@ services: - class: PHPStan\Reflection\BetterReflection\SourceLocator\FileNodesFetcher + arguments: + parser: @defaultAnalysisParser - class: PHPStan\Reflection\BetterReflection\SourceLocator\AutoloadSourceLocator @@ -641,11 +643,14 @@ services: - class: PHPStan\Reflection\Php\PhpClassReflectionExtension arguments: + parser: @defaultAnalysisParser inferPrivatePropertyTypeFromConstructor: %inferPrivatePropertyTypeFromConstructor% universalObjectCratesClasses: %universalObjectCratesClasses% - implement: PHPStan\Reflection\Php\PhpMethodReflectionFactory + arguments: + parser: @defaultAnalysisParser - class: PHPStan\Reflection\Php\Soap\SoapClientMethodsClassReflectionExtension @@ -837,6 +842,8 @@ services: - class: PHPStan\Type\FileTypeMapper + arguments: + phpParser: @defaultAnalysisParser - class: PHPStan\Type\TypeAliasResolver @@ -1511,7 +1518,8 @@ services: class: PHPStan\Parser\RichParser arguments: parser: @currentPhpVersionPhpParser - autowired: no + autowired: + - PHPStan\Parser\Parser currentPhpVersionSimpleParser: class: PHPStan\Parser\CleaningParser @@ -1525,10 +1533,17 @@ services: parser: @currentPhpVersionPhpParser autowired: no + defaultAnalysisParser: + class: PHPStan\Parser\CachedParser + arguments: + originalParser: @pathRoutingParser + cachedNodesByStringCountMax: %cache.nodesByStringCountMax% + autowired: false + phpParserDecorator: class: PHPStan\Parser\PhpParserDecorator arguments: - wrappedParser: @PHPStan\Parser\Parser + wrappedParser: @defaultAnalysisParser autowired: - PhpParser\Parser @@ -1550,6 +1565,7 @@ services: stubPhpDocProvider: class: PHPStan\PhpDoc\StubPhpDocProvider arguments: + parser: @defaultAnalysisParser stubFiles: %stubFiles% # Reflection providers From da203c034c97a7be4ec435c5028252da53ad5e2b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 15 Oct 2021 12:36:06 +0200 Subject: [PATCH 0447/1284] Fix --- src/Testing/PHPStanTestCase.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Testing/PHPStanTestCase.php b/src/Testing/PHPStanTestCase.php index 6bda3dd2bf..a04eeffce4 100644 --- a/src/Testing/PHPStanTestCase.php +++ b/src/Testing/PHPStanTestCase.php @@ -18,6 +18,7 @@ use PHPStan\DependencyInjection\Type\OperatorTypeSpecifyingExtensionRegistryProvider; use PHPStan\File\FileHelper; use PHPStan\Parser\CachedParser; +use PHPStan\Parser\Parser; use PHPStan\PhpDoc\TypeNodeResolver; use PHPStan\PhpDoc\TypeStringResolver; use PHPStan\Reflection\ReflectionProvider; @@ -77,10 +78,10 @@ public static function getAdditionalConfigFiles(): array return []; } - public function getParser(): \PHPStan\Parser\Parser + public function getParser(): Parser { - /** @var \PHPStan\Parser\Parser $parser */ - $parser = self::getContainer()->getByType(CachedParser::class); + /** @var Parser $parser */ + $parser = self::getContainer()->getService('defaultAnalysisParser'); return $parser; } From ba16285d988af367d1907c5d720e6bde99860653 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 15 Oct 2021 12:39:46 +0200 Subject: [PATCH 0448/1284] Fix CS --- src/Testing/PHPStanTestCase.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Testing/PHPStanTestCase.php b/src/Testing/PHPStanTestCase.php index a04eeffce4..d537617cfd 100644 --- a/src/Testing/PHPStanTestCase.php +++ b/src/Testing/PHPStanTestCase.php @@ -17,7 +17,6 @@ use PHPStan\DependencyInjection\Type\DynamicReturnTypeExtensionRegistryProvider; use PHPStan\DependencyInjection\Type\OperatorTypeSpecifyingExtensionRegistryProvider; use PHPStan\File\FileHelper; -use PHPStan\Parser\CachedParser; use PHPStan\Parser\Parser; use PHPStan\PhpDoc\TypeNodeResolver; use PHPStan\PhpDoc\TypeStringResolver; From 732d03389e801cf3949988f65297fb6b9450f907 Mon Sep 17 00:00:00 2001 From: Matt Glaman Date: Fri, 15 Oct 2021 05:42:24 -0500 Subject: [PATCH 0449/1284] define: case_insensitive is ignored, case-insensitive constants are no longer supported --- conf/config.level0.neon | 1 + src/Php/PhpVersion.php | 5 ++ src/Rules/Functions/DefineParametersRule.php | 54 +++++++++++++++++++ .../Functions/DefineParametersRuleTest.php | 32 +++++++++++ .../Rules/Functions/data/call-to-define.php | 6 +++ 5 files changed, 98 insertions(+) create mode 100644 src/Rules/Functions/DefineParametersRule.php create mode 100644 tests/PHPStan/Rules/Functions/DefineParametersRuleTest.php create mode 100644 tests/PHPStan/Rules/Functions/data/call-to-define.php diff --git a/conf/config.level0.neon b/conf/config.level0.neon index 64ec96e8cc..d9e4a0f6bc 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -36,6 +36,7 @@ rules: - PHPStan\Rules\Functions\ArrowFunctionReturnNullsafeByRefRule - PHPStan\Rules\Functions\CallToFunctionParametersRule - PHPStan\Rules\Functions\ClosureAttributesRule + - PHPStan\Rules\Functions\DefineParametersRule - PHPStan\Rules\Functions\ExistingClassesInArrowFunctionTypehintsRule - PHPStan\Rules\Functions\ExistingClassesInClosureTypehintsRule - PHPStan\Rules\Functions\ExistingClassesInTypehintsRule diff --git a/src/Php/PhpVersion.php b/src/Php/PhpVersion.php index 100cc69ed0..f085f65192 100644 --- a/src/Php/PhpVersion.php +++ b/src/Php/PhpVersion.php @@ -147,4 +147,9 @@ public function supportsEnums(): bool return $this->versionId >= 80100; } + public function supportsCaseInsensitiveConstantNames(): bool + { + return $this->versionId < 80000; + } + } diff --git a/src/Rules/Functions/DefineParametersRule.php b/src/Rules/Functions/DefineParametersRule.php new file mode 100644 index 0000000000..967b457208 --- /dev/null +++ b/src/Rules/Functions/DefineParametersRule.php @@ -0,0 +1,54 @@ + + */ +class DefineParametersRule implements \PHPStan\Rules\Rule +{ + + private PhpVersion $phpVersion; + + public function __construct(PhpVersion $phpVersion) + { + $this->phpVersion = $phpVersion; + } + + public function getNodeType(): string + { + return FuncCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!($node->name instanceof \PhpParser\Node\Name)) { + return []; + } + if ($this->phpVersion->supportsCaseInsensitiveConstantNames()) { + return []; + } + $name = strtolower((string) $node->name); + if ($name !== 'define') { + return []; + } + $args = $node->getArgs(); + $argsCount = count($args); + // Expects 2 or 3, 1 arg is caught by CallToFunctionParametersRule + if ($argsCount < 3) { + return []; + } + return [ + RuleErrorBuilder::message( + 'Argument #3 ($case_insensitive) is ignored since declaration of case-insensitive constants is no longer supported.' + )->line($node->getLine())->build(), + ]; + } + +} diff --git a/tests/PHPStan/Rules/Functions/DefineParametersRuleTest.php b/tests/PHPStan/Rules/Functions/DefineParametersRuleTest.php new file mode 100644 index 0000000000..02f7f6c977 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/DefineParametersRuleTest.php @@ -0,0 +1,32 @@ + + */ +class DefineParametersRuleTest extends \PHPStan\Testing\RuleTestCase +{ + + protected function getRule(): \PHPStan\Rules\Rule + { + return new DefineParametersRule(new PhpVersion(PHP_VERSION_ID)); + } + + public function testFile(): void + { + if (PHP_VERSION_ID < 80000) { + $this->analyse([__DIR__ . '/data/call-to-define.php'], []); + } else { + $this->analyse([__DIR__ . '/data/call-to-define.php'], [ + [ + 'Argument #3 ($case_insensitive) is ignored since declaration of case-insensitive constants is no longer supported.', + 3, + ], + ]); + } + } + +} diff --git a/tests/PHPStan/Rules/Functions/data/call-to-define.php b/tests/PHPStan/Rules/Functions/data/call-to-define.php new file mode 100644 index 0000000000..fbb38e6306 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/call-to-define.php @@ -0,0 +1,6 @@ + Date: Thu, 14 Oct 2021 14:35:50 -0500 Subject: [PATCH 0450/1284] NativeFunctionReflection deprecated support --- .../Native/NativeFunctionReflection.php | 8 +++- .../NativeFunctionReflectionProvider.php | 5 ++- .../Reflection/ReflectionProviderTest.php | 39 +++++++++++++++++++ 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/src/Reflection/Native/NativeFunctionReflection.php b/src/Reflection/Native/NativeFunctionReflection.php index 901d61e443..edda6f7c6a 100644 --- a/src/Reflection/Native/NativeFunctionReflection.php +++ b/src/Reflection/Native/NativeFunctionReflection.php @@ -18,6 +18,8 @@ class NativeFunctionReflection implements \PHPStan\Reflection\FunctionReflection private TrinaryLogic $hasSideEffects; + private bool $isDeprecated; + /** * @param string $name * @param \PHPStan\Reflection\ParametersAcceptor[] $variants @@ -28,13 +30,15 @@ public function __construct( string $name, array $variants, ?Type $throwType, - TrinaryLogic $hasSideEffects + TrinaryLogic $hasSideEffects, + bool $isDeprecated ) { $this->name = $name; $this->variants = $variants; $this->throwType = $throwType; $this->hasSideEffects = $hasSideEffects; + $this->isDeprecated = $isDeprecated; } public function getName(): string @@ -62,7 +66,7 @@ public function getDeprecatedDescription(): ?string public function isDeprecated(): TrinaryLogic { - return TrinaryLogic::createNo(); + return TrinaryLogic::createFromBoolean($this->isDeprecated); } public function isInternal(): TrinaryLogic diff --git a/src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php b/src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php index 2e466e1f8c..98192be818 100644 --- a/src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php +++ b/src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php @@ -144,6 +144,7 @@ public function findFunctionReflection(string $functionName): ?NativeFunctionRef } $throwType = null; + $isDeprecated = false; try { $reflectionFunction = $this->functionReflector->reflect($functionName); if ($reflectionFunction->getFileName() !== null) { @@ -154,6 +155,7 @@ public function findFunctionReflection(string $functionName): ?NativeFunctionRef if ($throwsTag !== null) { $throwType = $throwsTag->getType(); } + $isDeprecated = $reflectionFunction->isDeprecated(); } } catch (\PHPStan\BetterReflection\Reflector\Exception\IdentifierNotFound $e) { // pass @@ -165,7 +167,8 @@ public function findFunctionReflection(string $functionName): ?NativeFunctionRef $lowerCasedFunctionName, $variants, $throwType, - $hasSideEffects + $hasSideEffects, + $isDeprecated ); self::$functionMap[$lowerCasedFunctionName] = $functionReflection; diff --git a/tests/PHPStan/Reflection/ReflectionProviderTest.php b/tests/PHPStan/Reflection/ReflectionProviderTest.php index 7af3d45fc7..322d881ecb 100644 --- a/tests/PHPStan/Reflection/ReflectionProviderTest.php +++ b/tests/PHPStan/Reflection/ReflectionProviderTest.php @@ -4,6 +4,7 @@ use PhpParser\Node\Name; use PHPStan\Testing\PHPStanTestCase; +use PHPStan\TrinaryLogic; use PHPStan\Type\ObjectType; use PHPStan\Type\Type; use PHPStan\Type\VerbosityLevel; @@ -62,6 +63,44 @@ public function testFunctionThrowType(string $functionName, ?Type $expectedThrow ); } + public function dataFunctionDeprecated(): iterable + { + if (PHP_VERSION_ID < 80000) { + yield 'create_function' => [ + 'create_function', + PHP_VERSION_ID >= 70200, + ]; + yield 'each' => [ + 'each', + PHP_VERSION_ID >= 70200, + ]; + } + + if (PHP_VERSION_ID < 90000) { + yield 'date_sunrise' => [ + 'date_sunrise', + PHP_VERSION_ID >= 80100, + ]; + } + + yield 'strtolower' => [ + 'strtolower', + false, + ]; + } + + /** + * @dataProvider dataFunctionDeprecated + * @param string $functionName + * @param bool $isDeprecated + */ + public function testFunctionDeprecated(string $functionName, bool $isDeprecated): void + { + $reflectionProvider = $this->createReflectionProvider(); + $function = $reflectionProvider->getFunction(new Name($functionName), null); + $this->assertEquals(TrinaryLogic::createFromBoolean($isDeprecated), $function->isDeprecated()); + } + public function dataMethodThrowType(): array { return [ From 082b08ba11901b77ecc504520fe089703af67122 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 14 Oct 2021 20:50:41 +0200 Subject: [PATCH 0451/1284] More precise dirname signature --- resources/functionMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 01f5222db1..52be86f48c 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -1855,7 +1855,7 @@ 'DirectoryIterator::setFileClass' => ['void', 'class_name='=>'string'], 'DirectoryIterator::setInfoClass' => ['void', 'class_name='=>'string'], 'DirectoryIterator::valid' => ['bool'], -'dirname' => ['string', 'path'=>'string', 'levels='=>'int'], +'dirname' => ['string', 'path'=>'string', 'levels='=>'positive-int'], 'disk_free_space' => ['float|false', 'path'=>'string'], 'disk_total_space' => ['float|false', 'path'=>'string'], 'diskfreespace' => ['float|false', 'path'=>'string'], From e248b055b15e836fb0d249f88c300e2eec762f4d Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 14 Oct 2021 20:39:19 +0200 Subject: [PATCH 0452/1284] More precise chunk_split signature --- resources/functionMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 52be86f48c..1741f816db 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -946,7 +946,7 @@ 'chown' => ['bool', 'filename'=>'string', 'user'=>'string|int'], 'chr' => ['non-empty-string', 'ascii'=>'int'], 'chroot' => ['bool', 'directory'=>'string'], -'chunk_split' => ['string', 'str'=>'string', 'chunklen='=>'int', 'ending='=>'string'], +'chunk_split' => ['string', 'str'=>'string', 'chunklen='=>'positive-int', 'ending='=>'string'], 'class_alias' => ['bool', 'user_class_name'=>'string', 'alias_name'=>'string', 'autoload='=>'bool'], 'class_exists' => ['bool', 'classname'=>'string', 'autoload='=>'bool'], 'class_implements' => ['array|false', 'what'=>'object|string', 'autoload='=>'bool'], From 0c3ff908c65e0ec1de654828652d0adc80f3f247 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 14 Oct 2021 20:32:07 +0200 Subject: [PATCH 0453/1284] More precise array_ unshift signature --- resources/functionMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 1741f816db..65138b0309 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -318,7 +318,7 @@ 'array_uintersect_uassoc' => ['array', 'arr1'=>'array', 'arr2'=>'array', 'data_compare_func'=>'callable(mixed,mixed):int', 'key_compare_func'=>'callable(mixed,mixed):int'], 'array_uintersect_uassoc\'1' => ['array', 'arr1'=>'array', 'arr2'=>'array', 'arr3'=>'array', 'arg4'=>'array|callable(mixed,mixed):int', 'arg5'=>'array|callable(mixed,mixed):int', '...rest='=>'array|callable(mixed,mixed):int'], 'array_unique' => ['array', 'array'=>'array', 'flags='=>'int'], -'array_unshift' => ['int', '&rw_stack'=>'array', 'var'=>'mixed', '...vars='=>'mixed'], +'array_unshift' => ['positive-int', '&rw_stack'=>'array', 'var'=>'mixed', '...vars='=>'mixed'], 'array_values' => ['array', 'input'=>'array'], 'array_walk' => ['bool', '&rw_input'=>'array|object', 'callback'=>'callable', 'userdata='=>'mixed'], 'array_walk_recursive' => ['bool', '&rw_input'=>'array|object', 'callback'=>'callable', 'userdata='=>'mixed'], From d4579a3884fe328eab54398121cdd12763c8fa3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Paris?= Date: Sun, 17 Oct 2021 13:41:33 +0200 Subject: [PATCH 0454/1284] Document possible return type for PDO::lastInsertId() Fixes https://github.com/phpstan/phpstan/issues/5788 --- resources/functionMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 65138b0309..1fa4e8a988 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -8443,7 +8443,7 @@ 'PDO::getAttribute' => ['', 'attribute'=>'int'], 'PDO::getAvailableDrivers' => ['array'], 'PDO::inTransaction' => ['bool'], -'PDO::lastInsertId' => ['string', 'seqname='=>'string'], +'PDO::lastInsertId' => ['string|false', 'seqname='=>'string'], 'PDO::pgsqlCopyFromArray' => ['bool', 'table_name'=>'string', 'rows'=>'array', 'delimiter'=>'string', 'null_as'=>'string', 'fields'=>'string'], 'PDO::pgsqlCopyFromFile' => ['bool', 'table_name'=>'string', 'filename'=>'string', 'delimiter'=>'string', 'null_as'=>'string', 'fields'=>'string'], 'PDO::pgsqlCopyToArray' => ['array', 'table_name'=>'string', 'delimiter'=>'string', 'null_as'=>'string', 'fields'=>'string'], From 2c1107588603afaa8cd3e97165b7eb1736cb4393 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 17 Oct 2021 14:23:27 +0200 Subject: [PATCH 0455/1284] More precise json_* signature --- resources/functionMap.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 1fa4e8a988..ad4bfa0c81 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -5694,8 +5694,8 @@ 'join' => ['string', 'glue'=>'string', 'pieces'=>'array'], 'join\'1' => ['string', 'pieces'=>'array'], 'jpeg2wbmp' => ['bool', 'jpegname'=>'string', 'wbmpname'=>'string', 'dest_height'=>'int', 'dest_width'=>'int', 'threshold'=>'int'], -'json_decode' => ['mixed', 'json'=>'string', 'assoc='=>'bool', 'depth='=>'int', 'options='=>'int'], -'json_encode' => ['string|false', 'data'=>'mixed', 'options='=>'int', 'depth='=>'int'], +'json_decode' => ['mixed', 'json'=>'string', 'assoc='=>'bool', 'depth='=>'positive-int', 'options='=>'int'], +'json_encode' => ['string|false', 'data'=>'mixed', 'options='=>'int', 'depth='=>'positive-int'], 'json_last_error' => ['int'], 'json_last_error_msg' => ['string'], 'JsonIncrementalParser::__construct' => ['void', 'depth'=>'', 'options'=>''], From 50fba6b75735757de673cc917300702e405b06db Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Wed, 20 Oct 2021 14:18:24 +0200 Subject: [PATCH 0456/1284] Implement ArrayAccess in ext-ds --- stubs/ext-ds.stub | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/stubs/ext-ds.stub b/stubs/ext-ds.stub index df3a08d9e7..c2171b3691 100644 --- a/stubs/ext-ds.stub +++ b/stubs/ext-ds.stub @@ -2,6 +2,7 @@ namespace Ds; +use ArrayAccess; use Countable; use JsonSerializable; use OutOfBoundsException; @@ -92,8 +93,9 @@ final class Deque implements Sequence * @template TKey * @template TValue * @implements Collection + * @implements ArrayAccess */ -final class Map implements Collection +final class Map implements Collection, ArrayAccess { /** * @param iterable $values @@ -380,8 +382,9 @@ final class Pair implements JsonSerializable /** * @template TValue * @extends Collection + * @extends ArrayAccess */ -interface Sequence extends Collection +interface Sequence extends Collection, ArrayAccess { /** * @param callable(TValue): TValue $callback @@ -597,8 +600,9 @@ final class Vector implements Sequence /** * @template TValue * @implements Collection + * @implements ArrayAccess */ -final class Set implements Collection +final class Set implements Collection, ArrayAccess { /** * @param iterable $values @@ -752,8 +756,9 @@ final class Set implements Collection /** * @template TValue * @implements Collection + * @implements ArrayAccess */ -final class Stack implements Collection +final class Stack implements Collection, ArrayAccess { /** * @param iterable $values @@ -804,8 +809,9 @@ final class Stack implements Collection /** * @template TValue * @implements Collection + * @implements ArrayAccess */ -final class Queue implements Collection +final class Queue implements Collection, ArrayAccess { /** * @param iterable $values From 2f53b70f61317b411f207e4209779cf7a1c4c6fc Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 22 Oct 2021 13:51:50 +0200 Subject: [PATCH 0457/1284] Do not autowire parsers at all --- conf/config.neon | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index 66e44ef7d4..71c37b9ca6 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1518,8 +1518,7 @@ services: class: PHPStan\Parser\RichParser arguments: parser: @currentPhpVersionPhpParser - autowired: - - PHPStan\Parser\Parser + autowired: no currentPhpVersionSimpleParser: class: PHPStan\Parser\CleaningParser @@ -1544,8 +1543,7 @@ services: class: PHPStan\Parser\PhpParserDecorator arguments: wrappedParser: @defaultAnalysisParser - autowired: - - PhpParser\Parser + autowired: false currentPhpVersionLexer: class: PhpParser\Lexer From 99e4ae0dced58fe0be7a7aec3168a5e9d639240a Mon Sep 17 00:00:00 2001 From: Can Vural Date: Wed, 20 Oct 2021 16:32:01 +0200 Subject: [PATCH 0458/1284] Variadic parameters in PHP8 can have int|string key --- src/Analyser/DirectScopeFactory.php | 8 +- src/Analyser/LazyScopeFactory.php | 2 + src/Analyser/MutatingScope.php | 21 ++++- src/Testing/PHPStanTestCase.php | 4 +- src/Type/TypeCombinator.php | 2 + .../Analyser/LegacyNodeScopeResolverTest.php | 4 +- .../Analyser/NodeScopeResolverTest.php | 16 +++- tests/PHPStan/Analyser/data/bug-2600-php8.php | 88 +++++++++++++++++++ tests/PHPStan/Analyser/data/bug-4902-php8.php | 53 +++++++++++ .../Analyser/data/variadic-parameter-php8.php | 18 ++++ 10 files changed, 209 insertions(+), 7 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/bug-2600-php8.php create mode 100644 tests/PHPStan/Analyser/data/bug-4902-php8.php create mode 100644 tests/PHPStan/Analyser/data/variadic-parameter-php8.php diff --git a/src/Analyser/DirectScopeFactory.php b/src/Analyser/DirectScopeFactory.php index eb7767275e..c14ad9ff91 100644 --- a/src/Analyser/DirectScopeFactory.php +++ b/src/Analyser/DirectScopeFactory.php @@ -5,6 +5,7 @@ use PHPStan\DependencyInjection\Container; use PHPStan\DependencyInjection\Type\DynamicReturnTypeExtensionRegistryProvider; use PHPStan\DependencyInjection\Type\OperatorTypeSpecifyingExtensionRegistryProvider; +use PHPStan\Php\PhpVersion; use PHPStan\Reflection\ParametersAcceptor; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\Properties\PropertyReflectionFinder; @@ -39,6 +40,8 @@ class DirectScopeFactory implements ScopeFactory /** @var string[] */ private array $dynamicConstantNames; + private PhpVersion $phpVersion; + public function __construct( string $scopeClass, ReflectionProvider $reflectionProvider, @@ -50,7 +53,8 @@ public function __construct( \PHPStan\Parser\Parser $parser, NodeScopeResolver $nodeScopeResolver, bool $treatPhpDocTypesAsCertain, - Container $container + Container $container, + PhpVersion $phpVersion ) { $this->scopeClass = $scopeClass; @@ -64,6 +68,7 @@ public function __construct( $this->nodeScopeResolver = $nodeScopeResolver; $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; $this->dynamicConstantNames = $container->getParameter('dynamicConstantNames'); + $this->phpVersion = $phpVersion; } /** @@ -121,6 +126,7 @@ public function create( $this->parser, $this->nodeScopeResolver, $context, + $this->phpVersion, $declareStrictTypes, $constantTypes, $function, diff --git a/src/Analyser/LazyScopeFactory.php b/src/Analyser/LazyScopeFactory.php index a85269526d..c9a830c786 100644 --- a/src/Analyser/LazyScopeFactory.php +++ b/src/Analyser/LazyScopeFactory.php @@ -6,6 +6,7 @@ use PHPStan\DependencyInjection\Container; use PHPStan\DependencyInjection\Type\DynamicReturnTypeExtensionRegistryProvider; use PHPStan\DependencyInjection\Type\OperatorTypeSpecifyingExtensionRegistryProvider; +use PHPStan\Php\PhpVersion; use PHPStan\Reflection\ParametersAcceptor; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\Properties\PropertyReflectionFinder; @@ -89,6 +90,7 @@ public function create( $this->container->getService('currentPhpVersionSimpleParser'), $this->container->getByType(NodeScopeResolver::class), $context, + $this->container->getByType(PhpVersion::class), $declareStrictTypes, $constantTypes, $function, diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index de1467d0a5..6e785c8d45 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -28,6 +28,7 @@ use PhpParser\NodeFinder; use PHPStan\Node\ExecutionEndNode; use PHPStan\Parser\Parser; +use PHPStan\Php\PhpVersion; use PHPStan\Reflection\ClassConstantReflection; use PHPStan\Reflection\ClassMemberReflection; use PHPStan\Reflection\ClassReflection; @@ -132,6 +133,8 @@ class MutatingScope implements Scope private \PHPStan\Analyser\ScopeContext $context; + private PhpVersion $phpVersion; + /** @var \PHPStan\Type\Type[] */ private array $resolvedTypes = []; @@ -189,6 +192,7 @@ class MutatingScope implements Scope * @param Parser $parser * @param NodeScopeResolver $nodeScopeResolver * @param \PHPStan\Analyser\ScopeContext $context + * @param PhpVersion $phpVersion * @param bool $declareStrictTypes * @param array $constantTypes * @param \PHPStan\Reflection\FunctionReflection|MethodReflection|null $function @@ -218,6 +222,7 @@ public function __construct( Parser $parser, NodeScopeResolver $nodeScopeResolver, ScopeContext $context, + PhpVersion $phpVersion, bool $declareStrictTypes = false, array $constantTypes = [], $function = null, @@ -251,6 +256,7 @@ public function __construct( $this->parser = $parser; $this->nodeScopeResolver = $nodeScopeResolver; $this->context = $context; + $this->phpVersion = $phpVersion; $this->declareStrictTypes = $declareStrictTypes; $this->constantTypes = $constantTypes; $this->function = $function; @@ -2384,6 +2390,7 @@ public function doNotTreatPhpDocTypesAsCertain(): Scope $this->parser, $this->nodeScopeResolver, $this->context, + $this->phpVersion, $this->declareStrictTypes, $this->constantTypes, $this->function, @@ -2954,7 +2961,11 @@ private function enterFunctionLike( foreach (ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getParameters() as $parameter) { $parameterType = $parameter->getType(); if ($parameter->isVariadic()) { - $parameterType = new ArrayType(new IntegerType(), $parameterType); + if ($this->phpVersion->supportsNamedArguments()) { + $parameterType = new ArrayType(new UnionType([new IntegerType(), new StringType()]), $parameterType); + } else { + $parameterType = new ArrayType(new IntegerType(), $parameterType); + } } $variableTypes[$parameter->getName()] = VariableTypeHolder::createYes($parameterType); $nativeExpressionTypes[sprintf('$%s', $parameter->getName())] = $parameter->getNativeType(); @@ -3374,6 +3385,14 @@ public function getFunctionType($type, bool $isNullable, bool $isVariadic): Type ); } if ($isVariadic) { + if ($this->phpVersion->supportsNamedArguments()) { + return new ArrayType(new UnionType([new IntegerType(), new StringType()]), $this->getFunctionType( + $type, + false, + false + )); + } + return new ArrayType(new IntegerType(), $this->getFunctionType( $type, false, diff --git a/src/Testing/PHPStanTestCase.php b/src/Testing/PHPStanTestCase.php index d537617cfd..b38b0fd015 100644 --- a/src/Testing/PHPStanTestCase.php +++ b/src/Testing/PHPStanTestCase.php @@ -18,6 +18,7 @@ use PHPStan\DependencyInjection\Type\OperatorTypeSpecifyingExtensionRegistryProvider; use PHPStan\File\FileHelper; use PHPStan\Parser\Parser; +use PHPStan\Php\PhpVersion; use PHPStan\PhpDoc\TypeNodeResolver; use PHPStan\PhpDoc\TypeStringResolver; use PHPStan\Reflection\ReflectionProvider; @@ -131,7 +132,8 @@ public function createScopeFactory(ReflectionProvider $reflectionProvider, TypeS $this->getParser(), self::getContainer()->getByType(NodeScopeResolver::class), $this->shouldTreatPhpDocTypesAsCertain(), - $container + $container, + $container->getByType(PhpVersion::class) ); } diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index fb6eba781a..034eb62323 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -704,6 +704,8 @@ private static function reduceArrays(array $constantArrays): array public static function intersect(Type ...$types): Type { + $types = array_values($types); + $sortTypes = static function (Type $a, Type $b): int { if (!$a instanceof UnionType || !$b instanceof UnionType) { return 0; diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index d72df9742b..6cdf52184c 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -1284,7 +1284,7 @@ public function dataParameterTypes(): array '$callable', ], [ - 'array', + PHP_VERSION_ID < 80000 ? 'array' : 'array', '$variadicStrings', ], [ @@ -4342,7 +4342,7 @@ public function dataAnonymousFunction(): array '$str', ], [ - 'array', + PHP_VERSION_ID < 80000 ? 'array' : 'array', '$arr', ], [ diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index b095b3db2a..e09cc3a426 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -93,7 +93,11 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/catch-without-variable.php'); } yield from $this->gatherAssertTypes(__DIR__ . '/data/mixed-typehint.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2600.php'); + if (PHP_VERSION_ID >= 80000) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2600-php8.php'); + } else { + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2600.php'); + } yield from $this->gatherAssertTypes(__DIR__ . '/data/array-typehint-without-null-in-phpdoc.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/override-root-scope-variable.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bitwise-not.php'); @@ -427,7 +431,11 @@ public function dataFileAsserts(): iterable if (self::$useStaticReflectionProvider || PHP_VERSION_ID >= 70400) { yield from $this->gatherAssertTypes(__DIR__ . '/data/arrow-function-types.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4902.php'); + if (PHP_VERSION_ID >= 80000) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4902-php8.php'); + } else { + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4902.php'); + } } yield from $this->gatherAssertTypes(__DIR__ . '/data/closure-types.php'); @@ -508,6 +516,10 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4741.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/more-type-strings.php'); + if (PHP_VERSION_ID >= 80000) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/variadic-parameter-php8.php'); + } + yield from $this->gatherAssertTypes(__DIR__ . '/data/eval-implicit-throw.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5628.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5501.php'); diff --git a/tests/PHPStan/Analyser/data/bug-2600-php8.php b/tests/PHPStan/Analyser/data/bug-2600-php8.php new file mode 100644 index 0000000000..5df2b73736 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-2600-php8.php @@ -0,0 +1,88 @@ +', $x); + } + + /** + * @param mixed ...$x + */ + public function doLorem(...$x) { + assertType('array', $x); + } + + public function doIpsum($x = null) { + $args = func_get_args(); + assertType('mixed', $x); + assertType('array', $args); + } +} + +class Bar +{ + /** + * @param string ...$x + */ + public function doFoo($x = null) { + $args = func_get_args(); + assertType('string|null', $x); + assertType('array', $args); + } + + /** + * @param string ...$x + */ + public function doBar($x = null) { + assertType('string|null', $x); + } + + /** + * @param string $x + */ + public function doBaz(...$x) { + assertType('array', $x); + } + + /** + * @param string ...$x + */ + public function doLorem(...$x) { + assertType('array', $x); + } +} + +function foo($x, string ...$y): void +{ + assertType('mixed', $x); + assertType('array', $y); +} + +function ($x, string ...$y): void { + assertType('mixed', $x); + assertType('array', $y); +}; diff --git a/tests/PHPStan/Analyser/data/bug-4902-php8.php b/tests/PHPStan/Analyser/data/bug-4902-php8.php new file mode 100644 index 0000000000..016ac2586f --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-4902-php8.php @@ -0,0 +1,53 @@ += 7.4 + +namespace Bug4902; + +use function PHPStan\Testing\assertType; + +/** + * @template T-wrapper + */ +class Wrapper { + /** @var T-wrapper */ + public $value; + + /** + * @param T-wrapper $value + */ + public function __construct($value) { + $this->value = $value; + } + + /** + * @template T-unwrap + * @param Wrapper $wrapper + * @return T-unwrap + */ + function unwrap(Wrapper $wrapper) { + return $wrapper->value; + } + + /** + * @template T-wrap + * @param T-wrap $value + * + * @return Wrapper + */ + function wrap($value): Wrapper + { + return new Wrapper($value); + } + + + /** + * @template T-all + * @param Wrapper ...$wrappers + */ + function unwrapAllAndWrapAgain(Wrapper ...$wrappers): void { + assertType('array', array_map(function (Wrapper $item) { + return $this->unwrap($item); + }, $wrappers)); + assertType('array', array_map(fn (Wrapper $item) => $this->unwrap($item), $wrappers)); + } + +} diff --git a/tests/PHPStan/Analyser/data/variadic-parameter-php8.php b/tests/PHPStan/Analyser/data/variadic-parameter-php8.php new file mode 100644 index 0000000000..52de2402f1 --- /dev/null +++ b/tests/PHPStan/Analyser/data/variadic-parameter-php8.php @@ -0,0 +1,18 @@ +', $args); + assertType('mixed', $args['foo']); + assertType('mixed', $args['bar']); +} + +function bar(string ...$args) +{ + assertType('array', $args); +} + From e42a6d19a93271c46a924736e0a2e3c7027b97d1 Mon Sep 17 00:00:00 2001 From: Kamil Tekiela Date: Sun, 24 Oct 2021 00:59:38 +0100 Subject: [PATCH 0459/1284] mysqli::fetch_fields does not return false According to PHP source code (both 7 & 8) this function never returns false. It can only return an array. --- resources/functionMap.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index ad4bfa0c81..6be3b147b5 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -7239,7 +7239,7 @@ 'mysqli_fetch_assoc' => ['array|null', 'result'=>'mysqli_result'], 'mysqli_fetch_field' => ['object|false', 'result'=>'mysqli_result'], 'mysqli_fetch_field_direct' => ['object|false', 'result'=>'mysqli_result', 'fieldnr'=>'int'], -'mysqli_fetch_fields' => ['array|false', 'result'=>'mysqli_result'], +'mysqli_fetch_fields' => ['array', 'result'=>'mysqli_result'], 'mysqli_fetch_lengths' => ['array|false', 'result'=>'mysqli_result'], 'mysqli_fetch_object' => ['object|null', 'result'=>'mysqli_result', 'class_name='=>'string', 'params='=>'?array'], 'mysqli_fetch_row' => ['array|null', 'result'=>'mysqli_result'], @@ -7290,7 +7290,7 @@ 'mysqli_result::fetch_assoc' => ['array|null'], 'mysqli_result::fetch_field' => ['object|false'], 'mysqli_result::fetch_field_direct' => ['object|false', 'fieldnr'=>'int'], -'mysqli_result::fetch_fields' => ['array|false'], +'mysqli_result::fetch_fields' => ['array'], 'mysqli_result::fetch_object' => ['object|null', 'class_name='=>'string', 'params='=>'array'], 'mysqli_result::fetch_row' => ['array|null'], 'mysqli_result::field_seek' => ['bool', 'fieldnr'=>'int'], From 669b6cd8cf030df2edd882d04ef41f5149794588 Mon Sep 17 00:00:00 2001 From: Dylan T Date: Sun, 24 Oct 2021 10:01:18 +0100 Subject: [PATCH 0460/1284] FileExcluder: Improve performance of isExcludedFromAnalysing --- src/File/FileExcluder.php | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/src/File/FileExcluder.php b/src/File/FileExcluder.php index 4b60c7d5c4..8a6728a42b 100644 --- a/src/File/FileExcluder.php +++ b/src/File/FileExcluder.php @@ -10,7 +10,15 @@ class FileExcluder * * @var string[] */ - private array $analyseExcludes; + private array $literalAnalyseExcludes = []; + + /** + * fnmatch() patterns to use for excluding files and directories from analysing + * @var string[] + */ + private array $fnmatchAnalyseExcludes = []; + + private int $fnmatchFlags; /** * @param FileHelper $fileHelper @@ -23,7 +31,7 @@ public function __construct( array $stubFiles ) { - $this->analyseExcludes = array_map(function (string $exclude) use ($fileHelper): string { + foreach (array_merge($analyseExcludes, $stubFiles) as $exclude) { $len = strlen($exclude); $trailingDirSeparator = ($len > 0 && in_array($exclude[$len - 1], ['\\', '/'], true)); @@ -34,28 +42,29 @@ public function __construct( } if ($this->isFnmatchPattern($normalized)) { - return $normalized; + $this->fnmatchAnalyseExcludes[] = $normalized; + } else { + $this->literalAnalyseExcludes[] = $fileHelper->absolutizePath($normalized); } + } - return $fileHelper->absolutizePath($normalized); - }, array_merge($analyseExcludes, $stubFiles)); + $isWindows = DIRECTORY_SEPARATOR === '\\'; + if ($isWindows) { + $this->fnmatchFlags = FNM_NOESCAPE | FNM_CASEFOLD; + } else { + $this->fnmatchFlags = 0; + } } public function isExcludedFromAnalysing(string $file): bool { - foreach ($this->analyseExcludes as $exclude) { + foreach ($this->literalAnalyseExcludes as $exclude) { if (strpos($file, $exclude) === 0) { return true; } - - $isWindows = DIRECTORY_SEPARATOR === '\\'; - if ($isWindows) { - $fnmatchFlags = FNM_NOESCAPE | FNM_CASEFOLD; - } else { - $fnmatchFlags = 0; - } - - if ($this->isFnmatchPattern($exclude) && fnmatch($exclude, $file, $fnmatchFlags)) { + } + foreach ($this->fnmatchAnalyseExcludes as $exclude) { + if (fnmatch($exclude, $file, $this->fnmatchFlags)) { return true; } } From 83672a79f870a5aba9f2191957982ef097d2a47d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 24 Oct 2021 17:57:41 +0200 Subject: [PATCH 0461/1284] AnalysisResult and Error are part of BC promise --- src/Analyser/Error.php | 1 + src/Command/AnalysisResult.php | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Analyser/Error.php b/src/Analyser/Error.php index 7a80edbf8b..0dfdb0b900 100644 --- a/src/Analyser/Error.php +++ b/src/Analyser/Error.php @@ -2,6 +2,7 @@ namespace PHPStan\Analyser; +/** @api */ class Error implements \JsonSerializable { diff --git a/src/Command/AnalysisResult.php b/src/Command/AnalysisResult.php index 2398b0833c..9db96212d2 100644 --- a/src/Command/AnalysisResult.php +++ b/src/Command/AnalysisResult.php @@ -4,6 +4,7 @@ use PHPStan\Analyser\Error; +/** @api */ class AnalysisResult { From 27df5cf55ee17cd3368c9cbecb681105def7f581 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 24 Oct 2021 19:03:29 +0200 Subject: [PATCH 0462/1284] Changing analysedPaths does not regenerate the DI container --- src/DependencyInjection/ContainerFactory.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DependencyInjection/ContainerFactory.php b/src/DependencyInjection/ContainerFactory.php index 2cf9f3fe48..1e58172194 100644 --- a/src/DependencyInjection/ContainerFactory.php +++ b/src/DependencyInjection/ContainerFactory.php @@ -89,9 +89,7 @@ public function create( 'cliArgumentsVariablesRegistered' => ini_get('register_argc_argv') === '1', 'tmpDir' => $tempDirectory, 'additionalConfigFiles' => $additionalConfigFiles, - 'analysedPaths' => $analysedPaths, 'composerAutoloaderProjectPaths' => $composerAutoloaderProjectPaths, - 'analysedPathsFromConfig' => $analysedPathsFromConfig, 'generateBaselineFile' => $generateBaselineFile, 'usedLevel' => $usedLevel, 'cliAutoloadFile' => $cliAutoloadFile, @@ -100,6 +98,8 @@ public function create( $configurator->addDynamicParameters([ 'singleReflectionFile' => $singleReflectionFile, 'singleReflectionInsteadOfFile' => $singleReflectionInsteadOfFile, + 'analysedPaths' => $analysedPaths, + 'analysedPathsFromConfig' => $analysedPathsFromConfig, ]); $configurator->addConfig($this->configDirectory . '/config.neon'); foreach ($additionalConfigFiles as $additionalConfigFile) { From b726e087b729c1732256d9124b4c74e06ae4c803 Mon Sep 17 00:00:00 2001 From: Dylan T Date: Sun, 24 Oct 2021 21:24:18 +0100 Subject: [PATCH 0463/1284] Fix CompileCommand renaming PhpStormStubsMap on Windows (#732) --- compiler/src/Console/CompileCommand.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/compiler/src/Console/CompileCommand.php b/compiler/src/Console/CompileCommand.php index 194198f2f2..68eb5a8d21 100644 --- a/compiler/src/Console/CompileCommand.php +++ b/compiler/src/Console/CompileCommand.php @@ -88,7 +88,10 @@ private function renamePhpStormStubs(): void } $stubFinder = \Symfony\Component\Finder\Finder::create(); - $stubsMapPath = $directory . '/PhpStormStubsMap.php'; + $stubsMapPath = realpath($directory . '/PhpStormStubsMap.php'); + if ($stubsMapPath === false) { + throw new \Exception('realpath() failed'); + } foreach ($stubFinder->files()->name('*.php')->in($directory) as $stubFile) { $path = $stubFile->getPathname(); if ($path === $stubsMapPath) { From 0f6245bfaf28c31804d62b7c54d2b9b591ea5c7e Mon Sep 17 00:00:00 2001 From: Dylan T Date: Mon, 25 Oct 2021 16:33:12 +0200 Subject: [PATCH 0464/1284] Do not clear old containers in worker processes --- src/Command/CommandHelper.php | 7 +++++-- src/Command/FixerWorkerCommand.php | 3 ++- src/Command/WorkerCommand.php | 4 +++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php index 935d5eb7bb..4be4654f26 100644 --- a/src/Command/CommandHelper.php +++ b/src/Command/CommandHelper.php @@ -47,7 +47,8 @@ public static function begin( bool $manageMemoryLimitFile = true, bool $debugEnabled = false, ?string $singleReflectionFile = null, - ?string $singleReflectionInsteadOfFile = null + ?string $singleReflectionInsteadOfFile = null, + bool $cleanupContainerCache = true ): InceptionResult { if (!$allowXdebug) { @@ -235,7 +236,9 @@ public static function begin( throw new \PHPStan\Command\InceptionNotSuccessfulException(); } - $containerFactory->clearOldContainers($tmpDir); + if ($cleanupContainerCache) { + $containerFactory->clearOldContainers($tmpDir); + } if (count($paths) === 0) { $errorOutput->writeLineFormatted('At least one path must be specified to analyse.'); diff --git a/src/Command/FixerWorkerCommand.php b/src/Command/FixerWorkerCommand.php index 03ffaacd60..792c2d9860 100644 --- a/src/Command/FixerWorkerCommand.php +++ b/src/Command/FixerWorkerCommand.php @@ -114,7 +114,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int false, false, $singleReflectionFile, - $insteadOfFile + $insteadOfFile, + false ); } catch (\PHPStan\Command\InceptionNotSuccessfulException $e) { return 1; diff --git a/src/Command/WorkerCommand.php b/src/Command/WorkerCommand.php index 69dba03e90..a2380080c7 100644 --- a/src/Command/WorkerCommand.php +++ b/src/Command/WorkerCommand.php @@ -107,7 +107,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int $allowXdebug, false, false, - $singleReflectionFile + $singleReflectionFile, + null, + false ); } catch (\PHPStan\Command\InceptionNotSuccessfulException $e) { return 1; From 0e42081f3a9011c22fe1071ab31c46c2253a0818 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 25 Oct 2021 14:45:48 +0200 Subject: [PATCH 0465/1284] faster IgnoredError->shouldIgnore() --- src/Analyser/IgnoredError.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Analyser/IgnoredError.php b/src/Analyser/IgnoredError.php index f683a95ee1..bec912e37d 100644 --- a/src/Analyser/IgnoredError.php +++ b/src/Analyser/IgnoredError.php @@ -54,12 +54,11 @@ public static function shouldIgnore( $ignoredErrorPattern = str_replace([preg_quote('\r\n'), preg_quote('\r')], preg_quote('\n'), $ignoredErrorPattern); if ($path !== null) { - $fileExcluder = new FileExcluder($fileHelper, [$path], []); - if (\Nette\Utils\Strings::match($errorMessage, $ignoredErrorPattern) === null) { return false; } + $fileExcluder = new FileExcluder($fileHelper, [$path], []); $isExcluded = $fileExcluder->isExcludedFromAnalysing($error->getFilePath()); if (!$isExcluded && $error->getTraitFilePath() !== null) { return $fileExcluder->isExcludedFromAnalysing($error->getTraitFilePath()); From 1505153525ec010466b4cbd9dcbae79682c43e31 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 25 Oct 2021 17:01:55 +0200 Subject: [PATCH 0466/1284] faster `FileHelper::normalizePath()` --- src/File/FileHelper.php | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/File/FileHelper.php b/src/File/FileHelper.php index eac8c5accc..890d8309fe 100644 --- a/src/File/FileHelper.php +++ b/src/File/FileHelper.php @@ -2,8 +2,6 @@ namespace PHPStan\File; -use Nette\Utils\Strings; - class FileHelper { @@ -41,7 +39,13 @@ public function absolutizePath(string $path): string /** @api */ public function normalizePath(string $originalPath, string $directorySeparator = DIRECTORY_SEPARATOR): string { - $matches = \Nette\Utils\Strings::match($originalPath, '~^([a-z]+)\\:\\/\\/(.+)~'); + $isLocalPath = $originalPath !== '' && $originalPath[0] === '/'; + + $matches = null; + if (!$isLocalPath) { + $matches = \Nette\Utils\Strings::match($originalPath, '~^([a-z]+)\\:\\/\\/(.+)~'); + } + if ($matches !== null) { [, $scheme, $path] = $matches; } else { @@ -49,8 +53,7 @@ public function normalizePath(string $originalPath, string $directorySeparator = $path = $originalPath; } - $path = str_replace('\\', '/', $path); - $path = Strings::replace($path, '~/{2,}~', '/'); + $path = str_replace(['\\', '//', '///', '////'], '/', $path); $pathRoot = strpos($path, '/') === 0 ? $directorySeparator : ''; $pathParts = explode('/', trim($path, '/')); From a1e2fd9702187175907ee159f6ba997e32a3a36c Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 25 Oct 2021 17:04:30 +0200 Subject: [PATCH 0467/1284] use is_file() over file_exists(), which is faster --- src/Cache/FileCacheStorage.php | 2 +- src/Command/AnalyserRunner.php | 2 +- src/Command/CommandHelper.php | 8 ++++---- src/Command/FixerApplication.php | 6 +++--- src/File/FileFinder.php | 6 +++--- .../SourceLocator/AutoloadSourceLocator.php | 6 +++--- .../SourceLocator/OptimizedPsrAutoloaderLocator.php | 2 +- src/Reflection/ClassReflection.php | 2 +- src/Reflection/Php/PhpFunctionReflection.php | 4 ++-- src/Reflection/Php/PhpMethodReflection.php | 2 +- src/Type/FileTypeMapper.php | 8 ++++---- 11 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/Cache/FileCacheStorage.php b/src/Cache/FileCacheStorage.php index 709cbdf00d..4efb25fbf0 100644 --- a/src/Cache/FileCacheStorage.php +++ b/src/Cache/FileCacheStorage.php @@ -93,7 +93,7 @@ public function save(string $key, string $variableKey, $data): void } @unlink($tmpPath); - if (DIRECTORY_SEPARATOR === '/' || !file_exists($path)) { + if (DIRECTORY_SEPARATOR === '/' || !is_file($path)) { throw new \InvalidArgumentException(sprintf('Could not write data to cache file %s.', $path)); } } diff --git a/src/Command/AnalyserRunner.php b/src/Command/AnalyserRunner.php index e52904d869..3552cd7264 100644 --- a/src/Command/AnalyserRunner.php +++ b/src/Command/AnalyserRunner.php @@ -66,7 +66,7 @@ public function runAnalyser( $schedule = $this->scheduler->scheduleWork($this->cpuCoreCounter->getNumberOfCpuCores(), $files); $mainScript = null; - if (isset($_SERVER['argv'][0]) && file_exists($_SERVER['argv'][0])) { + if (isset($_SERVER['argv'][0]) && is_file($_SERVER['argv'][0])) { $mainScript = $_SERVER['argv'][0]; } diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php index 4be4654f26..395eefc747 100644 --- a/src/Command/CommandHelper.php +++ b/src/Command/CommandHelper.php @@ -180,7 +180,7 @@ public static function begin( $includedFilePath = null; if (isset($extensionConfig['relative_install_path'])) { $includedFilePath = sprintf('%s/%s/%s', $generatedConfigDirectory, $extensionConfig['relative_install_path'], $includedFile); - if (!file_exists($includedFilePath) || !is_readable($includedFilePath)) { + if (!is_file($includedFilePath) || !is_readable($includedFilePath)) { $includedFilePath = null; } } @@ -188,7 +188,7 @@ public static function begin( if ($includedFilePath === null) { $includedFilePath = sprintf('%s/%s', $extensionConfig['install_path'], $includedFile); } - if (!file_exists($includedFilePath) || !is_readable($includedFilePath)) { + if (!is_file($includedFilePath) || !is_readable($includedFilePath)) { $errorOutput->writeLineFormatted(sprintf('Config file %s does not exist or isn\'t readable', $includedFilePath)); throw new \PHPStan\Command\InceptionNotSuccessfulException(); } @@ -246,7 +246,7 @@ public static function begin( } $memoryLimitFile = $container->getParameter('memoryLimitFile'); - if ($manageMemoryLimitFile && file_exists($memoryLimitFile)) { + if ($manageMemoryLimitFile && is_file($memoryLimitFile)) { $memoryLimitFileContents = FileReader::read($memoryLimitFile); $errorOutput->writeLineFormatted('PHPStan crashed in the previous run probably because of excessive memory consumption.'); $errorOutput->writeLineFormatted(sprintf('It consumed around %s of memory.', $memoryLimitFileContents)); @@ -415,7 +415,7 @@ private static function setUpSignalHandler(Output $output, ?string $memoryLimitF pcntl_async_signals(true); pcntl_signal(SIGINT, static function () use ($output, $memoryLimitFile): void { - if ($memoryLimitFile !== null && file_exists($memoryLimitFile)) { + if ($memoryLimitFile !== null && is_file($memoryLimitFile)) { @unlink($memoryLimitFile); } $output->writeLineFormatted(''); diff --git a/src/Command/FixerApplication.php b/src/Command/FixerApplication.php index 957dc82bb0..698b24d7e9 100644 --- a/src/Command/FixerApplication.php +++ b/src/Command/FixerApplication.php @@ -37,7 +37,7 @@ use const PHP_BINARY; use function Clue\React\Block\await; use function escapeshellarg; -use function file_exists; +use function is_file; use function React\Promise\resolve; class FixerApplication @@ -284,7 +284,7 @@ private function getFixerProcess(OutputInterface $output, int $serverPort): Proc try { $this->downloadPhar($output, $pharPath, $infoPath); } catch (\RuntimeException $e) { - if (!file_exists($pharPath)) { + if (!is_file($pharPath)) { $output->writeln('Could not download the PHPStan Pro executable.'); $output->writeln($e->getMessage()); @@ -356,7 +356,7 @@ private function downloadPhar( ): void { $currentVersion = null; - if (file_exists($pharPath) && file_exists($infoPath)) { + if (is_file($pharPath) && is_file($infoPath)) { /** @var array{version: string, date: string} $currentInfo */ $currentInfo = Json::decode(FileReader::read($infoPath), Json::FORCE_ARRAY); $currentVersion = $currentInfo['version']; diff --git a/src/File/FileFinder.php b/src/File/FileFinder.php index 61469de531..1ee302b990 100644 --- a/src/File/FileFinder.php +++ b/src/File/FileFinder.php @@ -39,10 +39,10 @@ public function findFiles(array $paths): FileFinderResult $onlyFiles = true; $files = []; foreach ($paths as $path) { - if (!file_exists($path)) { - throw new \PHPStan\File\PathNotFoundException($path); - } elseif (is_file($path)) { + if (is_file($path)) { $files[] = $this->fileHelper->normalizePath($path); + } elseif (!file_exists($path)) { + throw new \PHPStan\File\PathNotFoundException($path); } else { $finder = new Finder(); $finder->followLinks(); diff --git a/src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php b/src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php index f88f060808..8ca67f3188 100644 --- a/src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php +++ b/src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php @@ -17,7 +17,7 @@ use ReflectionClass; use ReflectionFunction; use function array_key_exists; -use function file_exists; +use function is_file; use function restore_error_handler; /** @@ -77,7 +77,7 @@ public function locateIdentifier(Reflector $reflector, Identifier $identifier): if (!is_string($reflectionFileName)) { return null; } - if (!file_exists($reflectionFileName)) { + if (!is_file($reflectionFileName)) { return null; } @@ -276,7 +276,7 @@ private function locateClassByName(string $className): ?array return null; } - if (!file_exists($filename)) { + if (!is_file($filename)) { return null; } diff --git a/src/Reflection/BetterReflection/SourceLocator/OptimizedPsrAutoloaderLocator.php b/src/Reflection/BetterReflection/SourceLocator/OptimizedPsrAutoloaderLocator.php index b065a1f7ef..27a4c27800 100644 --- a/src/Reflection/BetterReflection/SourceLocator/OptimizedPsrAutoloaderLocator.php +++ b/src/Reflection/BetterReflection/SourceLocator/OptimizedPsrAutoloaderLocator.php @@ -28,7 +28,7 @@ public function __construct( public function locateIdentifier(Reflector $reflector, Identifier $identifier): ?Reflection { foreach ($this->mapping->resolvePossibleFilePaths($identifier) as $file) { - if (!file_exists($file)) { + if (!is_file($file)) { continue; } diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index 80b1cb4c09..2d6a4e3576 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -175,7 +175,7 @@ public function getFileName(): ?string return $this->filename = null; } - if (!file_exists($fileName)) { + if (!is_file($fileName)) { return $this->filename = null; } diff --git a/src/Reflection/Php/PhpFunctionReflection.php b/src/Reflection/Php/PhpFunctionReflection.php index f43eb8be1d..5690fc2b54 100644 --- a/src/Reflection/Php/PhpFunctionReflection.php +++ b/src/Reflection/Php/PhpFunctionReflection.php @@ -116,7 +116,7 @@ public function getFileName(): ?string return null; } - if (!file_exists($this->filename)) { + if (!is_file($this->filename)) { return null; } @@ -164,7 +164,7 @@ private function isVariadic(): bool $isNativelyVariadic = $this->reflection->isVariadic(); if (!$isNativelyVariadic && $this->reflection->getFileName() !== false) { $fileName = $this->reflection->getFileName(); - if (file_exists($fileName)) { + if (is_file($fileName)) { $functionName = $this->reflection->getName(); $modifiedTime = filemtime($fileName); if ($modifiedTime === false) { diff --git a/src/Reflection/Php/PhpMethodReflection.php b/src/Reflection/Php/PhpMethodReflection.php index 71a4f41e27..94ce869502 100644 --- a/src/Reflection/Php/PhpMethodReflection.php +++ b/src/Reflection/Php/PhpMethodReflection.php @@ -268,7 +268,7 @@ private function isVariadic(): bool $filename = $this->declaringTrait->getFileName(); } - if (!$isNativelyVariadic && $filename !== null && file_exists($filename)) { + if (!$isNativelyVariadic && $filename !== null && is_file($filename)) { $modifiedTime = filemtime($filename); if ($modifiedTime === false) { $modifiedTime = time(); diff --git a/src/Type/FileTypeMapper.php b/src/Type/FileTypeMapper.php index 85344d851f..ddbfd42485 100644 --- a/src/Type/FileTypeMapper.php +++ b/src/Type/FileTypeMapper.php @@ -22,8 +22,8 @@ use PHPStan\Type\Generic\TemplateTypeHelper; use PHPStan\Type\Generic\TemplateTypeMap; use function array_key_exists; -use function file_exists; use function filemtime; +use function is_file; class FileTypeMapper { @@ -465,7 +465,7 @@ function (\PhpParser\Node $node) use ($fileName, $lookForTrait, $traitMethodAlia if ($traitReflection->getFileName() === null) { continue; } - if (!file_exists($traitReflection->getFileName())) { + if (!is_file($traitReflection->getFileName())) { continue; } @@ -674,7 +674,7 @@ private function getCachedDependentFilesWithTimestamps(string $fileName): array $cachedFilename = $cachedFile['filename']; $cachedTimestamp = $cachedFile['modifiedTime']; - if (!file_exists($cachedFilename)) { + if (!is_file($cachedFilename)) { $useCached = false; break; } @@ -757,7 +757,7 @@ function (Node $node) use (&$dependentFiles) { if ($traitReflection->getFileName() === false) { continue; } - if (!file_exists($traitReflection->getFileName())) { + if (!is_file($traitReflection->getFileName())) { continue; } From d07a1bdded18c62915116eb52440e31aa3952894 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 25 Oct 2021 17:06:57 +0200 Subject: [PATCH 0468/1284] TypehintHelper::decideTypeFromReflection() is part of BC promise --- src/Type/TypehintHelper.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Type/TypehintHelper.php b/src/Type/TypehintHelper.php index 61fc587a6a..2ee54e7a55 100644 --- a/src/Type/TypehintHelper.php +++ b/src/Type/TypehintHelper.php @@ -62,6 +62,7 @@ private static function getTypeObjectFromTypehint(string $typeString, ?string $s } } + /** @api */ public static function decideTypeFromReflection( ?\ReflectionType $reflectionType, ?Type $phpDocType = null, From ac39505f78650b938bcae82746410d01fa6409ab Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 26 Oct 2021 12:57:22 +0200 Subject: [PATCH 0469/1284] `+`-operator with `non-empty-array` should lead to `non-empty-array` --- src/Analyser/MutatingScope.php | 10 +++++++++- .../Analyser/LegacyNodeScopeResolverTest.php | 2 +- .../Analyser/NodeScopeResolverTest.php | 1 + tests/PHPStan/Analyser/data/array-plus.php | 20 +++++++++++++++++++ 4 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/array-plus.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 6e785c8d45..fddb980225 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -50,6 +50,7 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\Accessory\AccessoryLiteralStringType; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; +use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\ArrayType; use PHPStan\Type\BenevolentUnionType; use PHPStan\Type\BooleanType; @@ -1290,6 +1291,7 @@ private function resolveType(Expr $node): Type return TypeCombinator::union(...$resultTypes); } + $arrayType = new ArrayType(new MixedType(), new MixedType()); if ($arrayType->isSuperTypeOf($leftType)->yes() && $arrayType->isSuperTypeOf($rightType)->yes()) { @@ -1306,10 +1308,16 @@ private function resolveType(Expr $node): Type } $keyType = TypeCombinator::union(...$keyTypes); } - return new ArrayType( + + $arrayType = new ArrayType( $keyType, TypeCombinator::union($leftType->getIterableValueType(), $rightType->getIterableValueType()) ); + + if ($leftType->isIterableAtLeastOnce()->yes() || $rightType->isIterableAtLeastOnce()->yes()) { + return TypeCombinator::intersect($arrayType, new NonEmptyArrayType()); + } + return $arrayType; } if ($leftType instanceof MixedType && $rightType instanceof MixedType) { diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 6cdf52184c..d224a419e5 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -2830,7 +2830,7 @@ public function dataBinaryOperations(): array '[1, 2, 3] + [4, 5, 6]', ], [ - 'array', + 'non-empty-array', '$arrayOfUnknownIntegers + [1, 2, 3]', ], [ diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index e09cc3a426..ffca869fb5 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -272,6 +272,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/array-map.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/array-map-closure.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/array-sum.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/array-plus.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4573.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4577.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4579.php'); diff --git a/tests/PHPStan/Analyser/data/array-plus.php b/tests/PHPStan/Analyser/data/array-plus.php new file mode 100644 index 0000000000..b6d8b99e2d --- /dev/null +++ b/tests/PHPStan/Analyser/data/array-plus.php @@ -0,0 +1,20 @@ + Date: Tue, 26 Oct 2021 15:41:02 +0200 Subject: [PATCH 0470/1284] Test on PHP 8.1 --- .github/workflows/lint.yml | 1 + .github/workflows/static-analysis.yml | 1 + .github/workflows/tests.yml | 1 + 3 files changed, 3 insertions(+) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 01976ec12f..150d609f1a 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -26,6 +26,7 @@ jobs: - "7.3" - "7.4" - "8.0" + - "8.1" steps: - name: "Checkout" diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index be5b2663c8..3114a00a21 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -26,6 +26,7 @@ jobs: - "7.3" - "7.4" - "8.0" + - "8.1" operating-system: [ubuntu-latest, windows-latest] script: - "make phpstan" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d2a1ecd903..30483c16c0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -24,6 +24,7 @@ jobs: - "7.3" - "7.4" - "8.0" + - "8.1" operating-system: [ ubuntu-latest, windows-latest ] script: - "make tests" From 8ff597ccd3d3dae2e459faee04dd44b3f9a0f666 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 26 Oct 2021 15:46:28 +0200 Subject: [PATCH 0471/1284] Mark jsonSerialize with ReturnTypeWillChange attribute --- src/Analyser/Error.php | 1 + src/Dependency/ExportedNode/ExportedClassConstantNode.php | 1 + src/Dependency/ExportedNode/ExportedClassNode.php | 1 + src/Dependency/ExportedNode/ExportedFunctionNode.php | 1 + src/Dependency/ExportedNode/ExportedInterfaceNode.php | 1 + src/Dependency/ExportedNode/ExportedMethodNode.php | 1 + src/Dependency/ExportedNode/ExportedParameterNode.php | 1 + src/Dependency/ExportedNode/ExportedPhpDocNode.php | 1 + src/Dependency/ExportedNode/ExportedPropertyNode.php | 1 + src/Dependency/ExportedNode/ExportedTraitNode.php | 1 + src/Dependency/ExportedNode/ExportedTraitUseAdaptation.php | 1 + 11 files changed, 11 insertions(+) diff --git a/src/Analyser/Error.php b/src/Analyser/Error.php index 0dfdb0b900..7286515e3d 100644 --- a/src/Analyser/Error.php +++ b/src/Analyser/Error.php @@ -203,6 +203,7 @@ public function getMetadata(): array /** * @return mixed */ + #[\ReturnTypeWillChange] public function jsonSerialize() { return [ diff --git a/src/Dependency/ExportedNode/ExportedClassConstantNode.php b/src/Dependency/ExportedNode/ExportedClassConstantNode.php index e9c0523abf..312383bbc2 100644 --- a/src/Dependency/ExportedNode/ExportedClassConstantNode.php +++ b/src/Dependency/ExportedNode/ExportedClassConstantNode.php @@ -90,6 +90,7 @@ public static function decode(array $data): ExportedNode /** * @return mixed */ + #[\ReturnTypeWillChange] public function jsonSerialize() { return [ diff --git a/src/Dependency/ExportedNode/ExportedClassNode.php b/src/Dependency/ExportedNode/ExportedClassNode.php index 4977a47643..6e8c7ee091 100644 --- a/src/Dependency/ExportedNode/ExportedClassNode.php +++ b/src/Dependency/ExportedNode/ExportedClassNode.php @@ -116,6 +116,7 @@ public static function __set_state(array $properties): ExportedNode /** * @return mixed */ + #[\ReturnTypeWillChange] public function jsonSerialize() { return [ diff --git a/src/Dependency/ExportedNode/ExportedFunctionNode.php b/src/Dependency/ExportedNode/ExportedFunctionNode.php index 6c4382b1b0..5a56b26359 100644 --- a/src/Dependency/ExportedNode/ExportedFunctionNode.php +++ b/src/Dependency/ExportedNode/ExportedFunctionNode.php @@ -93,6 +93,7 @@ public static function __set_state(array $properties): ExportedNode /** * @return mixed */ + #[\ReturnTypeWillChange] public function jsonSerialize() { return [ diff --git a/src/Dependency/ExportedNode/ExportedInterfaceNode.php b/src/Dependency/ExportedNode/ExportedInterfaceNode.php index 0519efba99..50311538a2 100644 --- a/src/Dependency/ExportedNode/ExportedInterfaceNode.php +++ b/src/Dependency/ExportedNode/ExportedInterfaceNode.php @@ -65,6 +65,7 @@ public static function __set_state(array $properties): ExportedNode /** * @return mixed */ + #[\ReturnTypeWillChange] public function jsonSerialize() { return [ diff --git a/src/Dependency/ExportedNode/ExportedMethodNode.php b/src/Dependency/ExportedNode/ExportedMethodNode.php index a59a40c25a..3bf48ac832 100644 --- a/src/Dependency/ExportedNode/ExportedMethodNode.php +++ b/src/Dependency/ExportedNode/ExportedMethodNode.php @@ -128,6 +128,7 @@ public static function __set_state(array $properties): ExportedNode /** * @return mixed */ + #[\ReturnTypeWillChange] public function jsonSerialize() { return [ diff --git a/src/Dependency/ExportedNode/ExportedParameterNode.php b/src/Dependency/ExportedNode/ExportedParameterNode.php index 5f171a442f..c3438a262e 100644 --- a/src/Dependency/ExportedNode/ExportedParameterNode.php +++ b/src/Dependency/ExportedNode/ExportedParameterNode.php @@ -64,6 +64,7 @@ public static function __set_state(array $properties): ExportedNode /** * @return mixed */ + #[\ReturnTypeWillChange] public function jsonSerialize() { return [ diff --git a/src/Dependency/ExportedNode/ExportedPhpDocNode.php b/src/Dependency/ExportedNode/ExportedPhpDocNode.php index e271b6b752..36e1285ac4 100644 --- a/src/Dependency/ExportedNode/ExportedPhpDocNode.php +++ b/src/Dependency/ExportedNode/ExportedPhpDocNode.php @@ -41,6 +41,7 @@ public function equals(ExportedNode $node): bool /** * @return mixed */ + #[\ReturnTypeWillChange] public function jsonSerialize() { return [ diff --git a/src/Dependency/ExportedNode/ExportedPropertyNode.php b/src/Dependency/ExportedNode/ExportedPropertyNode.php index b5bf696d47..8dbde24e6c 100644 --- a/src/Dependency/ExportedNode/ExportedPropertyNode.php +++ b/src/Dependency/ExportedNode/ExportedPropertyNode.php @@ -104,6 +104,7 @@ public static function decode(array $data): ExportedNode /** * @return mixed */ + #[\ReturnTypeWillChange] public function jsonSerialize() { return [ diff --git a/src/Dependency/ExportedNode/ExportedTraitNode.php b/src/Dependency/ExportedNode/ExportedTraitNode.php index ed8a6fa5ef..85187d863c 100644 --- a/src/Dependency/ExportedNode/ExportedTraitNode.php +++ b/src/Dependency/ExportedNode/ExportedTraitNode.php @@ -41,6 +41,7 @@ public static function decode(array $data): ExportedNode /** * @return mixed */ + #[\ReturnTypeWillChange] public function jsonSerialize() { return [ diff --git a/src/Dependency/ExportedNode/ExportedTraitUseAdaptation.php b/src/Dependency/ExportedNode/ExportedTraitUseAdaptation.php index 1de62fcf0a..0757034f99 100644 --- a/src/Dependency/ExportedNode/ExportedTraitUseAdaptation.php +++ b/src/Dependency/ExportedNode/ExportedTraitUseAdaptation.php @@ -112,6 +112,7 @@ public static function decode(array $data): ExportedNode /** * @return mixed */ + #[\ReturnTypeWillChange] public function jsonSerialize() { return [ From 056f384967a8d748f8a37882d11b543f44c764bf Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 26 Oct 2021 15:50:59 +0200 Subject: [PATCH 0472/1284] Fix linting on PHP 8.1 --- Makefile | 3 +++ .../PHPStan/Rules/Constants/data/overriding-final-constant.php | 2 +- tests/PHPStan/Rules/Properties/data/overriding-property.php | 2 +- tests/PHPStan/Rules/Properties/data/read-only-property.php | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 0bfa9d747e..6680f57e3e 100644 --- a/Makefile +++ b/Makefile @@ -41,6 +41,9 @@ lint: --exclude tests/PHPStan/Rules/Functions/data/arrow-function-nullsafe-by-ref.php \ --exclude tests/PHPStan/Levels/data/namedArguments.php \ --exclude tests/PHPStan/Rules/Keywords/data/continue-break.php \ + --exclude tests/PHPStan/Rules/Properties/data/read-only-property.php \ + --exclude tests/PHPStan/Rules/Properties/data/overriding-property.php \ + --exclude tests/PHPStan/Rules/Constants/data/overriding-final-constant.php \ src tests compiler/src cs: diff --git a/tests/PHPStan/Rules/Constants/data/overriding-final-constant.php b/tests/PHPStan/Rules/Constants/data/overriding-final-constant.php index 50deb4b5a6..5eb27265af 100644 --- a/tests/PHPStan/Rules/Constants/data/overriding-final-constant.php +++ b/tests/PHPStan/Rules/Constants/data/overriding-final-constant.php @@ -1,4 +1,4 @@ -= 8.1 += 8.1 += 8.1 + Date: Tue, 26 Oct 2021 15:59:22 +0200 Subject: [PATCH 0473/1284] No longer test Never as class name --- tests/PHPStan/Analyser/data/phpdoc-pseudotype-namespace.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/PHPStan/Analyser/data/phpdoc-pseudotype-namespace.php b/tests/PHPStan/Analyser/data/phpdoc-pseudotype-namespace.php index b7ff3c7fda..c13d96db75 100644 --- a/tests/PHPStan/Analyser/data/phpdoc-pseudotype-namespace.php +++ b/tests/PHPStan/Analyser/data/phpdoc-pseudotype-namespace.php @@ -8,7 +8,6 @@ class Number {} class Numeric {} class Boolean {} class Resource {} -class Never {} class Double {} function () { @@ -21,9 +20,6 @@ function () { /** @var Numeric $numeric */ $numeric = doFoo(); - /** @var Never $never */ - $never = doFoo(); - /** @var Resource $resource */ $resource = doFoo(); @@ -34,6 +30,5 @@ function () { assertType('PhpdocPseudoTypesNamespace\Numeric', $numeric); assertType('PhpdocPseudoTypesNamespace\Boolean', $boolean); assertType('PhpdocPseudoTypesNamespace\Resource', $resource); - assertType('PhpdocPseudoTypesNamespace\Never', $never); assertType('PhpdocPseudoTypesNamespace\Double', $double); }; From 9062ab111be8b43bc74d48177206cfccba21cb95 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 26 Oct 2021 16:00:23 +0200 Subject: [PATCH 0474/1284] This sniff is broken with attributes --- phpcs.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/phpcs.xml b/phpcs.xml index b3b5bb5651..99137a707d 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -21,6 +21,7 @@ + From ec7b78d4f87ae416029fb8544ff68335fbce7284 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 26 Oct 2021 16:46:53 +0200 Subject: [PATCH 0475/1284] BetterReflection no longer reports any deprecations --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 1ce1713ed0..6094a604b5 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "nette/utils": "^3.1.3", "nikic/php-parser": "4.13.0", "ondram/ci-detector": "^3.4.0", - "ondrejmirtes/better-reflection": "4.3.71", + "ondrejmirtes/better-reflection": "4.3.72", "phpstan/php-8-stubs": "^0.1.23", "phpstan/phpdoc-parser": "^1.2.0", "react/child-process": "^0.6.4", diff --git a/composer.lock b/composer.lock index d7a83e1adc..8ef4e5b706 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "9adfaa28cae0183fb1e2bc8ca56a2bdf", + "content-hash": "4ee977a76189b7656cd06eeab8c9de65", "packages": [ { "name": "clue/block-react", @@ -2074,16 +2074,16 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "4.3.71", + "version": "4.3.72", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "2b7b2495ec99287e89dea471b7adc42abadcdb56" + "reference": "bca6925b51d64e26a2ab39e7c364a4afd9be11e3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/2b7b2495ec99287e89dea471b7adc42abadcdb56", - "reference": "2b7b2495ec99287e89dea471b7adc42abadcdb56", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/bca6925b51d64e26a2ab39e7c364a4afd9be11e3", + "reference": "bca6925b51d64e26a2ab39e7c364a4afd9be11e3", "shasum": "" }, "require": { @@ -2138,9 +2138,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/4.3.71" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/4.3.72" }, - "time": "2021-10-08T07:38:32+00:00" + "time": "2021-10-26T14:32:28+00:00" }, { "name": "phpstan/php-8-stubs", From fc57cd675f9b513b5a0489391a03bbef9755ab49 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 26 Oct 2021 21:45:02 +0200 Subject: [PATCH 0476/1284] Special handling of all runtime stubs --- .../ReflectionProvider/ClassBlacklistReflectionProvider.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Reflection/ReflectionProvider/ClassBlacklistReflectionProvider.php b/src/Reflection/ReflectionProvider/ClassBlacklistReflectionProvider.php index b3ace3ff3f..5363194fa8 100644 --- a/src/Reflection/ReflectionProvider/ClassBlacklistReflectionProvider.php +++ b/src/Reflection/ReflectionProvider/ClassBlacklistReflectionProvider.php @@ -80,7 +80,7 @@ private function isClassBlacklisted(string $className): bool if (!class_exists($className, false)) { return true; } - if (in_array(strtolower($className), ['reflectionuniontype', 'attribute'], true)) { + if (in_array(strtolower($className), ['reflectionuniontype', 'attribute', 'returntypewillchange'], true)) { return true; } $reflection = new \ReflectionClass($className); From c72145976431cf95b90dbb7397b53575597e3b06 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 26 Oct 2021 21:56:25 +0200 Subject: [PATCH 0477/1284] ReturnTypeWillChange attribute class is always defined --- composer.json | 2 +- composer.lock | 14 +++++++------- .../SourceLocator/SkipClassAliasSourceLocator.php | 3 +++ 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 6094a604b5..6b6db60a5e 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "nette/utils": "^3.1.3", "nikic/php-parser": "4.13.0", "ondram/ci-detector": "^3.4.0", - "ondrejmirtes/better-reflection": "4.3.72", + "ondrejmirtes/better-reflection": "4.3.73", "phpstan/php-8-stubs": "^0.1.23", "phpstan/phpdoc-parser": "^1.2.0", "react/child-process": "^0.6.4", diff --git a/composer.lock b/composer.lock index 8ef4e5b706..31bc8b57b5 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "4ee977a76189b7656cd06eeab8c9de65", + "content-hash": "8044e5c94d3d23696aeffc145b29598c", "packages": [ { "name": "clue/block-react", @@ -2074,16 +2074,16 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "4.3.72", + "version": "4.3.73", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "bca6925b51d64e26a2ab39e7c364a4afd9be11e3" + "reference": "36c86d7f9337964e22118404215dff749539c7d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/bca6925b51d64e26a2ab39e7c364a4afd9be11e3", - "reference": "bca6925b51d64e26a2ab39e7c364a4afd9be11e3", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/36c86d7f9337964e22118404215dff749539c7d3", + "reference": "36c86d7f9337964e22118404215dff749539c7d3", "shasum": "" }, "require": { @@ -2138,9 +2138,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/4.3.72" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/4.3.73" }, - "time": "2021-10-26T14:32:28+00:00" + "time": "2021-10-26T19:54:59+00:00" }, { "name": "phpstan/php-8-stubs", diff --git a/src/Reflection/BetterReflection/SourceLocator/SkipClassAliasSourceLocator.php b/src/Reflection/BetterReflection/SourceLocator/SkipClassAliasSourceLocator.php index 2afa00d549..ec4d4896ce 100644 --- a/src/Reflection/BetterReflection/SourceLocator/SkipClassAliasSourceLocator.php +++ b/src/Reflection/BetterReflection/SourceLocator/SkipClassAliasSourceLocator.php @@ -27,6 +27,9 @@ public function locateIdentifier(Reflector $reflector, Identifier $identifier): } $reflection = new \ReflectionClass($className); + if ($reflection->getName() === 'ReturnTypeWillChange') { + return $this->sourceLocator->locateIdentifier($reflector, $identifier); + } if ($reflection->getFileName() === false) { return $this->sourceLocator->locateIdentifier($reflector, $identifier); } From 21ac77864cf38be56a34ffdba7e91b0952054e78 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 26 Oct 2021 22:04:17 +0200 Subject: [PATCH 0478/1284] Get rid of PHP 8.1 deprecations in tests --- .../Analyser/LegacyNodeScopeResolverTest.php | 4 +- tests/PHPStan/Analyser/data/bug-4267.php | 2 + .../data/dynamic-method-return-types.php | 3 ++ .../Analyser/data/generic-offset-get.php | 4 ++ tests/PHPStan/Analyser/data/generics.php | 2 + tests/PHPStan/Analyser/data/if-defined.php | 1 + .../Rules/Methods/ReturnTypeRuleTest.php | 48 +++++++++---------- .../Rules/Methods/data/infer-array-key.php | 5 ++ .../Rules/Methods/data/returnTypes.php | 3 ++ 9 files changed, 46 insertions(+), 26 deletions(-) diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index d224a419e5..ac72606e29 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -2082,7 +2082,7 @@ public function dataBinaryOperations(): array '1.2 ** 1.4', ], [ - $typeCallback(3.2 % 2.4), + 'int', '3.2 % 2.4', ], [ @@ -2115,7 +2115,7 @@ public function dataBinaryOperations(): array '1 ** 1.4', ], [ - $typeCallback(3 % 2.4), + 'int', '3 % 2.4', ], [ diff --git a/tests/PHPStan/Analyser/data/bug-4267.php b/tests/PHPStan/Analyser/data/bug-4267.php index be533bafcd..c93256827b 100644 --- a/tests/PHPStan/Analyser/data/bug-4267.php +++ b/tests/PHPStan/Analyser/data/bug-4267.php @@ -10,6 +10,7 @@ class Model1 implements \IteratorAggregate { + #[\ReturnTypeWillChange] public function getIterator(): iterable { throw new \Exception('not implemented'); @@ -33,6 +34,7 @@ class Model2 implements \IteratorAggregate /** * @return iterable */ + #[\ReturnTypeWillChange] public function getIterator(): iterable { throw new \Exception('not implemented'); diff --git a/tests/PHPStan/Analyser/data/dynamic-method-return-types.php b/tests/PHPStan/Analyser/data/dynamic-method-return-types.php index 56be77284a..0fb7c70d6d 100644 --- a/tests/PHPStan/Analyser/data/dynamic-method-return-types.php +++ b/tests/PHPStan/Analyser/data/dynamic-method-return-types.php @@ -27,6 +27,7 @@ class InheritedEntityManager extends EntityManager class ComponentContainer implements \ArrayAccess { + #[\ReturnTypeWillChange] public function offsetExists($offset) { @@ -37,11 +38,13 @@ public function offsetGet($offset): Entity } + #[\ReturnTypeWillChange] public function offsetSet($offset, $value) { } + #[\ReturnTypeWillChange] public function offsetUnset($offset) { diff --git a/tests/PHPStan/Analyser/data/generic-offset-get.php b/tests/PHPStan/Analyser/data/generic-offset-get.php index 7243b96576..8f2cfa4f5a 100644 --- a/tests/PHPStan/Analyser/data/generic-offset-get.php +++ b/tests/PHPStan/Analyser/data/generic-offset-get.php @@ -9,6 +9,7 @@ class Foo implements ArrayAccess { + #[\ReturnTypeWillChange] public function offsetExists($offset) { return true; @@ -19,16 +20,19 @@ public function offsetExists($offset) * @param class-string $offset * @return T */ + #[\ReturnTypeWillChange] public function offsetGet($offset) { } + #[\ReturnTypeWillChange] public function offsetSet($offset, $value) { } + #[\ReturnTypeWillChange] public function offsetUnset($offset) { diff --git a/tests/PHPStan/Analyser/data/generics.php b/tests/PHPStan/Analyser/data/generics.php index b203267334..032fa71f17 100644 --- a/tests/PHPStan/Analyser/data/generics.php +++ b/tests/PHPStan/Analyser/data/generics.php @@ -1108,6 +1108,7 @@ function () { class GenericReflectionClass extends \ReflectionClass { + #[\ReturnTypeWillChange] public function newInstanceWithoutConstructor() { return parent::newInstanceWithoutConstructor(); @@ -1121,6 +1122,7 @@ public function newInstanceWithoutConstructor() class SpecificReflectionClass extends \ReflectionClass { + #[\ReturnTypeWillChange] public function newInstanceWithoutConstructor() { return parent::newInstanceWithoutConstructor(); diff --git a/tests/PHPStan/Analyser/data/if-defined.php b/tests/PHPStan/Analyser/data/if-defined.php index 81832db199..0f1a5ecfa3 100644 --- a/tests/PHPStan/Analyser/data/if-defined.php +++ b/tests/PHPStan/Analyser/data/if-defined.php @@ -15,6 +15,7 @@ public function offsetGet($offset) } + #[\ReturnTypeWillChange] public function offsetSet($offset, $value) { diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index 982a5cd157..25c585f5b5 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -180,99 +180,99 @@ public function testReturnTypeRule(): void ], [ 'Method ReturnTypes\ReturnTernary::returnTernary() should return ReturnTypes\Foo but returns false.', - 625, + 627, ], [ 'Method ReturnTypes\TrickyVoid::returnVoidOrInt() should return int|void but returns string.', - 656, + 658, ], [ 'Method ReturnTypes\TernaryWithJsonEncode::toJson() should return string but returns string|false.', - 687, + 689, ], [ 'Method ReturnTypes\AppendedArrayReturnType::foo() should return array but returns array.', - 700, + 702, ], [ 'Method ReturnTypes\AppendedArrayReturnType::bar() should return array but returns array.', - 710, + 712, ], [ 'Method ReturnTypes\WrongMagicMethods::__toString() should return string but returns true.', - 720, + 722, ], [ 'Method ReturnTypes\WrongMagicMethods::__isset() should return bool but returns int.', - 725, + 727, ], [ 'Method ReturnTypes\WrongMagicMethods::__destruct() with return type void returns int but should not return anything.', - 730, + 732, ], [ 'Method ReturnTypes\WrongMagicMethods::__unset() with return type void returns int but should not return anything.', - 735, + 737, ], [ 'Method ReturnTypes\WrongMagicMethods::__sleep() should return array but returns array.', - 740, + 742, ], [ 'Method ReturnTypes\WrongMagicMethods::__wakeup() with return type void returns int but should not return anything.', - 747, + 749, ], [ 'Method ReturnTypes\WrongMagicMethods::__set_state() should return object but returns array.', - 752, + 754, ], [ 'Method ReturnTypes\WrongMagicMethods::__clone() with return type void returns int but should not return anything.', - 757, + 759, ], [ 'Method ReturnTypes\ArrayFillKeysIssue::getIPs2() should return array> but returns array>.', - 815, + 817, ], [ 'Method ReturnTypes\AssertThisInstanceOf::doBar() should return $this(ReturnTypes\AssertThisInstanceOf) but returns ReturnTypes\AssertThisInstanceOf&ReturnTypes\FooInterface.', - 838, + 840, ], [ 'Method ReturnTypes\NestedArrayCheck::doFoo() should return array but returns array>.', - 858, + 860, ], [ 'Method ReturnTypes\NestedArrayCheck::doBar() should return array but returns array>.', - 873, + 875, ], [ 'Method ReturnTypes\Foo2::returnIntFromParent() should return int but returns string.', - 948, + 950, ], [ 'Method ReturnTypes\Foo2::returnIntFromParent() should return int but returns ReturnTypes\integer.', - 951, + 953, ], [ 'Method ReturnTypes\VariableOverwrittenInForeach::doFoo() should return int but returns int|string.', - 1009, + 1011, ], [ 'Method ReturnTypes\VariableOverwrittenInForeach::doBar() should return int but returns int|string.', - 1024, + 1026, ], [ 'Method ReturnTypes\ReturnStaticGeneric::instanceReturnsStatic() should return static(ReturnTypes\ReturnStaticGeneric) but returns ReturnTypes\ReturnStaticGeneric.', - 1064, + 1066, ], [ 'Method ReturnTypes\NeverReturn::doFoo() should never return but return statement found.', - 1238, + 1241, ], [ 'Method ReturnTypes\NeverReturn::doBaz3() should never return but return statement found.', - 1251, + 1254, ], ]); } diff --git a/tests/PHPStan/Rules/Methods/data/infer-array-key.php b/tests/PHPStan/Rules/Methods/data/infer-array-key.php index 7107f700c2..86ab98a1b5 100644 --- a/tests/PHPStan/Rules/Methods/data/infer-array-key.php +++ b/tests/PHPStan/Rules/Methods/data/infer-array-key.php @@ -13,6 +13,7 @@ class Foo implements \IteratorAggregate /** @var \stdClass[] */ private $items; + #[\ReturnTypeWillChange] public function getIterator() { $it = new \ArrayIterator($this->items); @@ -32,6 +33,7 @@ class Bar implements \IteratorAggregate /** @var array */ private $items; + #[\ReturnTypeWillChange] public function getIterator() { $it = new \ArrayIterator($this->items); @@ -51,6 +53,7 @@ class Baz implements \IteratorAggregate /** @var array */ private $items; + #[\ReturnTypeWillChange] public function getIterator() { $it = new \ArrayIterator($this->items); @@ -70,6 +73,7 @@ class Lorem implements \IteratorAggregate /** @var array<\stdClass> */ private $items; + #[\ReturnTypeWillChange] public function getIterator() { $it = new \ArrayIterator($this->items); @@ -89,6 +93,7 @@ class Ipsum implements \IteratorAggregate /** @var array */ private $items; + #[\ReturnTypeWillChange] public function getIterator() { $it = new \ArrayIterator($this->items); diff --git a/tests/PHPStan/Rules/Methods/data/returnTypes.php b/tests/PHPStan/Rules/Methods/data/returnTypes.php index c5326ae13c..26b59ee6ec 100644 --- a/tests/PHPStan/Rules/Methods/data/returnTypes.php +++ b/tests/PHPStan/Rules/Methods/data/returnTypes.php @@ -571,6 +571,7 @@ public function test() class Collection implements \IteratorAggregate { + #[\ReturnTypeWillChange] public function getIterator() { return new \ArrayIterator([]); @@ -581,6 +582,7 @@ public function getIterator() class AnotherCollection implements \IteratorAggregate { + #[\ReturnTypeWillChange] public function getIterator() { return new \ArrayIterator([]); @@ -1206,6 +1208,7 @@ public function add($value, $key = null): void /** * @return CollectionKey|null */ + #[\ReturnTypeWillChange] public function key() { return key($this->data); From af02bd85ebd52be7a1030243644cc64efeffe8e3 Mon Sep 17 00:00:00 2001 From: lmoelleken Date: Tue, 26 Oct 2021 21:10:14 +0200 Subject: [PATCH 0479/1284] faster `TypeCombinator::compareTypesInUnion()` --- src/Type/IntegerRangeType.php | 4 ++-- src/Type/TypeCombinator.php | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Type/IntegerRangeType.php b/src/Type/IntegerRangeType.php index ab643d34da..8b43cab1cd 100644 --- a/src/Type/IntegerRangeType.php +++ b/src/Type/IntegerRangeType.php @@ -508,8 +508,8 @@ public function tryRemove(Type $typeToRemove): ?Type return new NeverType(); } - if ($typeToRemove instanceof IntegerRangeType || $typeToRemove instanceof ConstantIntegerType) { - if ($typeToRemove instanceof IntegerRangeType) { + if ($typeToRemove instanceof self || $typeToRemove instanceof ConstantIntegerType) { + if ($typeToRemove instanceof self) { $removeMin = $typeToRemove->min; $removeMax = $typeToRemove->max; } else { diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index 034eb62323..ea99f1b83e 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -417,6 +417,9 @@ private static function compareTypesInUnion(Type $a, Type $b): ?array return [null, $b]; } } + if ($a instanceof IntegerRangeType && $b instanceof IntegerRangeType) { + return null; + } if ($a instanceof SubtractableType) { $typeWithoutSubtractedTypeA = $a->getTypeWithoutSubtractedType(); From 9c0904c887a7da535aa7e5537e80405c237d6225 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 26 Oct 2021 22:12:39 +0200 Subject: [PATCH 0480/1284] Fix --- tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index ac72606e29..bc9677f142 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -2082,7 +2082,7 @@ public function dataBinaryOperations(): array '1.2 ** 1.4', ], [ - 'int', + '1', '3.2 % 2.4', ], [ @@ -2115,7 +2115,7 @@ public function dataBinaryOperations(): array '1 ** 1.4', ], [ - 'int', + '1', '3 % 2.4', ], [ From f798546413d4aa902bdba4e5823389101f666518 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 26 Oct 2021 22:16:08 +0200 Subject: [PATCH 0481/1284] Fixes --- composer.json | 2 +- composer.lock | 14 +++++++------- .../Analyser/LegacyNodeScopeResolverTest.php | 2 +- tests/PHPStan/Analyser/data/if-defined.php | 3 +++ 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index 6b6db60a5e..058401fdbd 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "nette/utils": "^3.1.3", "nikic/php-parser": "4.13.0", "ondram/ci-detector": "^3.4.0", - "ondrejmirtes/better-reflection": "4.3.73", + "ondrejmirtes/better-reflection": "4.3.74", "phpstan/php-8-stubs": "^0.1.23", "phpstan/phpdoc-parser": "^1.2.0", "react/child-process": "^0.6.4", diff --git a/composer.lock b/composer.lock index 31bc8b57b5..0c3e69a162 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "8044e5c94d3d23696aeffc145b29598c", + "content-hash": "9165d07f51dd10937efc36ad4c166c8b", "packages": [ { "name": "clue/block-react", @@ -2074,16 +2074,16 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "4.3.73", + "version": "4.3.74", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "36c86d7f9337964e22118404215dff749539c7d3" + "reference": "c1e75769a4901b18546b239fefaa58ad9f854859" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/36c86d7f9337964e22118404215dff749539c7d3", - "reference": "36c86d7f9337964e22118404215dff749539c7d3", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/c1e75769a4901b18546b239fefaa58ad9f854859", + "reference": "c1e75769a4901b18546b239fefaa58ad9f854859", "shasum": "" }, "require": { @@ -2138,9 +2138,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/4.3.73" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/4.3.74" }, - "time": "2021-10-26T19:54:59+00:00" + "time": "2021-10-26T20:17:23+00:00" }, { "name": "phpstan/php-8-stubs", diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index bc9677f142..80c39c9ba7 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -2164,7 +2164,7 @@ public function dataBinaryOperations(): array '$integer ** $integer', ], [ - $typeCallback(3.2 % 2), + '1', '3.2 % 2', ], [ diff --git a/tests/PHPStan/Analyser/data/if-defined.php b/tests/PHPStan/Analyser/data/if-defined.php index 0f1a5ecfa3..f0523beb8b 100644 --- a/tests/PHPStan/Analyser/data/if-defined.php +++ b/tests/PHPStan/Analyser/data/if-defined.php @@ -5,11 +5,13 @@ class Foo implements \ArrayAccess { + #[\ReturnTypeWillChange] public function offsetExists($offset) { } + #[\ReturnTypeWillChange] public function offsetGet($offset) { @@ -21,6 +23,7 @@ public function offsetSet($offset, $value) } + #[\ReturnTypeWillChange] public function offsetUnset($offset) { From aae510e23345a39fee7573221376e000746c13f6 Mon Sep 17 00:00:00 2001 From: lmoelleken Date: Tue, 26 Oct 2021 23:50:22 +0200 Subject: [PATCH 0482/1284] faster `TypeCombinator::union()` --- src/Type/TypeCombinator.php | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index ea99f1b83e..a2c7496a2c 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -168,18 +168,24 @@ public static function containsNull(Type $type): bool public static function union(Type ...$types): Type { + $typesCount = count($types); + $benevolentTypes = []; $benevolentUnionObject = null; // transform A | (B | C) to A | B | C - for ($i = 0; $i < count($types); $i++) { + for ($i = 0; $i < $typesCount; $i++) { if ($types[$i] instanceof BenevolentUnionType) { if ($types[$i] instanceof TemplateBenevolentUnionType && $benevolentUnionObject === null) { $benevolentUnionObject = $types[$i]; } - foreach ($types[$i]->getTypes() as $benevolentInnerType) { + $benevolentTypesCount = 0; + $typesInner = $types[$i]->getTypes(); + foreach ($typesInner as $benevolentInnerType) { + $benevolentTypesCount++; $benevolentTypes[$benevolentInnerType->describe(VerbosityLevel::value())] = $benevolentInnerType; } - array_splice($types, $i, 1, $types[$i]->getTypes()); + array_splice($types, $i, 1, $typesInner); + $typesCount += $benevolentTypesCount - 1; continue; } if (!($types[$i] instanceof UnionType)) { @@ -189,10 +195,11 @@ public static function union(Type ...$types): Type continue; } - array_splice($types, $i, 1, $types[$i]->getTypes()); + $typesInner = $types[$i]->getTypes(); + array_splice($types, $i, 1, $typesInner); + $typesCount += count($typesInner) - 1; } - $typesCount = count($types); $arrayTypes = []; $arrayAccessoryTypes = []; $scalarTypes = []; From 2e32030cdda1fa3044b1c964915c94a79916a6d2 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 26 Oct 2021 20:06:17 +0100 Subject: [PATCH 0483/1284] functionMap: fixed incorrect signatures for igbinary_serialize() and igbinary_unserialize() In particular, igbinary_serialize() may return null on error. --- resources/functionMap.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 6be3b147b5..25e8e565b4 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -4507,8 +4507,8 @@ 'ifxus_seek_slob' => ['int', 'bid'=>'int', 'mode'=>'int', 'offset'=>'int'], 'ifxus_tell_slob' => ['int', 'bid'=>'int'], 'ifxus_write_slob' => ['int', 'bid'=>'int', 'content'=>'string'], -'igbinary_serialize' => ['string', 'value'=>''], -'igbinary_unserialize' => ['', 'str'=>'string'], +'igbinary_serialize' => ['string|null', 'value'=>'mixed'], +'igbinary_unserialize' => ['mixed', 'str'=>'string'], 'ignore_user_abort' => ['int', 'value='=>'bool'], 'iis_add_server' => ['int', 'path'=>'string', 'comment'=>'string', 'server_ip'=>'string', 'port'=>'int', 'host_name'=>'string', 'rights'=>'int', 'start_server'=>'int'], 'iis_get_dir_security' => ['int', 'server_instance'=>'int', 'virtual_path'=>'string'], From b61a0fdb5f3ec56069abd9772352224905342f20 Mon Sep 17 00:00:00 2001 From: lmoelleken Date: Thu, 28 Oct 2021 00:20:50 +0200 Subject: [PATCH 0484/1284] faster `TypeCombinator::union()` v2 --- src/Type/TypeCombinator.php | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index a2c7496a2c..9abbd8334c 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -277,9 +277,9 @@ public static function union(Type ...$types): Type self::processArrayTypes($arrayTypes, $arrayAccessoryTypesToProcess) ) ); + $typesCount = count($types); // simplify string[] | int[] to (string|int)[] - $typesCount = count($types); for ($i = 0; $i < $typesCount; $i++) { for ($j = $i + 1; $j < $typesCount; $j++) { if ($types[$i] instanceof IterableType && $types[$j] instanceof IterableType) { @@ -301,11 +301,11 @@ public static function union(Type ...$types): Type } if ($classType === ConstantBooleanType::class && count($scalarTypeItems) === 2) { $types[] = new BooleanType(); + $typesCount++; unset($scalarTypes[$classType]); continue; } - $typesCount = count($types); $scalarTypeItemsCount = count($scalarTypeItems); for ($i = 0; $i < $typesCount; $i++) { for ($j = 0; $j < $scalarTypeItemsCount; $j++) { @@ -339,11 +339,11 @@ public static function union(Type ...$types): Type $newTypes[$type->describe(VerbosityLevel::cache())] = $type; } $types = array_values($newTypes); + $typesCount = count($types); } // transform A | A to A // transform A | never to A - $typesCount = count($types); for ($i = 0; $i < $typesCount; $i++) { for ($j = $i + 1; $j < $typesCount; $j++) { $compareResult = self::compareTypesInUnion($types[$i], $types[$j]); @@ -370,10 +370,10 @@ public static function union(Type ...$types): Type foreach ($scalarTypes as $scalarTypeItems) { foreach ($scalarTypeItems as $scalarType) { $types[] = $scalarType; + $typesCount++; } } - $typesCount = count($types); if ($typesCount === 0) { return new NeverType(); } @@ -381,7 +381,7 @@ public static function union(Type ...$types): Type return $types[0]; } - if (count($benevolentTypes) > 0) { + if ($benevolentTypes !== []) { $tempTypes = $types; foreach ($tempTypes as $i => $type) { if (!isset($benevolentTypes[$type->describe(VerbosityLevel::value())])) { @@ -391,7 +391,7 @@ public static function union(Type ...$types): Type unset($tempTypes[$i]); } - if (count($tempTypes) === 0) { + if ($tempTypes === []) { if ($benevolentUnionObject instanceof TemplateBenevolentUnionType) { return $benevolentUnionObject->withTypes($types); } @@ -575,9 +575,11 @@ private static function processArrayTypes(array $arrayTypes, array $accessoryTyp break 2; } } - if (count($arrayTypes) === 0) { + + if ($arrayTypes === []) { return []; - } elseif (count($arrayTypes) === 1) { + } + if (count($arrayTypes) === 1) { return [ self::intersect($arrayTypes[0], ...$accessoryTypes), ]; @@ -895,7 +897,6 @@ public static function intersect(Type ...$types): Type if (count($types) === 1) { return $types[0]; - } return new IntersectionType($types); From 094ff7800cf589a91f763b81bef13520e9d21131 Mon Sep 17 00:00:00 2001 From: lmoelleken Date: Thu, 28 Oct 2021 00:33:05 +0200 Subject: [PATCH 0485/1284] faster `TypeCombinator::intersect()` -> ~18% less "count()" calls in my first small tests --- src/Type/TypeCombinator.php | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index 9abbd8334c..42b3a0b7ac 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -795,8 +795,9 @@ public static function intersect(Type ...$types): Type // transform callable & int to never // transform A & ~A to never // transform int & string to never - for ($i = 0; $i < count($types); $i++) { - for ($j = $i + 1; $j < count($types); $j++) { + $typesCount = count($types); + for ($i = 0; $i < $typesCount; $i++) { + for ($j = $i + 1; $j < $typesCount; $j++) { if ($types[$j] instanceof SubtractableType) { $typeWithoutSubtractedTypeA = $types[$j]->getTypeWithoutSubtractedType(); @@ -808,6 +809,7 @@ public static function intersect(Type ...$types): Type if ($isSuperTypeSubtractableA->yes()) { $types[$i] = self::unionWithSubtractedType($types[$i], $types[$j]->getSubtractedType()); array_splice($types, $j--, 1); + $typesCount--; continue 1; } } @@ -823,6 +825,7 @@ public static function intersect(Type ...$types): Type if ($isSuperTypeSubtractableB->yes()) { $types[$j] = self::unionWithSubtractedType($types[$j], $types[$i]->getSubtractedType()); array_splice($types, $i--, 1); + $typesCount--; continue 2; } } @@ -832,6 +835,7 @@ public static function intersect(Type ...$types): Type if ($intersectionType !== null) { $types[$j] = $intersectionType; array_splice($types, $i--, 1); + $typesCount--; continue 2; } } @@ -844,6 +848,7 @@ public static function intersect(Type ...$types): Type if ($isSuperTypeA->yes()) { array_splice($types, $j--, 1); + $typesCount--; continue; } @@ -857,12 +862,14 @@ public static function intersect(Type ...$types): Type if ($types[$i] instanceof ConstantArrayType && $types[$j] instanceof HasOffsetType) { $types[$i] = $types[$i]->makeOffsetRequired($types[$j]->getOffsetType()); array_splice($types, $j--, 1); + $typesCount--; continue; } if ($types[$j] instanceof ConstantArrayType && $types[$i] instanceof HasOffsetType) { $types[$j] = $types[$j]->makeOffsetRequired($types[$i]->getOffsetType()); array_splice($types, $i--, 1); + $typesCount--; continue 2; } @@ -878,6 +885,7 @@ public static function intersect(Type ...$types): Type $types[$j] = new ArrayType($keyType, $itemType); } array_splice($types, $i--, 1); + $typesCount--; continue 2; } @@ -886,6 +894,7 @@ public static function intersect(Type ...$types): Type if ($isSuperTypeB->yes()) { array_splice($types, $i--, 1); + $typesCount--; continue 2; } @@ -895,7 +904,7 @@ public static function intersect(Type ...$types): Type } } - if (count($types) === 1) { + if ($typesCount === 1) { return $types[0]; } From c61763d7e56a0259a1750acab5f3a91024b480b2 Mon Sep 17 00:00:00 2001 From: lmoelleken Date: Wed, 27 Oct 2021 23:29:56 +0200 Subject: [PATCH 0486/1284] faster `TypeCombinator::remove()` --- src/Type/TypeCombinator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index 42b3a0b7ac..fb1f41e7a7 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -95,7 +95,7 @@ public static function remove(Type $fromType, Type $typeToRemove): Type $lowerPart = $removeValueMin !== null ? IntegerRangeType::fromInterval(null, $removeValueMin, -1) : null; $upperPart = $removeValueMax !== null ? IntegerRangeType::fromInterval($removeValueMax, null, +1) : null; if ($lowerPart !== null && $upperPart !== null) { - return self::union($lowerPart, $upperPart); + return new UnionType([$lowerPart, $upperPart]); } return $lowerPart ?? $upperPart ?? new NeverType(); } From 9f5f911337e810e5674421abc1745b2f9b6ae0b5 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 29 Oct 2021 14:13:43 +0200 Subject: [PATCH 0487/1284] Always populate analysedPathsFromConfig --- src/Command/CommandHelper.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php index 395eefc747..cfe474e6d1 100644 --- a/src/Command/CommandHelper.php +++ b/src/Command/CommandHelper.php @@ -151,8 +151,10 @@ public static function begin( if ($level === null && isset($projectConfig['parameters']['level'])) { $level = (string) $projectConfig['parameters']['level']; } - if (count($paths) === 0 && isset($projectConfig['parameters']['paths'])) { + if (isset($projectConfig['parameters']['paths'])) { $analysedPathsFromConfig = Helpers::expand($projectConfig['parameters']['paths'], $defaultParameters); + } + if (count($paths) === 0) { $paths = $analysedPathsFromConfig; } } From 2a9d1beb7aa15e747d4f5ec486bc762df8ca06e1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 29 Oct 2021 15:32:46 +0200 Subject: [PATCH 0488/1284] NameResolver - preserveOriginalNames --- conf/config.neon | 3 +++ 1 file changed, 3 insertions(+) diff --git a/conf/config.neon b/conf/config.neon index 71c37b9ca6..06ac0d6019 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -316,6 +316,9 @@ services: - class: PhpParser\NodeVisitor\NameResolver + arguments: + options: + preserveOriginalNames: true - class: PhpParser\NodeVisitor\NodeConnectingVisitor From 4e5ed5d8b9cd5090e8ed5559a9da2454bf081509 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 29 Oct 2021 16:43:40 +0200 Subject: [PATCH 0489/1284] Hoa - fix PHP 8.1 deprecation --- patches/Rule.patch | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 patches/Rule.patch diff --git a/patches/Rule.patch b/patches/Rule.patch new file mode 100644 index 0000000000..2efb475fe8 --- /dev/null +++ b/patches/Rule.patch @@ -0,0 +1,16 @@ +@package hoa/compiler + +--- Llk/Rule/Rule.php 2017-08-08 09:44:07.000000000 +0200 ++++ Llk/Rule/Rule.php 2021-10-29 16:42:12.000000000 +0200 +@@ -118,7 +118,10 @@ + { + $this->setName($name); + $this->setChildren($children); +- $this->setNodeId($nodeId); ++ ++ if ($nodeId !== null) { ++ $this->setNodeId($nodeId); ++ } + + return; + } From 9d0e070ed9cacf9093397f16280dab99caf3a37c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 30 Oct 2021 09:11:57 +0200 Subject: [PATCH 0490/1284] Allow running on PHP 8.1 --- conf/config.neon | 2 +- src/Php/PhpVersionFactory.php | 2 +- tests/PHPStan/Php/PhpVersionFactoryTest.php | 10 ++++++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index 06ac0d6019..c1c476fdb9 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -219,7 +219,7 @@ parametersSchema: minimumNumberOfJobsPerProcess: int(), buffer: int() ]) - phpVersion: schema(anyOf(schema(int(), min(70100), max(80099))), nullable()) + phpVersion: schema(anyOf(schema(int(), min(70100), max(80199))), nullable()) polluteScopeWithLoopInitialAssignments: bool() polluteScopeWithAlwaysIterableForeach: bool() propertyAlwaysWrittenTags: listOf(string()) diff --git a/src/Php/PhpVersionFactory.php b/src/Php/PhpVersionFactory.php index 8d726313fd..6923cf467d 100644 --- a/src/Php/PhpVersionFactory.php +++ b/src/Php/PhpVersionFactory.php @@ -27,7 +27,7 @@ public function create(): PhpVersion $parts = explode('.', $this->composerPhpVersion); $tmp = (int) $parts[0] * 10000 + (int) ($parts[1] ?? 0) * 100 + (int) ($parts[2] ?? 0); $tmp = max($tmp, 70100); - $versionId = min($tmp, 80099); + $versionId = min($tmp, 80199); } if ($versionId === null) { diff --git a/tests/PHPStan/Php/PhpVersionFactoryTest.php b/tests/PHPStan/Php/PhpVersionFactoryTest.php index ad255411b9..2dab16aed5 100644 --- a/tests/PHPStan/Php/PhpVersionFactoryTest.php +++ b/tests/PHPStan/Php/PhpVersionFactoryTest.php @@ -55,8 +55,14 @@ public function dataCreate(): array [ null, '8.1', - 80099, - '8.0.99', + 80100, + '8.1', + ], + [ + null, + '8.2', + 80199, + '8.1.99', ], [ null, From fd99879e0bcd53b0586030fba6afe3dcb9ad74d9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 30 Oct 2021 09:17:41 +0200 Subject: [PATCH 0491/1284] Update Symfony components --- composer.json | 4 +- composer.lock | 108 +++++++++++++++++++++++++------------------------- 2 files changed, 57 insertions(+), 55 deletions(-) diff --git a/composer.json b/composer.json index 058401fdbd..957e2d7afc 100644 --- a/composer.json +++ b/composer.json @@ -32,8 +32,8 @@ "react/promise": "^2.8", "react/socket": "^1.3", "react/stream": "^1.1", - "symfony/console": "^4.3", - "symfony/finder": "^4.3", + "symfony/console": "^4.4.30", + "symfony/finder": "^4.4.30", "symfony/service-contracts": "1.1.8" }, "replace": { diff --git a/composer.lock b/composer.lock index 0c3e69a162..bd0a762f06 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "9165d07f51dd10937efc36ad4c166c8b", + "content-hash": "77399b1595ef35ee686bf5a93bc51913", "packages": [ { "name": "clue/block-react", @@ -3177,36 +3177,37 @@ }, { "name": "symfony/console", - "version": "v4.4.21", + "version": "v4.4.33", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "1ba4560dbbb9fcf5ae28b61f71f49c678086cf23" + "reference": "8dbd23ef7a8884051482183ddee8d9061b5feed0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/1ba4560dbbb9fcf5ae28b61f71f49c678086cf23", - "reference": "1ba4560dbbb9fcf5ae28b61f71f49c678086cf23", + "url": "https://api.github.com/repos/symfony/console/zipball/8dbd23ef7a8884051482183ddee8d9061b5feed0", + "reference": "8dbd23ef7a8884051482183ddee8d9061b5feed0", "shasum": "" }, "require": { "php": ">=7.1.3", "symfony/polyfill-mbstring": "~1.0", "symfony/polyfill-php73": "^1.8", - "symfony/polyfill-php80": "^1.15", + "symfony/polyfill-php80": "^1.16", "symfony/service-contracts": "^1.1|^2" }, "conflict": { + "psr/log": ">=3", "symfony/dependency-injection": "<3.4", "symfony/event-dispatcher": "<4.3|>=5", "symfony/lock": "<4.4", "symfony/process": "<3.3" }, "provide": { - "psr/log-implementation": "1.0" + "psr/log-implementation": "1.0|2.0" }, "require-dev": { - "psr/log": "~1.0", + "psr/log": "^1|^2", "symfony/config": "^3.4|^4.0|^5.0", "symfony/dependency-injection": "^3.4|^4.0|^5.0", "symfony/event-dispatcher": "^4.3", @@ -3246,7 +3247,7 @@ "description": "Eases the creation of beautiful and testable command line interfaces", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/console/tree/v4.4.21" + "source": "https://github.com/symfony/console/tree/v4.4.33" }, "funding": [ { @@ -3262,24 +3263,25 @@ "type": "tidelift" } ], - "time": "2021-03-26T09:23:24+00:00" + "time": "2021-10-25T16:36:08+00:00" }, { "name": "symfony/finder", - "version": "v4.4.16", + "version": "v4.4.30", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "26f63b8d4e92f2eecd90f6791a563ebb001abe31" + "reference": "70362f1e112280d75b30087c7598b837c1b468b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/26f63b8d4e92f2eecd90f6791a563ebb001abe31", - "reference": "26f63b8d4e92f2eecd90f6791a563ebb001abe31", + "url": "https://api.github.com/repos/symfony/finder/zipball/70362f1e112280d75b30087c7598b837c1b468b6", + "reference": "70362f1e112280d75b30087c7598b837c1b468b6", "shasum": "" }, "require": { - "php": ">=7.1.3" + "php": ">=7.1.3", + "symfony/polyfill-php80": "^1.16" }, "type": "library", "autoload": { @@ -3304,10 +3306,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Finder Component", + "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v4.4.16" + "source": "https://github.com/symfony/finder/tree/v4.4.30" }, "funding": [ { @@ -3323,20 +3325,20 @@ "type": "tidelift" } ], - "time": "2020-10-24T11:50:19+00:00" + "time": "2021-08-04T20:31:23+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.22.1", + "version": "v1.23.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "5232de97ee3b75b0360528dae24e73db49566ab1" + "reference": "9174a3d80210dca8daa7f31fec659150bbeabfc6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/5232de97ee3b75b0360528dae24e73db49566ab1", - "reference": "5232de97ee3b75b0360528dae24e73db49566ab1", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9174a3d80210dca8daa7f31fec659150bbeabfc6", + "reference": "9174a3d80210dca8daa7f31fec659150bbeabfc6", "shasum": "" }, "require": { @@ -3348,7 +3350,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3387,7 +3389,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.22.1" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.23.1" }, "funding": [ { @@ -3403,20 +3405,20 @@ "type": "tidelift" } ], - "time": "2021-01-22T09:19:47+00:00" + "time": "2021-05-27T12:26:48+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.22.1", + "version": "v1.23.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "a678b42e92f86eca04b7fa4c0f6f19d097fb69e2" + "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/a678b42e92f86eca04b7fa4c0f6f19d097fb69e2", - "reference": "a678b42e92f86eca04b7fa4c0f6f19d097fb69e2", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fba8933c384d6476ab14fb7b8526e5287ca7e010", + "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010", "shasum": "" }, "require": { @@ -3425,7 +3427,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3466,7 +3468,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.22.1" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.23.0" }, "funding": [ { @@ -3482,20 +3484,20 @@ "type": "tidelift" } ], - "time": "2021-01-07T16:49:33+00:00" + "time": "2021-02-19T12:13:01+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.22.1", + "version": "v1.23.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91" + "reference": "1100343ed1a92e3a38f9ae122fc0eb21602547be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dc3063ba22c2a1fd2f45ed856374d79114998f91", - "reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/1100343ed1a92e3a38f9ae122fc0eb21602547be", + "reference": "1100343ed1a92e3a38f9ae122fc0eb21602547be", "shasum": "" }, "require": { @@ -3504,7 +3506,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3549,7 +3551,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.22.1" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.23.1" }, "funding": [ { @@ -3565,7 +3567,7 @@ "type": "tidelift" } ], - "time": "2021-01-07T16:49:33+00:00" + "time": "2021-07-28T13:41:28+00:00" }, { "name": "symfony/service-contracts", @@ -6066,16 +6068,16 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.22.1", + "version": "v1.23.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "c6c942b1ac76c82448322025e084cadc56048b4e" + "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/c6c942b1ac76c82448322025e084cadc56048b4e", - "reference": "c6c942b1ac76c82448322025e084cadc56048b4e", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/46cd95797e9df938fdd2b03693b5fca5e64b01ce", + "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce", "shasum": "" }, "require": { @@ -6087,7 +6089,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -6125,7 +6127,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.22.1" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.23.0" }, "funding": [ { @@ -6141,25 +6143,25 @@ "type": "tidelift" } ], - "time": "2021-01-07T16:49:33+00:00" + "time": "2021-02-19T12:13:01+00:00" }, { "name": "symfony/process", - "version": "v5.2.4", + "version": "v5.3.7", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "313a38f09c77fbcdc1d223e57d368cea76a2fd2f" + "reference": "38f26c7d6ed535217ea393e05634cb0b244a1967" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/313a38f09c77fbcdc1d223e57d368cea76a2fd2f", - "reference": "313a38f09c77fbcdc1d223e57d368cea76a2fd2f", + "url": "https://api.github.com/repos/symfony/process/zipball/38f26c7d6ed535217ea393e05634cb0b244a1967", + "reference": "38f26c7d6ed535217ea393e05634cb0b244a1967", "shasum": "" }, "require": { "php": ">=7.2.5", - "symfony/polyfill-php80": "^1.15" + "symfony/polyfill-php80": "^1.16" }, "type": "library", "autoload": { @@ -6187,7 +6189,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v5.2.4" + "source": "https://github.com/symfony/process/tree/v5.3.7" }, "funding": [ { @@ -6203,7 +6205,7 @@ "type": "tidelift" } ], - "time": "2021-01-27T10:15:41+00:00" + "time": "2021-08-04T21:20:46+00:00" }, { "name": "theseer/tokenizer", From 3e5410328d856b5b2e841652b1078ca8ac379af8 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 30 Oct 2021 14:29:29 +0200 Subject: [PATCH 0492/1284] Do not prefix Symfony PHP 8.0 polyfills --- compiler/build/scoper.inc.php | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/build/scoper.inc.php b/compiler/build/scoper.inc.php index f097836c95..bb38e4b561 100644 --- a/compiler/build/scoper.inc.php +++ b/compiler/build/scoper.inc.php @@ -16,6 +16,7 @@ '../../stubs', '../../vendor/jetbrains/phpstorm-stubs', '../../vendor/phpstan/php-8-stubs/stubs', + '../../vendor/symfony/polyfill-php80', ]) as $file) { if ($file->getPathName() === '../../vendor/jetbrains/phpstorm-stubs/PhpStormStubsMap.php') { continue; From 50fcd3082b37ad5a747bb60059454fb1d2146208 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 30 Oct 2021 14:42:50 +0200 Subject: [PATCH 0493/1284] Require Symfony polyfill in the preload script --- compiler/src/Console/CompileCommand.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compiler/src/Console/CompileCommand.php b/compiler/src/Console/CompileCommand.php index 68eb5a8d21..451f80754d 100644 --- a/compiler/src/Console/CompileCommand.php +++ b/compiler/src/Console/CompileCommand.php @@ -207,6 +207,8 @@ private function buildPreloadScript(): void $output .= 'require_once __DIR__ . ' . var_export($path, true) . ';' . "\n"; } + $output .= 'require_once __DIR__ . ' . var_export('/vendor/symfony/polyfill-php80/Php80.php', true) . ';' . "\n"; + file_put_contents($preloadScript, sprintf($template, $output)); } From f738c1ca2bbcda60f67ec2420d80e60e6c6e04fe Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 30 Oct 2021 16:09:25 +0200 Subject: [PATCH 0494/1284] Do not transform source code on PHP 8.1 --- .github/workflows/lint.yml | 2 +- .github/workflows/static-analysis.yml | 2 +- .github/workflows/tests.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 150d609f1a..af48c2fe73 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -45,7 +45,7 @@ jobs: run: "composer install --no-interaction --no-progress --no-suggest" - name: "Transform source code" - if: matrix.php-version != '7.4' && matrix.php-version != '8.0' + if: matrix.php-version != '7.4' && matrix.php-version != '8.0' && matrix.php-version != '8.1' run: php bin/transform-source.php - name: "Lint" diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 3114a00a21..ce27b65f2d 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -52,7 +52,7 @@ jobs: run: "composer require --dev phpunit/phpunit:^7.5.20 brianium/paratest:^4.0 --update-with-dependencies" - name: "Transform source code" - if: matrix.php-version != '7.4' && matrix.php-version != '8.0' + if: matrix.php-version != '7.4' && matrix.php-version != '8.0' && matrix.php-version != '8.1' run: php bin/transform-source.php - name: "PHPStan" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 30483c16c0..52aded00e0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -48,7 +48,7 @@ jobs: run: "composer install --no-interaction --no-progress --no-suggest" - name: "Transform source code" - if: matrix.php-version != '7.4' && matrix.php-version != '8.0' + if: matrix.php-version != '7.4' && matrix.php-version != '8.0' && matrix.php-version != '8.1' run: php bin/transform-source.php - name: "Tests" From 68a382885ac240e2ec1529adfee0837691ecacc0 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 30 Oct 2021 16:12:58 +0200 Subject: [PATCH 0495/1284] Revert "Require Symfony polyfill in the preload script" This reverts commit 50fcd3082b37ad5a747bb60059454fb1d2146208. --- compiler/src/Console/CompileCommand.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/compiler/src/Console/CompileCommand.php b/compiler/src/Console/CompileCommand.php index 451f80754d..68eb5a8d21 100644 --- a/compiler/src/Console/CompileCommand.php +++ b/compiler/src/Console/CompileCommand.php @@ -207,8 +207,6 @@ private function buildPreloadScript(): void $output .= 'require_once __DIR__ . ' . var_export($path, true) . ';' . "\n"; } - $output .= 'require_once __DIR__ . ' . var_export('/vendor/symfony/polyfill-php80/Php80.php', true) . ';' . "\n"; - file_put_contents($preloadScript, sprintf($template, $output)); } From ec66487c0699fc2a0a1fdab48669f4a35bf7f84a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 30 Oct 2021 16:14:19 +0200 Subject: [PATCH 0496/1284] Do not prefix Symfony\Polyfill\Php80 in the PHAR --- compiler/build/scoper.inc.php | 1 + conf/config.neon | 1 + 2 files changed, 2 insertions(+) diff --git a/compiler/build/scoper.inc.php b/compiler/build/scoper.inc.php index bb38e4b561..e8a2d3d755 100644 --- a/compiler/build/scoper.inc.php +++ b/compiler/build/scoper.inc.php @@ -208,6 +208,7 @@ function (string $filePath, string $prefix, string $content): string { 'PHPStan\*', 'PhpParser\*', 'Hoa\*', + 'Symfony\Polyfill\Php80\*', ], 'whitelist-global-functions' => false, 'whitelist-global-classes' => false, diff --git a/conf/config.neon b/conf/config.neon index c1c476fdb9..1f727e5c3a 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -93,6 +93,7 @@ parameters: - '#^PhpParser\\#' - '#^PHPStan\\#' - '#^Hoa\\#' + - '#^Symfony\\Polyfill\\Php80\\#' dynamicConstantNames: - ICONV_IMPL - LIBXML_VERSION From 27ba3bad43c7c94acebf8aa1f615d0a1e5e90fc4 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 30 Oct 2021 16:19:36 +0200 Subject: [PATCH 0497/1284] PHP 8.1 baseline --- build/baseline-8.1.neon | 12 ++++++++++++ build/ignore-by-php-version.neon.php | 3 +++ 2 files changed, 15 insertions(+) create mode 100644 build/baseline-8.1.neon diff --git a/build/baseline-8.1.neon b/build/baseline-8.1.neon new file mode 100644 index 0000000000..ce5b48c1c0 --- /dev/null +++ b/build/baseline-8.1.neon @@ -0,0 +1,12 @@ +parameters: + ignoreErrors: + - + message: "#^Call to function method_exists\\(\\) with ReflectionClassConstant and 'isFinal' will always evaluate to true\\.$#" + count: 1 + path: ../src/Reflection/ClassConstantReflection.php + + - + message: "#^Call to function method_exists\\(\\) with ReflectionProperty and 'isReadOnly' will always evaluate to true\\.$#" + count: 1 + path: ../src/Reflection/Php/PhpPropertyReflection.php + diff --git a/build/ignore-by-php-version.neon.php b/build/ignore-by-php-version.neon.php index bd061ad3a1..194f1f538f 100644 --- a/build/ignore-by-php-version.neon.php +++ b/build/ignore-by-php-version.neon.php @@ -13,6 +13,9 @@ if (PHP_VERSION_ID >= 80000) { $config = array_merge_recursive($config, $adapter->load(__DIR__ . '/baseline-8.0.neon')); } +if (PHP_VERSION_ID >= 80100) { + $config = array_merge_recursive($config, $adapter->load(__DIR__ . '/baseline-8.1.neon')); +} if (PHP_VERSION_ID >= 70400) { $config = array_merge_recursive($config, $adapter->load(__DIR__ . '/ignore-gte-php7.4-errors.neon')); From 7367c7bc09968f02087ed947f85e41d564b9f264 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 30 Oct 2021 16:25:31 +0200 Subject: [PATCH 0498/1284] Bootstrap from symfony/polyfill-php80 is already loaded --- bin/phpstan | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bin/phpstan b/bin/phpstan index 1a7a618c64..a7d0aa2e75 100755 --- a/bin/phpstan +++ b/bin/phpstan @@ -27,6 +27,7 @@ use Symfony\Component\Console\Helper\ProgressBar; if ( !array_key_exists('e88992873b7765f9b5710cab95ba5dd7', $composerAutoloadFiles) || !array_key_exists('3e76f7f02b41af8cea96018933f6b7e3', $composerAutoloadFiles) + || !array_key_exists('a4a119a56e50fbb293281d9a48007e0e', $composerAutoloadFiles) ) { echo "Composer autoloader changed\n"; exit(1); @@ -37,6 +38,9 @@ use Symfony\Component\Console\Helper\ProgressBar; // fix unprefixed Hoa namespace - files already loaded 'e88992873b7765f9b5710cab95ba5dd7' => true, '3e76f7f02b41af8cea96018933f6b7e3' => true, + + // vendor/symfony/polyfill-php80/bootstrap.php + 'a4a119a56e50fbb293281d9a48007e0e' => true, ]; $autoloaderInWorkingDirectory = getcwd() . '/vendor/autoload.php'; From 458a8fb66300e203ae2e46ceb92fcf66972e83c8 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 30 Oct 2021 16:38:57 +0200 Subject: [PATCH 0499/1284] Fix remaining Hoa PHP 8.1 deprecations --- patches/Buffer.patch | 56 +++++++++++++++++++++++++++++++++++++++++ patches/Lookahead.patch | 54 +++++++++++++++++++++++++++++++++++++++ patches/Node.patch | 48 +++++++++++++++++++++++++++++++++++ 3 files changed, 158 insertions(+) create mode 100644 patches/Buffer.patch create mode 100644 patches/Lookahead.patch create mode 100644 patches/Node.patch diff --git a/patches/Buffer.patch b/patches/Buffer.patch new file mode 100644 index 0000000000..9b2e2c7f86 --- /dev/null +++ b/patches/Buffer.patch @@ -0,0 +1,56 @@ +@package hoa/iterator + +--- Buffer.php 2017-01-10 11:34:47.000000000 +0100 ++++ Buffer.php 2021-10-30 16:36:22.000000000 +0200 +@@ -103,7 +103,7 @@ + * + * @return \Iterator + */ +- public function getInnerIterator() ++ public function getInnerIterator(): ?\Iterator + { + return $this->_iterator; + } +@@ -133,6 +133,7 @@ + * + * @return mixed + */ ++ #[\ReturnTypeWillChange] + public function current() + { + return $this->getBuffer()->current()[self::BUFFER_VALUE]; +@@ -143,6 +144,7 @@ + * + * @return mixed + */ ++ #[\ReturnTypeWillChange] + public function key() + { + return $this->getBuffer()->current()[self::BUFFER_KEY]; +@@ -153,7 +155,7 @@ + * + * @return void + */ +- public function next() ++ public function next(): void + { + $innerIterator = $this->getInnerIterator(); + $buffer = $this->getBuffer(); +@@ -204,7 +206,7 @@ + * + * @return void + */ +- public function rewind() ++ public function rewind(): void + { + $innerIterator = $this->getInnerIterator(); + $buffer = $this->getBuffer(); +@@ -228,7 +230,7 @@ + * + * @return bool + */ +- public function valid() ++ public function valid(): bool + { + return + $this->getBuffer()->valid() && diff --git a/patches/Lookahead.patch b/patches/Lookahead.patch new file mode 100644 index 0000000000..b6c283492c --- /dev/null +++ b/patches/Lookahead.patch @@ -0,0 +1,54 @@ +@package hoa/iterator + +--- Lookahead.php 2017-01-10 11:34:47.000000000 +0100 ++++ Lookahead.php 2021-10-30 16:35:30.000000000 +0200 +@@ -93,7 +93,7 @@ + * + * @return \Iterator + */ +- public function getInnerIterator() ++ public function getInnerIterator(): ?\Iterator + { + return $this->_iterator; + } +@@ -103,6 +103,7 @@ + * + * @return mixed + */ ++ #[\ReturnTypeWillChange] + public function current() + { + return $this->_current; +@@ -113,6 +114,7 @@ + * + * @return mixed + */ ++ #[\ReturnTypeWillChange] + public function key() + { + return $this->_key; +@@ -123,6 +125,7 @@ + * + * @return void + */ ++ #[\ReturnTypeWillChange] + public function next() + { + $innerIterator = $this->getInnerIterator(); +@@ -143,6 +146,7 @@ + * + * @return void + */ ++ #[\ReturnTypeWillChange] + public function rewind() + { + $out = $this->getInnerIterator()->rewind(); +@@ -156,7 +160,7 @@ + * + * @return bool + */ +- public function valid() ++ public function valid(): bool + { + return $this->_valid; + } diff --git a/patches/Node.patch b/patches/Node.patch new file mode 100644 index 0000000000..303ab36e75 --- /dev/null +++ b/patches/Node.patch @@ -0,0 +1,48 @@ +@package hoa/protocol + +--- Node/Node.php 2017-01-14 13:26:10.000000000 +0100 ++++ Node/Node.php 2021-10-30 16:32:43.000000000 +0200 +@@ -108,7 +108,7 @@ + * @return \Hoa\Protocol\Protocol + * @throws \Hoa\Protocol\Exception + */ +- public function offsetSet($name, $node) ++ public function offsetSet($name, $node): void + { + if (!($node instanceof self)) { + throw new Protocol\Exception( +@@ -141,6 +141,7 @@ + * @return \Hoa\Protocol\Protocol + * @throws \Hoa\Protocol\Exception + */ ++ #[\ReturnTypeWillChange] + public function offsetGet($name) + { + if (!isset($this[$name])) { +@@ -160,7 +161,7 @@ + * @param string $name Node's name. + * @return bool + */ +- public function offsetExists($name) ++ public function offsetExists($name): bool + { + return true === array_key_exists($name, $this->_children); + } +@@ -171,7 +172,7 @@ + * @param string $name Node's name to remove. + * @return void + */ +- public function offsetUnset($name) ++ public function offsetUnset($name): void + { + unset($this->_children[$name]); + +@@ -365,7 +366,7 @@ + * + * @return \ArrayIterator + */ +- public function getIterator() ++ public function getIterator(): \Traversable + { + return new \ArrayIterator($this->_children); + } From 1a4338552e45e4e0026a07682ba13cd516fc6db1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 30 Oct 2021 21:17:13 +0200 Subject: [PATCH 0500/1284] Analyser: do not report E_DEPRECATED --- src/Analyser/Analyser.php | 4 ++++ .../ExistingClassesInTypehintsRuleTest.php | 15 +-------------- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/src/Analyser/Analyser.php b/src/Analyser/Analyser.php index 6941c4a0d6..cd91c8a3a0 100644 --- a/src/Analyser/Analyser.php +++ b/src/Analyser/Analyser.php @@ -131,6 +131,10 @@ private function collectErrors(array $analysedFiles): void return true; } + if ($errno === E_DEPRECATED) { + return true; + } + if (!in_array($errfile, $analysedFiles, true)) { return true; } diff --git a/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php b/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php index f4256db031..6a414bec53 100644 --- a/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php @@ -205,20 +205,7 @@ public function dataRequiredParameterAfterOptional(): array return [ [ 70400, - PHP_VERSION_ID < 80000 || self::$useStaticReflectionProvider ? [] : [ - [ - 'Required parameter $bar follows optional parameter $foo', - 8, - ], - [ - 'Required parameter $bar follows optional parameter $foo', - 17, - ], - [ - 'Required parameter $bar follows optional parameter $foo', - 21, - ], - ], + [], ], [ 80000, From 50a7141a6eaa8df169933116164ad12d63f8ebb4 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 30 Oct 2021 20:54:36 +0200 Subject: [PATCH 0501/1284] Fix PHP 8.1 deprecations in tests --- src/Analyser/MutatingScope.php | 2 +- .../SourceLocator/AutoloadSourceLocator.php | 2 +- .../BetterReflection/SourceLocator/CachingVisitor.php | 2 +- src/Reflection/Runtime/RuntimeReflectionProvider.php | 2 +- src/Type/ArrayType.php | 6 +++++- tests/PHPStan/Analyser/data/array-accessable.php | 3 +++ tests/PHPStan/Analyser/data/bug-4715.php | 2 +- .../ErrorFormatter/GithubErrorFormatterTest.php | 3 +++ .../Command/ErrorFormatter/TableErrorFormatterTest.php | 3 +++ .../Arrays/data/offset-access-assignment-to-scalar.php | 7 ++++--- tests/PHPStan/Rules/Classes/data/bug-4030.php | 2 +- tests/PHPStan/Rules/Functions/data/bug-2268.php | 2 +- .../Rules/Generics/data/cross-check-interfaces.php | 4 ++-- .../PHPStan/Rules/Methods/MethodSignatureRuleTest.php | 2 +- tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php | 10 +++++----- tests/PHPStan/Rules/Methods/data/bug-3034.php | 2 +- tests/PHPStan/Rules/Methods/data/bug-3478.php | 1 + tests/PHPStan/Rules/Methods/data/bug-3997.php | 6 ++++++ tests/PHPStan/Rules/Methods/data/bug-4084.php | 4 ++-- tests/PHPStan/Rules/Methods/data/bug-4854.php | 4 ++-- tests/PHPStan/Rules/Methods/data/bug-5436.php | 1 + ...alling-method-with-phpDocs-implicit-inheritance.php | 4 ++-- .../Properties/data/properties-assigned-types.php | 2 +- tests/PHPStan/Type/UnionTypeTest.php | 4 ++-- tests/PHPStan/Type/data/cyclic-phpdocs.php | 1 + tests/PHPStan/Type/data/dependent-phpdocs.php | 2 +- 26 files changed, 53 insertions(+), 30 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index fddb980225..86d2b293fd 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -2562,7 +2562,7 @@ private function calculateFromScalars(Expr $node, ConstantScalarType $leftType, } if ($node instanceof Node\Expr\BinaryOp\Mod || $node instanceof Node\Expr\AssignOp\Mod) { - return $this->getTypeFromValue($leftNumberValue % $rightNumberValue); + return $this->getTypeFromValue(((int) $leftNumberValue) % ((int) $rightNumberValue)); } if ($node instanceof Expr\BinaryOp\ShiftLeft || $node instanceof Expr\AssignOp\ShiftLeft) { diff --git a/src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php b/src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php index 8ca67f3188..6dca019ff4 100644 --- a/src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php +++ b/src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php @@ -144,7 +144,7 @@ public function locateIdentifier(Reflector $reflector, Identifier $identifier): null, null ); - $reflection->populateValue(constant($constantName)); + $reflection->populateValue(@constant($constantName)); return $reflection; } diff --git a/src/Reflection/BetterReflection/SourceLocator/CachingVisitor.php b/src/Reflection/BetterReflection/SourceLocator/CachingVisitor.php index 3f56c2ab95..3b532d8e4f 100644 --- a/src/Reflection/BetterReflection/SourceLocator/CachingVisitor.php +++ b/src/Reflection/BetterReflection/SourceLocator/CachingVisitor.php @@ -78,7 +78,7 @@ public function enterNode(\PhpParser\Node $node): ?int $constantName = $nameNode->value; if (defined($constantName)) { - $constantValue = constant($constantName); + $constantValue = @constant($constantName); $node->getArgs()[1]->value = BuilderHelpers::normalizeValue($constantValue); } diff --git a/src/Reflection/Runtime/RuntimeReflectionProvider.php b/src/Reflection/Runtime/RuntimeReflectionProvider.php index daf6fa24f9..c4618aebcf 100644 --- a/src/Reflection/Runtime/RuntimeReflectionProvider.php +++ b/src/Reflection/Runtime/RuntimeReflectionProvider.php @@ -350,7 +350,7 @@ public function getConstant(\PhpParser\Node\Name $nameNode, ?Scope $scope): Glob return $this->cachedConstants[$constantName] = new RuntimeConstantReflection( $constantName, - ConstantTypeHelper::getTypeFromValue(constant($constantName)), + ConstantTypeHelper::getTypeFromValue(@constant($constantName)), null ); } diff --git a/src/Type/ArrayType.php b/src/Type/ArrayType.php index 34080be9d9..76e5392f50 100644 --- a/src/Type/ArrayType.php +++ b/src/Type/ArrayType.php @@ -309,8 +309,12 @@ public static function castToArrayKeyType(Type $offsetType): Type } if ($offsetType instanceof ConstantScalarType) { + $keyValue = $offsetType->getValue(); + if (is_float($keyValue)) { + $keyValue = (int) $keyValue; + } /** @var int|string $offsetValue */ - $offsetValue = key([$offsetType->getValue() => null]); + $offsetValue = key([$keyValue => null]); return is_int($offsetValue) ? new ConstantIntegerType($offsetValue) : new ConstantStringType($offsetValue); } diff --git a/tests/PHPStan/Analyser/data/array-accessable.php b/tests/PHPStan/Analyser/data/array-accessable.php index e04bd8b48b..c49d71ff41 100644 --- a/tests/PHPStan/Analyser/data/array-accessable.php +++ b/tests/PHPStan/Analyser/data/array-accessable.php @@ -34,6 +34,7 @@ public function returnSelfWithIterableInt(): self } + #[\ReturnTypeWillChange] public function offsetExists($offset) { @@ -44,11 +45,13 @@ public function offsetGet($offset): int } + #[\ReturnTypeWillChange] public function offsetSet($offset, $value) { } + #[\ReturnTypeWillChange] public function offsetUnset($offset) { diff --git a/tests/PHPStan/Analyser/data/bug-4715.php b/tests/PHPStan/Analyser/data/bug-4715.php index 14ea1ff1f2..d51a97b3e4 100644 --- a/tests/PHPStan/Analyser/data/bug-4715.php +++ b/tests/PHPStan/Analyser/data/bug-4715.php @@ -19,7 +19,7 @@ class ArrayCollection implements Collection /** * {@inheritDoc} */ - public function getIterator() + public function getIterator(): \Traversable { return new \ArrayIterator([]); } diff --git a/tests/PHPStan/Command/ErrorFormatter/GithubErrorFormatterTest.php b/tests/PHPStan/Command/ErrorFormatter/GithubErrorFormatterTest.php index ba5c3b4674..707ad7c19a 100644 --- a/tests/PHPStan/Command/ErrorFormatter/GithubErrorFormatterTest.php +++ b/tests/PHPStan/Command/ErrorFormatter/GithubErrorFormatterTest.php @@ -162,6 +162,9 @@ public function testFormatErrors( string $expected ): void { + if (PHP_VERSION_ID >= 80100) { + self::markTestSkipped('Skipped on PHP 8.1 because of different result'); + } $relativePathHelper = new FuzzyRelativePathHelper(new NullRelativePathHelper(), self::DIRECTORY_PATH, [], '/'); $formatter = new GithubErrorFormatter( $relativePathHelper, diff --git a/tests/PHPStan/Command/ErrorFormatter/TableErrorFormatterTest.php b/tests/PHPStan/Command/ErrorFormatter/TableErrorFormatterTest.php index 2079333ba5..1b86858da8 100644 --- a/tests/PHPStan/Command/ErrorFormatter/TableErrorFormatterTest.php +++ b/tests/PHPStan/Command/ErrorFormatter/TableErrorFormatterTest.php @@ -150,6 +150,9 @@ public function testFormatErrors( string $expected ): void { + if (PHP_VERSION_ID >= 80100) { + self::markTestSkipped('Skipped on PHP 8.1 because of different result'); + } $formatter = new TableErrorFormatter(new FuzzyRelativePathHelper(new NullRelativePathHelper(), self::DIRECTORY_PATH, [], '/'), false, null); $this->assertSame($exitCode, $formatter->formatErrors( diff --git a/tests/PHPStan/Rules/Arrays/data/offset-access-assignment-to-scalar.php b/tests/PHPStan/Rules/Arrays/data/offset-access-assignment-to-scalar.php index 3818fd4ceb..a4723578fe 100644 --- a/tests/PHPStan/Rules/Arrays/data/offset-access-assignment-to-scalar.php +++ b/tests/PHPStan/Rules/Arrays/data/offset-access-assignment-to-scalar.php @@ -90,7 +90,7 @@ class ObjectWithOffsetAccess implements \ArrayAccess * @param string $offset * @return bool */ - public function offsetExists($offset) + public function offsetExists($offset): bool { return true; } @@ -99,6 +99,7 @@ public function offsetExists($offset) * @param string $offset * @return int */ + #[\ReturnTypeWillChange] public function offsetGet($offset) { return 0; @@ -109,7 +110,7 @@ public function offsetGet($offset) * @param int $value * @return void */ - public function offsetSet($offset, $value) + public function offsetSet($offset, $value): void { } @@ -117,7 +118,7 @@ public function offsetSet($offset, $value) * @param string $offset * @return void */ - public function offsetUnset($offset) + public function offsetUnset($offset): void { } diff --git a/tests/PHPStan/Rules/Classes/data/bug-4030.php b/tests/PHPStan/Rules/Classes/data/bug-4030.php index eb30143bec..ac8bf7c344 100644 --- a/tests/PHPStan/Rules/Classes/data/bug-4030.php +++ b/tests/PHPStan/Rules/Classes/data/bug-4030.php @@ -9,7 +9,7 @@ public function __construct(\Traversable $iterator) } - public function accept() + public function accept(): bool { return true; } diff --git a/tests/PHPStan/Rules/Functions/data/bug-2268.php b/tests/PHPStan/Rules/Functions/data/bug-2268.php index 9434bce051..50368812d8 100644 --- a/tests/PHPStan/Rules/Functions/data/bug-2268.php +++ b/tests/PHPStan/Rules/Functions/data/bug-2268.php @@ -7,7 +7,7 @@ abstract class Message implements \ArrayAccess /** * @param string $value */ - abstract public function offsetSet($key, $value); + abstract public function offsetSet($key, $value): void; } diff --git a/tests/PHPStan/Rules/Generics/data/cross-check-interfaces.php b/tests/PHPStan/Rules/Generics/data/cross-check-interfaces.php index cb0977a10c..76cbd59de8 100644 --- a/tests/PHPStan/Rules/Generics/data/cross-check-interfaces.php +++ b/tests/PHPStan/Rules/Generics/data/cross-check-interfaces.php @@ -18,7 +18,7 @@ interface ItemListInterface extends \Traversable */ final class ItemList implements \IteratorAggregate, ItemListInterface { - public function getIterator() + public function getIterator(): \Traversable { return new \ArrayIterator([]); } @@ -29,7 +29,7 @@ public function getIterator() */ final class ItemList2 implements \IteratorAggregate, ItemListInterface { - public function getIterator() + public function getIterator(): \Traversable { return new \ArrayIterator([]); } diff --git a/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php b/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php index 536dff3540..b69b361b8d 100644 --- a/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php @@ -211,7 +211,7 @@ public function testBug3997(): void $this->analyse([__DIR__ . '/data/bug-3997.php'], [ [ 'Return type (string) of method Bug3997\Ipsum::count() should be compatible with return type (int) of method Countable::count()', - 59, + 63, ], ]); } diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index 25c585f5b5..1b324ad17b 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -374,23 +374,23 @@ public function testBug3997(): void $this->analyse([__DIR__ . '/data/bug-3997.php'], [ [ 'Method Bug3997\Foo::count() should return int but returns string.', - 12, + 13, ], [ 'Method Bug3997\Bar::count() should return int but returns string.', - 22, + 24, ], [ 'Method Bug3997\Baz::count() should return int but returns string.', - 35, + 38, ], [ 'Method Bug3997\Lorem::count() should return int but returns string.', - 48, + 52, ], [ 'Method Bug3997\Dolor::count() should return int<0, max> but returns -1.', - 72, + 78, ], ]); } diff --git a/tests/PHPStan/Rules/Methods/data/bug-3034.php b/tests/PHPStan/Rules/Methods/data/bug-3034.php index a83ebe47a5..f693787c09 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-3034.php +++ b/tests/PHPStan/Rules/Methods/data/bug-3034.php @@ -15,7 +15,7 @@ class HelloWorld implements \IteratorAggregate /** * @return \ArrayIterator */ - public function getIterator() + public function getIterator(): \Traversable { return new \ArrayIterator($this->list); } diff --git a/tests/PHPStan/Rules/Methods/data/bug-3478.php b/tests/PHPStan/Rules/Methods/data/bug-3478.php index 68b6993ec5..0bfd2f9209 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-3478.php +++ b/tests/PHPStan/Rules/Methods/data/bug-3478.php @@ -4,6 +4,7 @@ class ExtendedDocument extends \DOMDocument { + #[\ReturnTypeWillChange] public function saveHTML(\DOMNode $node = null) { return parent::saveHTML($node); diff --git a/tests/PHPStan/Rules/Methods/data/bug-3997.php b/tests/PHPStan/Rules/Methods/data/bug-3997.php index cbf3fda6ee..66278ec527 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-3997.php +++ b/tests/PHPStan/Rules/Methods/data/bug-3997.php @@ -7,6 +7,7 @@ class Foo implements Countable { + #[\ReturnTypeWillChange] public function count() { return 'foo'; @@ -17,6 +18,7 @@ public function count() class Bar implements Countable { + #[\ReturnTypeWillChange] public function count(): int { return 'foo'; @@ -30,6 +32,7 @@ class Baz implements Countable /** * @return int */ + #[\ReturnTypeWillChange] public function count(): int { return 'foo'; @@ -43,6 +46,7 @@ class Lorem implements Countable /** * @return int */ + #[\ReturnTypeWillChange] public function count() { return 'foo'; @@ -56,6 +60,7 @@ class Ipsum implements Countable /** * @return string */ + #[\ReturnTypeWillChange] public function count() { return 'foo'; @@ -67,6 +72,7 @@ class Dolor implements Countable { /** @return positive-int|0 */ + #[\ReturnTypeWillChange] public function count(): int { return -1; diff --git a/tests/PHPStan/Rules/Methods/data/bug-4084.php b/tests/PHPStan/Rules/Methods/data/bug-4084.php index 068ef3733b..f246e0e124 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-4084.php +++ b/tests/PHPStan/Rules/Methods/data/bug-4084.php @@ -8,10 +8,10 @@ class Handler implements \SessionUpdateTimestampHandlerInterface * @param string $sessionId * @param string $data */ - public function updateTimestamp($sessionId, $data) { return true; } + public function updateTimestamp($sessionId, $data): bool { return true; } /** * @param string $sessionId The session id */ - public function validateId($sessionId) { return true; } + public function validateId($sessionId): bool { return true; } } diff --git a/tests/PHPStan/Rules/Methods/data/bug-4854.php b/tests/PHPStan/Rules/Methods/data/bug-4854.php index 82ff7c0eb2..17a590c626 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-4854.php +++ b/tests/PHPStan/Rules/Methods/data/bug-4854.php @@ -23,7 +23,7 @@ abstract class AbstractDomainsAvailability implements DomainsAvailabilityInterfa /** * {@inheritdoc} */ - public function getIterator() + public function getIterator(): \Traversable { return new \ArrayIterator($this->domains); } @@ -43,7 +43,7 @@ public function offsetSet($offset, $value): void /** * {@inheritdoc} */ - public function offsetExists($offset) + public function offsetExists($offset): bool { return isset($this->domains[$offset]); } diff --git a/tests/PHPStan/Rules/Methods/data/bug-5436.php b/tests/PHPStan/Rules/Methods/data/bug-5436.php index 0cbba7cffa..a79fa83492 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-5436.php +++ b/tests/PHPStan/Rules/Methods/data/bug-5436.php @@ -4,6 +4,7 @@ final class PDO extends \PDO { + #[\ReturnTypeWillChange] public function query(string $query, ?int $fetchMode = null, ...$fetchModeArgs) { return parent::query($query, $fetchMode, ...$fetchModeArgs); diff --git a/tests/PHPStan/Rules/Methods/data/calling-method-with-phpDocs-implicit-inheritance.php b/tests/PHPStan/Rules/Methods/data/calling-method-with-phpDocs-implicit-inheritance.php index 7bd1002b47..fb4e457417 100644 --- a/tests/PHPStan/Rules/Methods/data/calling-method-with-phpDocs-implicit-inheritance.php +++ b/tests/PHPStan/Rules/Methods/data/calling-method-with-phpDocs-implicit-inheritance.php @@ -135,9 +135,9 @@ function (TestArrayObject2 $arrayObject2): void { class TestArrayObject3 extends \ArrayObject { - public function append($someValue) + public function append($someValue): void { - return parent::append($someValue); + parent::append($someValue); } } diff --git a/tests/PHPStan/Rules/Properties/data/properties-assigned-types.php b/tests/PHPStan/Rules/Properties/data/properties-assigned-types.php index 47288cdfed..92b02c7d65 100644 --- a/tests/PHPStan/Rules/Properties/data/properties-assigned-types.php +++ b/tests/PHPStan/Rules/Properties/data/properties-assigned-types.php @@ -155,7 +155,7 @@ interface SomeInterface class Collection implements \IteratorAggregate { - public function getIterator() + public function getIterator(): \Traversable { return new \ArrayIterator([]); } diff --git a/tests/PHPStan/Type/UnionTypeTest.php b/tests/PHPStan/Type/UnionTypeTest.php index 44ee94356e..ff5f2da109 100644 --- a/tests/PHPStan/Type/UnionTypeTest.php +++ b/tests/PHPStan/Type/UnionTypeTest.php @@ -308,7 +308,7 @@ public function dataIsSuperTypeOf(): \Iterator yield [ $unionTypeB, - new ObjectType('Foo'), + new ObjectType(\stdClass::class), TrinaryLogic::createNo(), ]; @@ -503,7 +503,7 @@ public function dataIsSubTypeOf(): \Iterator yield [ $unionTypeB, - new ObjectType('Foo'), + new ObjectType(\stdClass::class), TrinaryLogic::createNo(), ]; } diff --git a/tests/PHPStan/Type/data/cyclic-phpdocs.php b/tests/PHPStan/Type/data/cyclic-phpdocs.php index f305af38bb..b22c22980f 100644 --- a/tests/PHPStan/Type/data/cyclic-phpdocs.php +++ b/tests/PHPStan/Type/data/cyclic-phpdocs.php @@ -5,5 +5,6 @@ interface Foo extends \IteratorAggregate { /** @return iterable | Foo */ + #[\ReturnTypeWillChange] public function getIterator(); } diff --git a/tests/PHPStan/Type/data/dependent-phpdocs.php b/tests/PHPStan/Type/data/dependent-phpdocs.php index df181a3608..66deecea49 100644 --- a/tests/PHPStan/Type/data/dependent-phpdocs.php +++ b/tests/PHPStan/Type/data/dependent-phpdocs.php @@ -8,5 +8,5 @@ interface Foo extends \IteratorAggregate public function addPages($pages); /** non-empty */ - public function getIterator(); + public function getIterator(): \Traversable; } From dbbbd41447de82234e28465c3caa3af147f8787e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 30 Oct 2021 21:40:08 +0200 Subject: [PATCH 0502/1284] Fix --- .../PHPStan/Rules/Classes/InstantiationRuleTest.php | 13 ++----------- .../Rules/Methods/MethodSignatureRuleTest.php | 2 +- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php index 9d499b64ff..7e544db348 100644 --- a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php +++ b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php @@ -246,21 +246,12 @@ public function testOldStyleConstructorOnPhp7(): void $this->markTestSkipped('Test requires PHP 7.x'); } - $errors = [ + $this->analyse([__DIR__ . '/data/php80-constructor.php'], [ [ 'Class OldStyleConstructorOnPhp8 constructor invoked with 0 parameters, 1 required.', 19, ], - ]; - - if (!self::$useStaticReflectionProvider) { - $errors[] = [ - 'Methods with the same name as their class will not be constructors in a future version of PHP; OldStyleConstructorOnPhp8 has a deprecated constructor', - 3, - ]; - } - - $this->analyse([__DIR__ . '/data/php80-constructor.php'], $errors); + ]); } public function testBug4030(): void diff --git a/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php b/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php index b69b361b8d..c69810ab83 100644 --- a/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php @@ -211,7 +211,7 @@ public function testBug3997(): void $this->analyse([__DIR__ . '/data/bug-3997.php'], [ [ 'Return type (string) of method Bug3997\Ipsum::count() should be compatible with return type (int) of method Countable::count()', - 63, + 64, ], ]); } From f8885db4593ce7678ef95fd5b732e13f7db15e5e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 30 Oct 2021 21:41:52 +0200 Subject: [PATCH 0503/1284] Fix --- tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php b/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php index c69810ab83..d92e609b7a 100644 --- a/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php @@ -211,7 +211,7 @@ public function testBug3997(): void $this->analyse([__DIR__ . '/data/bug-3997.php'], [ [ 'Return type (string) of method Bug3997\Ipsum::count() should be compatible with return type (int) of method Countable::count()', - 64, + PHP_VERSION_ID >= 80000 ? 63 : 64, ], ]); } From afef6dc7f42ac00945dbed3f52f8700de9593656 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 30 Oct 2021 21:49:02 +0200 Subject: [PATCH 0504/1284] Fix --- tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php b/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php index d92e609b7a..00a6f9d225 100644 --- a/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php @@ -211,7 +211,7 @@ public function testBug3997(): void $this->analyse([__DIR__ . '/data/bug-3997.php'], [ [ 'Return type (string) of method Bug3997\Ipsum::count() should be compatible with return type (int) of method Countable::count()', - PHP_VERSION_ID >= 80000 ? 63 : 64, + (PHP_VERSION_ID >= 80000 || self::$useStaticReflectionProvider) ? 63 : 64, ], ]); } From fc2b308fd14fc989be72922531e64a6231368ae3 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 30 Oct 2021 21:58:20 +0200 Subject: [PATCH 0505/1284] Fix CS --- tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php b/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php index 00a6f9d225..e28bb75df5 100644 --- a/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php @@ -211,7 +211,7 @@ public function testBug3997(): void $this->analyse([__DIR__ . '/data/bug-3997.php'], [ [ 'Return type (string) of method Bug3997\Ipsum::count() should be compatible with return type (int) of method Countable::count()', - (PHP_VERSION_ID >= 80000 || self::$useStaticReflectionProvider) ? 63 : 64, + PHP_VERSION_ID >= 80000 || self::$useStaticReflectionProvider ? 63 : 64, ], ]); } From 038c7a518f57b1a04536b038d24f110f9d06aa01 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 31 Oct 2021 12:51:15 +0100 Subject: [PATCH 0506/1284] Nicer error message about missing autowired parser service --- src/Command/CommandHelper.php | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php index cfe474e6d1..6eecec6300 100644 --- a/src/Command/CommandHelper.php +++ b/src/Command/CommandHelper.php @@ -235,6 +235,37 @@ public static function begin( } catch (\Nette\DI\InvalidConfigurationException | \Nette\Utils\AssertionException $e) { $errorOutput->writeLineFormatted('Invalid configuration:'); $errorOutput->writeLineFormatted($e->getMessage()); + throw new \PHPStan\Command\InceptionNotSuccessfulException(); + } catch (\Nette\DI\ServiceCreationException $e) { + $matches = Strings::match($e->getMessage(), '#Service of type (?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff\\\\]*[a-zA-Z0-9_\x7f-\xff]): Service of type (?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff\\\\]*[a-zA-Z0-9_\x7f-\xff]) needed by \$(?[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*) in (?[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*)\(\)#'); + if ($matches === null) { + throw $e; + } + + if ($matches['parserServiceType'] !== 'PHPStan\\Parser\\Parser') { + throw $e; + } + + if ($matches['methodName'] !== '__construct') { + throw $e; + } + + $errorOutput->writeLineFormatted('Invalid configuration:'); + $errorOutput->writeLineFormatted(sprintf("Service of type %s is no longer autowired.\n", $matches['parserServiceType'])); + $errorOutput->writeLineFormatted('You need to choose one of the following services'); + $errorOutput->writeLineFormatted(sprintf('and use it in the %s argument of your service %s:', $matches['parameterName'], $matches['serviceType'])); + $errorOutput->writeLineFormatted('* defaultAnalysisParser (if you\'re parsing files from analysed paths)'); + $errorOutput->writeLineFormatted('* currentPhpVersionSimpleDirectParser (in most other situations)'); + + $errorOutput->writeLineFormatted(''); + $errorOutput->writeLineFormatted('After fixing this problem, your configuration will look something like this:'); + $errorOutput->writeLineFormatted(''); + $errorOutput->writeLineFormatted('-'); + $errorOutput->writeLineFormatted(sprintf("\tclass: %s", $matches['serviceType'])); + $errorOutput->writeLineFormatted(sprintf("\targuments:")); + $errorOutput->writeLineFormatted(sprintf("\t\t%s: @defaultAnalysisParser", $matches['parameterName'])); + $errorOutput->writeLineFormatted(''); + throw new \PHPStan\Command\InceptionNotSuccessfulException(); } From c836a423d34faa818127e34efb89ef4b3a7b66b6 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 31 Oct 2021 13:38:23 +0100 Subject: [PATCH 0507/1284] Contextual help about PHP-Parser 4.13.0 --- src/Rules/FoundTypeResult.php | 11 +++++++++- src/Rules/Properties/AccessPropertiesRule.php | 15 +++++++++----- src/Rules/RuleLevelHelper.php | 20 ++++++++++++------- 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/Rules/FoundTypeResult.php b/src/Rules/FoundTypeResult.php index b9bd52cbda..4839e092e6 100644 --- a/src/Rules/FoundTypeResult.php +++ b/src/Rules/FoundTypeResult.php @@ -16,6 +16,8 @@ class FoundTypeResult /** @var RuleError[] */ private array $unknownClassErrors; + private ?string $tip; + /** * @param \PHPStan\Type\Type $type * @param string[] $referencedClasses @@ -24,12 +26,14 @@ class FoundTypeResult public function __construct( Type $type, array $referencedClasses, - array $unknownClassErrors + array $unknownClassErrors, + ?string $tip ) { $this->type = $type; $this->referencedClasses = $referencedClasses; $this->unknownClassErrors = $unknownClassErrors; + $this->tip = $tip; } public function getType(): Type @@ -53,4 +57,9 @@ public function getUnknownClassErrors(): array return $this->unknownClassErrors; } + public function getTip(): ?string + { + return $this->tip; + } + } diff --git a/src/Rules/Properties/AccessPropertiesRule.php b/src/Rules/Properties/AccessPropertiesRule.php index 61c9373577..1838be2af1 100644 --- a/src/Rules/Properties/AccessPropertiesRule.php +++ b/src/Rules/Properties/AccessPropertiesRule.php @@ -139,12 +139,17 @@ static function (Type $type) use ($name): bool { } } + $ruleErrorBuilder = RuleErrorBuilder::message(sprintf( + 'Access to an undefined property %s::$%s.', + $type->describe(VerbosityLevel::typeOnly()), + $name + )); + if ($typeResult->getTip() !== null) { + $ruleErrorBuilder->tip($typeResult->getTip()); + } + return [ - RuleErrorBuilder::message(sprintf( - 'Access to an undefined property %s::$%s.', - $type->describe(VerbosityLevel::typeOnly()), - $name - ))->build(), + $ruleErrorBuilder->build(), ]; } diff --git a/src/Rules/RuleLevelHelper.php b/src/Rules/RuleLevelHelper.php index ab0437b604..5e94e324d0 100644 --- a/src/Rules/RuleLevelHelper.php +++ b/src/Rules/RuleLevelHelper.php @@ -20,6 +20,7 @@ use PHPStan\Type\TypeTraverser; use PHPStan\Type\TypeUtils; use PHPStan\Type\UnionType; +use PHPStan\Type\VerbosityLevel; class RuleLevelHelper { @@ -135,7 +136,7 @@ public function findTypeToCheck( ): FoundTypeResult { if ($this->checkThisOnly && !$this->isThis($var)) { - return new FoundTypeResult(new ErrorType(), [], []); + return new FoundTypeResult(new ErrorType(), [], [], null); } $type = $scope->getType($var); if (!$this->checkNullables && !$type instanceof NullType) { @@ -148,11 +149,11 @@ public function findTypeToCheck( && !$type instanceof TemplateMixedType && $type->isExplicitMixed() ) { - return new FoundTypeResult(new StrictMixedType(), [], []); + return new FoundTypeResult(new StrictMixedType(), [], [], null); } if ($type instanceof MixedType || $type instanceof NeverType) { - return new FoundTypeResult(new ErrorType(), [], []); + return new FoundTypeResult(new ErrorType(), [], [], null); } if ($type instanceof StaticType) { $type = $type->getStaticObjectType(); @@ -178,12 +179,12 @@ public function findTypeToCheck( } if (count($errors) > 0 || $hasClassExistsClass) { - return new FoundTypeResult(new ErrorType(), [], $errors); + return new FoundTypeResult(new ErrorType(), [], $errors, null); } if (!$this->checkUnionTypes) { if ($type instanceof ObjectWithoutClassType) { - return new FoundTypeResult(new ErrorType(), [], []); + return new FoundTypeResult(new ErrorType(), [], [], null); } if ($type instanceof UnionType) { $newTypes = []; @@ -196,12 +197,17 @@ public function findTypeToCheck( } if (count($newTypes) > 0) { - return new FoundTypeResult(TypeCombinator::union(...$newTypes), $directClassNames, []); + return new FoundTypeResult(TypeCombinator::union(...$newTypes), $directClassNames, [], null); } } } - return new FoundTypeResult($type, $directClassNames, []); + $tip = null; + if (strpos($type->describe(VerbosityLevel::typeOnly()), 'PhpParser\\Node\\Arg|PhpParser\\Node\\VariadicPlaceholder') !== false && !$unionTypeCriteriaCallback($type)) { + $tip = 'Use ->getArgs() instead of ->args.'; + } + + return new FoundTypeResult($type, $directClassNames, [], $tip); } } From 5ddca4279e348a3e9e8ce1b1dbac70459b7ab20c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 31 Oct 2021 15:10:34 +0100 Subject: [PATCH 0508/1284] All FunctionReflection implementations have getFileName() --- src/Analyser/MutatingScope.php | 2 ++ src/Reflection/FunctionReflection.php | 2 ++ src/Reflection/Native/NativeFunctionReflection.php | 5 +++++ .../Php/PhpFunctionFromParserNodeReflection.php | 9 +++++++++ src/Reflection/Php/PhpMethodFromParserNodeReflection.php | 2 ++ 5 files changed, 20 insertions(+) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 86d2b293fd..b9bf542f8f 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -2831,6 +2831,7 @@ public function enterClassMethod( new PhpMethodFromParserNodeReflection( $this->getClassReflection(), $classMethod, + $this->getFile(), $templateTypeMap, $this->getRealParameterTypes($classMethod), array_map(static function (Type $type): Type { @@ -2940,6 +2941,7 @@ public function enterFunction( return $this->enterFunctionLike( new PhpFunctionFromParserNodeReflection( $function, + $this->getFile(), $templateTypeMap, $this->getRealParameterTypes($function), array_map(static function (Type $type): Type { diff --git a/src/Reflection/FunctionReflection.php b/src/Reflection/FunctionReflection.php index 6520745366..5d4df99aeb 100644 --- a/src/Reflection/FunctionReflection.php +++ b/src/Reflection/FunctionReflection.php @@ -11,6 +11,8 @@ interface FunctionReflection public function getName(): string; + public function getFileName(): ?string; + /** * @return \PHPStan\Reflection\ParametersAcceptor[] */ diff --git a/src/Reflection/Native/NativeFunctionReflection.php b/src/Reflection/Native/NativeFunctionReflection.php index edda6f7c6a..6525a21173 100644 --- a/src/Reflection/Native/NativeFunctionReflection.php +++ b/src/Reflection/Native/NativeFunctionReflection.php @@ -46,6 +46,11 @@ public function getName(): string return $this->name; } + public function getFileName(): ?string + { + return null; + } + /** * @return \PHPStan\Reflection\ParametersAcceptor[] */ diff --git a/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php b/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php index a41d6eb572..9b3eaf48e2 100644 --- a/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php +++ b/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php @@ -19,6 +19,8 @@ class PhpFunctionFromParserNodeReflection implements \PHPStan\Reflection\Functio private \PhpParser\Node\FunctionLike $functionLike; + private string $fileName; + private \PHPStan\Type\Generic\TemplateTypeMap $templateTypeMap; /** @var \PHPStan\Type\Type[] */ @@ -66,6 +68,7 @@ class PhpFunctionFromParserNodeReflection implements \PHPStan\Reflection\Functio */ public function __construct( FunctionLike $functionLike, + string $fileName, TemplateTypeMap $templateTypeMap, array $realParameterTypes, array $phpDocParameterTypes, @@ -81,6 +84,7 @@ public function __construct( ) { $this->functionLike = $functionLike; + $this->fileName = $fileName; $this->templateTypeMap = $templateTypeMap; $this->realParameterTypes = $realParameterTypes; $this->phpDocParameterTypes = $phpDocParameterTypes; @@ -100,6 +104,11 @@ protected function getFunctionLike(): FunctionLike return $this->functionLike; } + public function getFileName(): string + { + return $this->fileName; + } + public function getName(): string { if ($this->functionLike instanceof ClassMethod) { diff --git a/src/Reflection/Php/PhpMethodFromParserNodeReflection.php b/src/Reflection/Php/PhpMethodFromParserNodeReflection.php index a0610af795..d1fdb0ca4f 100644 --- a/src/Reflection/Php/PhpMethodFromParserNodeReflection.php +++ b/src/Reflection/Php/PhpMethodFromParserNodeReflection.php @@ -40,6 +40,7 @@ class PhpMethodFromParserNodeReflection extends PhpFunctionFromParserNodeReflect public function __construct( ClassReflection $declaringClass, ClassMethod $classMethod, + string $fileName, TemplateTypeMap $templateTypeMap, array $realParameterTypes, array $phpDocParameterTypes, @@ -79,6 +80,7 @@ public function __construct( parent::__construct( $classMethod, + $fileName, $templateTypeMap, $realParameterTypes, $phpDocParameterTypes, From 3738fcd98a98f5a8ef61b40a4c0c5384b7704714 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 31 Oct 2021 17:12:11 +0100 Subject: [PATCH 0509/1284] [BCB] Removed ReflectionWithFilename, use ClassReflection|FunctionReflection instead --- src/Dependency/DependencyResolver.php | 18 +++++++----------- src/Dependency/NodeDependencies.php | 7 ++++--- src/Reflection/ClassReflection.php | 2 +- src/Reflection/Php/PhpFunctionReflection.php | 3 +-- .../ClassBlacklistReflectionProvider.php | 4 ---- src/Reflection/ReflectionWithFilename.php | 11 ----------- 6 files changed, 13 insertions(+), 32 deletions(-) delete mode 100644 src/Reflection/ReflectionWithFilename.php diff --git a/src/Dependency/DependencyResolver.php b/src/Dependency/DependencyResolver.php index 5507705a72..04612b1305 100644 --- a/src/Dependency/DependencyResolver.php +++ b/src/Dependency/DependencyResolver.php @@ -11,10 +11,11 @@ use PHPStan\File\FileHelper; use PHPStan\Node\InClassMethodNode; use PHPStan\Node\InFunctionNode; +use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; use PHPStan\Reflection\ReflectionProvider; -use PHPStan\Reflection\ReflectionWithFilename; use PHPStan\Type\ClosureType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\Type; @@ -208,7 +209,7 @@ private function considerArrayForCallableTest(Scope $scope, Array_ $arrayNode): /** * @param string $className - * @param array $dependenciesReflections + * @param array $dependenciesReflections */ private function addClassToDependencies(string $className, array &$dependenciesReflections): void { @@ -233,19 +234,14 @@ private function addClassToDependencies(string $className, array &$dependenciesR } while ($classReflection !== null); } - private function getFunctionReflection(\PhpParser\Node\Name $nameNode, ?Scope $scope): ReflectionWithFilename + private function getFunctionReflection(\PhpParser\Node\Name $nameNode, ?Scope $scope): FunctionReflection { - $reflection = $this->reflectionProvider->getFunction($nameNode, $scope); - if (!$reflection instanceof ReflectionWithFilename) { - throw new \PHPStan\Broker\FunctionNotFoundException((string) $nameNode); - } - - return $reflection; + return $this->reflectionProvider->getFunction($nameNode, $scope); } /** * @param ParametersAcceptorWithPhpDocs $parametersAcceptor - * @param ReflectionWithFilename[] $dependenciesReflections + * @param array $dependenciesReflections */ private function extractFromParametersAcceptor( ParametersAcceptorWithPhpDocs $parametersAcceptor, @@ -274,7 +270,7 @@ private function extractFromParametersAcceptor( /** * @param Type|null $throwType - * @param ReflectionWithFilename[] $dependenciesReflections + * @param array $dependenciesReflections */ private function extractThrowType( ?Type $throwType, diff --git a/src/Dependency/NodeDependencies.php b/src/Dependency/NodeDependencies.php index b6fcb0ce51..cbc108bea8 100644 --- a/src/Dependency/NodeDependencies.php +++ b/src/Dependency/NodeDependencies.php @@ -3,21 +3,22 @@ namespace PHPStan\Dependency; use PHPStan\File\FileHelper; -use PHPStan\Reflection\ReflectionWithFilename; +use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\FunctionReflection; class NodeDependencies { private FileHelper $fileHelper; - /** @var array */ + /** @var array */ private array $reflections; private ?ExportedNode $exportedNode; /** * @param FileHelper $fileHelper - * @param array $reflections + * @param array $reflections */ public function __construct( FileHelper $fileHelper, diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index 2d6a4e3576..fc4a1bc6f6 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -31,7 +31,7 @@ use ReflectionMethod; /** @api */ -class ClassReflection implements ReflectionWithFilename +class ClassReflection { private \PHPStan\Reflection\ReflectionProvider $reflectionProvider; diff --git a/src/Reflection/Php/PhpFunctionReflection.php b/src/Reflection/Php/PhpFunctionReflection.php index 5690fc2b54..0850ca922a 100644 --- a/src/Reflection/Php/PhpFunctionReflection.php +++ b/src/Reflection/Php/PhpFunctionReflection.php @@ -13,7 +13,6 @@ use PHPStan\Reflection\FunctionVariantWithPhpDocs; use PHPStan\Reflection\ParametersAcceptor; use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; -use PHPStan\Reflection\ReflectionWithFilename; use PHPStan\TrinaryLogic; use PHPStan\Type\Generic\TemplateTypeMap; use PHPStan\Type\MixedType; @@ -21,7 +20,7 @@ use PHPStan\Type\TypehintHelper; use PHPStan\Type\VoidType; -class PhpFunctionReflection implements FunctionReflection, ReflectionWithFilename +class PhpFunctionReflection implements FunctionReflection { private \ReflectionFunction $reflection; diff --git a/src/Reflection/ReflectionProvider/ClassBlacklistReflectionProvider.php b/src/Reflection/ReflectionProvider/ClassBlacklistReflectionProvider.php index 5363194fa8..ce896640a0 100644 --- a/src/Reflection/ReflectionProvider/ClassBlacklistReflectionProvider.php +++ b/src/Reflection/ReflectionProvider/ClassBlacklistReflectionProvider.php @@ -9,7 +9,6 @@ use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\GlobalConstantReflection; use PHPStan\Reflection\ReflectionProvider; -use PHPStan\Reflection\ReflectionWithFilename; class ClassBlacklistReflectionProvider implements ReflectionProvider { @@ -138,9 +137,6 @@ public function hasFunction(\PhpParser\Node\Name $nameNode, ?Scope $scope): bool } $functionReflection = $this->reflectionProvider->getFunction($nameNode, $scope); - if (!$functionReflection instanceof ReflectionWithFilename) { - return true; - } return $functionReflection->getFileName() !== $this->singleReflectionInsteadOfFile; } diff --git a/src/Reflection/ReflectionWithFilename.php b/src/Reflection/ReflectionWithFilename.php deleted file mode 100644 index c626dbb7cf..0000000000 --- a/src/Reflection/ReflectionWithFilename.php +++ /dev/null @@ -1,11 +0,0 @@ - Date: Mon, 1 Nov 2021 08:18:04 +0100 Subject: [PATCH 0510/1284] Update PHPStan deps --- composer.lock | 49 ++++++++++++++++++++++--------------------------- 1 file changed, 22 insertions(+), 27 deletions(-) diff --git a/composer.lock b/composer.lock index bd0a762f06..a6551ad9f5 100644 --- a/composer.lock +++ b/composer.lock @@ -4344,16 +4344,16 @@ }, { "name": "phpstan/phpstan-deprecation-rules", - "version": "dev-master", + "version": "1.0.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-deprecation-rules.git", - "reference": "a6a1ed55749e2e39ad15b1976d523ba0af57f9c3" + "reference": "e5ccafb0dd8d835dd65d8d7a1a0d2b1b75414682" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/a6a1ed55749e2e39ad15b1976d523ba0af57f9c3", - "reference": "a6a1ed55749e2e39ad15b1976d523ba0af57f9c3", + "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/e5ccafb0dd8d835dd65d8d7a1a0d2b1b75414682", + "reference": "e5ccafb0dd8d835dd65d8d7a1a0d2b1b75414682", "shasum": "" }, "require": { @@ -4365,7 +4365,6 @@ "phpstan/phpstan-phpunit": "^1.0", "phpunit/phpunit": "^9.5" }, - "default-branch": true, "type": "phpstan-extension", "extra": { "branch-alias": { @@ -4389,13 +4388,13 @@ "description": "PHPStan rules for detecting usage of deprecated classes, methods, properties, constants and traits.", "support": { "issues": "https://github.com/phpstan/phpstan-deprecation-rules/issues", - "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/master" + "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/1.0.0" }, - "time": "2021-09-12T20:22:56+00:00" + "time": "2021-09-23T11:02:21+00:00" }, { "name": "phpstan/phpstan-nette", - "version": "dev-master", + "version": "1.0.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-nette.git", @@ -4429,7 +4428,6 @@ "phpstan/phpstan-strict-rules": "^1.0", "phpunit/phpunit": "^9.5" }, - "default-branch": true, "type": "phpstan-extension", "extra": { "branch-alias": { @@ -4454,13 +4452,13 @@ "description": "Nette Framework class reflection extension for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-nette/issues", - "source": "https://github.com/phpstan/phpstan-nette/tree/master" + "source": "https://github.com/phpstan/phpstan-nette/tree/1.0.0" }, "time": "2021-09-20T16:12:57+00:00" }, { "name": "phpstan/phpstan-php-parser", - "version": "dev-master", + "version": "1.0.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-php-parser.git", @@ -4482,7 +4480,6 @@ "phpstan/phpstan-strict-rules": "^1.0", "phpunit/phpunit": "^9.5" }, - "default-branch": true, "type": "phpstan-extension", "extra": { "branch-alias": { @@ -4506,22 +4503,22 @@ "description": "PHP-Parser extensions for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-php-parser/issues", - "source": "https://github.com/phpstan/phpstan-php-parser/tree/master" + "source": "https://github.com/phpstan/phpstan-php-parser/tree/1.0.0" }, "time": "2021-09-13T11:29:18+00:00" }, { "name": "phpstan/phpstan-phpunit", - "version": "dev-master", + "version": "1.0.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-phpunit.git", - "reference": "5d501422a47e99f288a1eb07e1de141df0b1eab4" + "reference": "9eb88c9f689003a8a2a5ae9e010338ee94dc39b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/5d501422a47e99f288a1eb07e1de141df0b1eab4", - "reference": "5d501422a47e99f288a1eb07e1de141df0b1eab4", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/9eb88c9f689003a8a2a5ae9e010338ee94dc39b3", + "reference": "9eb88c9f689003a8a2a5ae9e010338ee94dc39b3", "shasum": "" }, "require": { @@ -4537,7 +4534,6 @@ "phpstan/phpstan-strict-rules": "^1.0", "phpunit/phpunit": "^9.5" }, - "default-branch": true, "type": "phpstan-extension", "extra": { "branch-alias": { @@ -4562,22 +4558,22 @@ "description": "PHPUnit extensions and rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-phpunit/issues", - "source": "https://github.com/phpstan/phpstan-phpunit/tree/master" + "source": "https://github.com/phpstan/phpstan-phpunit/tree/1.0.0" }, - "time": "2021-09-23T08:37:07+00:00" + "time": "2021-10-14T08:03:54+00:00" }, { "name": "phpstan/phpstan-strict-rules", - "version": "dev-master", + "version": "1.0.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "b49efedea9da854d6c6d0cd6e7802ec8d76e63b4" + "reference": "7f50eb112f37fda2ef956813d3f1e9b1e69d7940" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/b49efedea9da854d6c6d0cd6e7802ec8d76e63b4", - "reference": "b49efedea9da854d6c6d0cd6e7802ec8d76e63b4", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/7f50eb112f37fda2ef956813d3f1e9b1e69d7940", + "reference": "7f50eb112f37fda2ef956813d3f1e9b1e69d7940", "shasum": "" }, "require": { @@ -4590,7 +4586,6 @@ "phpstan/phpstan-phpunit": "^1.0", "phpunit/phpunit": "^9.5" }, - "default-branch": true, "type": "phpstan-extension", "extra": { "branch-alias": { @@ -4614,9 +4609,9 @@ "description": "Extra strict and opinionated rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", - "source": "https://github.com/phpstan/phpstan-strict-rules/tree/master" + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.0.0" }, - "time": "2021-09-23T10:45:37+00:00" + "time": "2021-10-11T06:57:58+00:00" }, { "name": "phpunit/php-code-coverage", From e5f8e72c41692014f1352c411e81e605b694fabe Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 1 Nov 2021 17:50:43 +0100 Subject: [PATCH 0511/1284] Fixed Expected node type PhpParser\Node\Stmt\Property, NULL occurred --- .../ExportedClassConstantNode.php | 47 +--- .../ExportedClassConstantsNode.php | 120 ++++++++++ .../ExportedNode/ExportedClassNode.php | 31 ++- .../ExportedNode/ExportedInterfaceNode.php | 30 ++- ...rtyNode.php => ExportedPropertiesNode.php} | 31 ++- src/Dependency/ExportedNodeResolver.php | 215 ++++++++++-------- 6 files changed, 318 insertions(+), 156 deletions(-) create mode 100644 src/Dependency/ExportedNode/ExportedClassConstantsNode.php rename src/Dependency/ExportedNode/{ExportedPropertyNode.php => ExportedPropertiesNode.php} (81%) diff --git a/src/Dependency/ExportedNode/ExportedClassConstantNode.php b/src/Dependency/ExportedNode/ExportedClassConstantNode.php index 312383bbc2..a1781c1af7 100644 --- a/src/Dependency/ExportedNode/ExportedClassConstantNode.php +++ b/src/Dependency/ExportedNode/ExportedClassConstantNode.php @@ -12,22 +12,10 @@ class ExportedClassConstantNode implements ExportedNode, JsonSerializable private string $value; - private bool $public; - - private bool $private; - - private bool $final; - - private ?ExportedPhpDocNode $phpDoc; - - public function __construct(string $name, string $value, bool $public, bool $private, bool $final, ?ExportedPhpDocNode $phpDoc) + public function __construct(string $name, string $value) { $this->name = $name; $this->value = $value; - $this->public = $public; - $this->private = $private; - $this->final = $final; - $this->phpDoc = $phpDoc; } public function equals(ExportedNode $node): bool @@ -36,23 +24,8 @@ public function equals(ExportedNode $node): bool return false; } - if ($this->phpDoc === null) { - if ($node->phpDoc !== null) { - return false; - } - } elseif ($node->phpDoc !== null) { - if (!$this->phpDoc->equals($node->phpDoc)) { - return false; - } - } else { - return false; - } - return $this->name === $node->name - && $this->value === $node->value - && $this->public === $node->public - && $this->private === $node->private - && $this->final === $node->final; + && $this->value === $node->value; } /** @@ -63,11 +36,7 @@ public static function __set_state(array $properties): ExportedNode { return new self( $properties['name'], - $properties['value'], - $properties['public'], - $properties['private'], - $properties['final'], - $properties['phpDoc'] + $properties['value'] ); } @@ -79,11 +48,7 @@ public static function decode(array $data): ExportedNode { return new self( $data['name'], - $data['value'], - $data['public'], - $data['private'], - $data['final'], - $data['phpDoc'] !== null ? ExportedPhpDocNode::decode($data['phpDoc']['data']) : null + $data['value'] ); } @@ -98,10 +63,6 @@ public function jsonSerialize() 'data' => [ 'name' => $this->name, 'value' => $this->value, - 'public' => $this->public, - 'private' => $this->private, - 'final' => $this->final, - 'phpDoc' => $this->phpDoc, ], ]; } diff --git a/src/Dependency/ExportedNode/ExportedClassConstantsNode.php b/src/Dependency/ExportedNode/ExportedClassConstantsNode.php new file mode 100644 index 0000000000..9c8f640933 --- /dev/null +++ b/src/Dependency/ExportedNode/ExportedClassConstantsNode.php @@ -0,0 +1,120 @@ +constants = $constants; + $this->public = $public; + $this->private = $private; + $this->final = $final; + $this->phpDoc = $phpDoc; + } + + public function equals(ExportedNode $node): bool + { + if (!$node instanceof self) { + return false; + } + + if ($this->phpDoc === null) { + if ($node->phpDoc !== null) { + return false; + } + } elseif ($node->phpDoc !== null) { + if (!$this->phpDoc->equals($node->phpDoc)) { + return false; + } + } else { + return false; + } + + if (count($this->constants) !== count($node->constants)) { + return false; + } + + foreach ($this->constants as $i => $constant) { + if (!$constant->equals($node->constants[$i])) { + return false; + } + } + + return $this->public === $node->public + && $this->private === $node->private + && $this->final === $node->final; + } + + /** + * @param mixed[] $properties + * @return self + */ + public static function __set_state(array $properties): ExportedNode + { + return new self( + $properties['constants'], + $properties['public'], + $properties['private'], + $properties['final'], + $properties['phpDoc'] + ); + } + + /** + * @param mixed[] $data + * @return self + */ + public static function decode(array $data): ExportedNode + { + return new self( + array_map(static function (array $constantData): ExportedClassConstantNode { + if ($constantData['type'] !== ExportedClassConstantNode::class) { + throw new \PHPStan\ShouldNotHappenException(); + } + return ExportedClassConstantNode::decode($constantData['data']); + }, $data['constants']), + $data['public'], + $data['private'], + $data['final'], + $data['phpDoc'] !== null ? ExportedPhpDocNode::decode($data['phpDoc']['data']) : null + ); + } + + /** + * @return mixed + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return [ + 'type' => self::class, + 'data' => [ + 'constants' => $this->constants, + 'public' => $this->public, + 'private' => $this->private, + 'final' => $this->final, + 'phpDoc' => $this->phpDoc, + ], + ]; + } + +} diff --git a/src/Dependency/ExportedNode/ExportedClassNode.php b/src/Dependency/ExportedNode/ExportedClassNode.php index 6e8c7ee091..fe0428eccc 100644 --- a/src/Dependency/ExportedNode/ExportedClassNode.php +++ b/src/Dependency/ExportedNode/ExportedClassNode.php @@ -27,6 +27,9 @@ class ExportedClassNode implements ExportedNode, JsonSerializable /** @var ExportedTraitUseAdaptation[] */ private array $traitUseAdaptations; + /** @var ExportedNode[] */ + private array $statements; + /** * @param string $name * @param ExportedPhpDocNode|null $phpDoc @@ -36,6 +39,7 @@ class ExportedClassNode implements ExportedNode, JsonSerializable * @param string[] $implements * @param string[] $usedTraits * @param ExportedTraitUseAdaptation[] $traitUseAdaptations + * @param ExportedNode[] $statements */ public function __construct( string $name, @@ -45,7 +49,8 @@ public function __construct( ?string $extends, array $implements, array $usedTraits, - array $traitUseAdaptations + array $traitUseAdaptations, + array $statements ) { $this->name = $name; @@ -56,6 +61,7 @@ public function __construct( $this->implements = $implements; $this->usedTraits = $usedTraits; $this->traitUseAdaptations = $traitUseAdaptations; + $this->statements = $statements; } public function equals(ExportedNode $node): bool @@ -87,6 +93,18 @@ public function equals(ExportedNode $node): bool } } + if (count($this->statements) !== count($node->statements)) { + return false; + } + + foreach ($this->statements as $i => $statement) { + if ($statement->equals($node->statements[$i])) { + continue; + } + + return false; + } + return $this->name === $node->name && $this->abstract === $node->abstract && $this->final === $node->final @@ -109,7 +127,8 @@ public static function __set_state(array $properties): ExportedNode $properties['extends'], $properties['implements'], $properties['usedTraits'], - $properties['traitUseAdaptations'] + $properties['traitUseAdaptations'], + $properties['statements'] ); } @@ -130,6 +149,7 @@ public function jsonSerialize() 'implements' => $this->implements, 'usedTraits' => $this->usedTraits, 'traitUseAdaptations' => $this->traitUseAdaptations, + 'statements' => $this->statements, ], ]; } @@ -153,7 +173,12 @@ public static function decode(array $data): ExportedNode throw new \PHPStan\ShouldNotHappenException(); } return ExportedTraitUseAdaptation::decode($traitUseAdaptationData['data']); - }, $data['traitUseAdaptations']) + }, $data['traitUseAdaptations']), + array_map(static function (array $node): ExportedNode { + $nodeType = $node['type']; + + return $nodeType::decode($node['data']); + }, $data['statements']), ); } diff --git a/src/Dependency/ExportedNode/ExportedInterfaceNode.php b/src/Dependency/ExportedNode/ExportedInterfaceNode.php index 50311538a2..9aa07a84d9 100644 --- a/src/Dependency/ExportedNode/ExportedInterfaceNode.php +++ b/src/Dependency/ExportedNode/ExportedInterfaceNode.php @@ -15,16 +15,21 @@ class ExportedInterfaceNode implements ExportedNode, JsonSerializable /** @var string[] */ private array $extends; + /** @var ExportedNode[] */ + private array $statements; + /** * @param string $name * @param ExportedPhpDocNode|null $phpDoc * @param string[] $extends + * @param ExportedNode[] $statements */ - public function __construct(string $name, ?ExportedPhpDocNode $phpDoc, array $extends) + public function __construct(string $name, ?ExportedPhpDocNode $phpDoc, array $extends, array $statements) { $this->name = $name; $this->phpDoc = $phpDoc; $this->extends = $extends; + $this->statements = $statements; } public function equals(ExportedNode $node): bool @@ -45,6 +50,18 @@ public function equals(ExportedNode $node): bool return false; } + if (count($this->statements) !== count($node->statements)) { + return false; + } + + foreach ($this->statements as $i => $statement) { + if ($statement->equals($node->statements[$i])) { + continue; + } + + return false; + } + return $this->name === $node->name && $this->extends === $node->extends; } @@ -58,7 +75,8 @@ public static function __set_state(array $properties): ExportedNode return new self( $properties['name'], $properties['phpDoc'], - $properties['extends'] + $properties['extends'], + $properties['statements'] ); } @@ -74,6 +92,7 @@ public function jsonSerialize() 'name' => $this->name, 'phpDoc' => $this->phpDoc, 'extends' => $this->extends, + 'statements' => $this->statements, ], ]; } @@ -87,7 +106,12 @@ public static function decode(array $data): ExportedNode return new self( $data['name'], $data['phpDoc'] !== null ? ExportedPhpDocNode::decode($data['phpDoc']['data']) : null, - $data['extends'] + $data['extends'], + array_map(static function (array $node): ExportedNode { + $nodeType = $node['type']; + + return $nodeType::decode($node['data']); + }, $data['statements']), ); } diff --git a/src/Dependency/ExportedNode/ExportedPropertyNode.php b/src/Dependency/ExportedNode/ExportedPropertiesNode.php similarity index 81% rename from src/Dependency/ExportedNode/ExportedPropertyNode.php rename to src/Dependency/ExportedNode/ExportedPropertiesNode.php index 8dbde24e6c..60f4d9f3e7 100644 --- a/src/Dependency/ExportedNode/ExportedPropertyNode.php +++ b/src/Dependency/ExportedNode/ExportedPropertiesNode.php @@ -5,10 +5,11 @@ use JsonSerializable; use PHPStan\Dependency\ExportedNode; -class ExportedPropertyNode implements JsonSerializable, ExportedNode +class ExportedPropertiesNode implements JsonSerializable, ExportedNode { - private string $name; + /** @var string[] */ + private array $names; private ?ExportedPhpDocNode $phpDoc; @@ -22,8 +23,11 @@ class ExportedPropertyNode implements JsonSerializable, ExportedNode private bool $readonly; + /** + * @param string[] $names + */ public function __construct( - string $name, + array $names, ?ExportedPhpDocNode $phpDoc, ?string $type, bool $public, @@ -32,7 +36,7 @@ public function __construct( bool $readonly ) { - $this->name = $name; + $this->names = $names; $this->phpDoc = $phpDoc; $this->type = $type; $this->public = $public; @@ -59,8 +63,17 @@ public function equals(ExportedNode $node): bool return false; } - return $this->name === $node->name - && $this->type === $node->type + if (count($this->names) !== count($node->names)) { + return false; + } + + foreach ($this->names as $i => $name) { + if ($name !== $node->names[$i]) { + return false; + } + } + + return $this->type === $node->type && $this->public === $node->public && $this->private === $node->private && $this->static === $node->static @@ -74,7 +87,7 @@ public function equals(ExportedNode $node): bool public static function __set_state(array $properties): ExportedNode { return new self( - $properties['name'], + $properties['names'], $properties['phpDoc'], $properties['type'], $properties['public'], @@ -91,7 +104,7 @@ public static function __set_state(array $properties): ExportedNode public static function decode(array $data): ExportedNode { return new self( - $data['name'], + $data['names'], $data['phpDoc'] !== null ? ExportedPhpDocNode::decode($data['phpDoc']['data']) : null, $data['type'], $data['public'], @@ -110,7 +123,7 @@ public function jsonSerialize() return [ 'type' => self::class, 'data' => [ - 'name' => $this->name, + 'names' => $this->names, 'phpDoc' => $this->phpDoc, 'type' => $this->type, 'public' => $this->public, diff --git a/src/Dependency/ExportedNodeResolver.php b/src/Dependency/ExportedNodeResolver.php index 0353437cac..3ebbae3995 100644 --- a/src/Dependency/ExportedNodeResolver.php +++ b/src/Dependency/ExportedNodeResolver.php @@ -7,16 +7,16 @@ use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Function_; -use PhpParser\Node\Stmt\Property; use PhpParser\PrettyPrinter\Standard; use PHPStan\Dependency\ExportedNode\ExportedClassConstantNode; +use PHPStan\Dependency\ExportedNode\ExportedClassConstantsNode; use PHPStan\Dependency\ExportedNode\ExportedClassNode; use PHPStan\Dependency\ExportedNode\ExportedFunctionNode; use PHPStan\Dependency\ExportedNode\ExportedInterfaceNode; use PHPStan\Dependency\ExportedNode\ExportedMethodNode; use PHPStan\Dependency\ExportedNode\ExportedParameterNode; use PHPStan\Dependency\ExportedNode\ExportedPhpDocNode; -use PHPStan\Dependency\ExportedNode\ExportedPropertyNode; +use PHPStan\Dependency\ExportedNode\ExportedPropertiesNode; use PHPStan\Dependency\ExportedNode\ExportedTraitNode; use PHPStan\Dependency\ExportedNode\ExportedTraitUseAdaptation; use PHPStan\Type\FileTypeMapper; @@ -95,7 +95,8 @@ public function resolve(string $fileName, \PhpParser\Node $node): ?ExportedNode } throw new \PHPStan\ShouldNotHappenException(); - }, $adaptations) + }, $adaptations), + $this->exportClassStatements($node->stmts, $fileName, $node, $className) ); } @@ -115,7 +116,8 @@ public function resolve(string $fileName, \PhpParser\Node $node): ?ExportedNode null, $docComment !== null ? $docComment->getText() : null ), - $extendsNames + $extendsNames, + $this->exportClassStatements($node->stmts, $fileName, $node, $interfaceName) ); } @@ -123,100 +125,6 @@ public function resolve(string $fileName, \PhpParser\Node $node): ?ExportedNode return new ExportedTraitNode($node->namespacedName->toString()); } - if ($node instanceof ClassMethod) { - if ($node->isAbstract() || $node->isFinal() || !$node->isPrivate()) { - $methodName = $node->name->toString(); - $docComment = $node->getDocComment(); - $parentNode = $node->getAttribute('parent'); - $continue = ($parentNode instanceof Class_ || $parentNode instanceof Node\Stmt\Interface_) && isset($parentNode->namespacedName); - if (!$continue) { - return null; - } - - return new ExportedMethodNode( - $methodName, - $this->exportPhpDocNode( - $fileName, - $parentNode->namespacedName->toString(), - $methodName, - $docComment !== null ? $docComment->getText() : null - ), - $node->byRef, - $node->isPublic(), - $node->isPrivate(), - $node->isAbstract(), - $node->isFinal(), - $node->isStatic(), - $this->printType($node->returnType), - $this->exportParameterNodes($node->params) - ); - } - } - - if ($node instanceof Node\Stmt\PropertyProperty) { - $parentNode = $node->getAttribute('parent'); - if (!$parentNode instanceof Property) { - throw new \PHPStan\ShouldNotHappenException(sprintf('Expected node type %s, %s occurred.', Property::class, is_object($parentNode) ? get_class($parentNode) : gettype($parentNode))); - } - if ($parentNode->isPrivate()) { - return null; - } - - $classNode = $parentNode->getAttribute('parent'); - if (!$classNode instanceof Class_ || !isset($classNode->namespacedName)) { - return null; - } - - $docComment = $parentNode->getDocComment(); - - return new ExportedPropertyNode( - $node->name->toString(), - $this->exportPhpDocNode( - $fileName, - $classNode->namespacedName->toString(), - null, - $docComment !== null ? $docComment->getText() : null - ), - $this->printType($parentNode->type), - $parentNode->isPublic(), - $parentNode->isPrivate(), - $parentNode->isStatic(), - $parentNode->isReadonly() - ); - } - - if ($node instanceof Node\Const_) { - $parentNode = $node->getAttribute('parent'); - if (!$parentNode instanceof Node\Stmt\ClassConst) { - return null; - } - - if ($parentNode->isPrivate()) { - return null; - } - - $classNode = $parentNode->getAttribute('parent'); - if (!$classNode instanceof Class_ || !isset($classNode->namespacedName)) { - return null; - } - - $docComment = $parentNode->getDocComment(); - - return new ExportedClassConstantNode( - $node->name->toString(), - $this->printer->prettyPrintExpr($node->value), - $parentNode->isPublic(), - $parentNode->isPrivate(), - $parentNode->isFinal(), - $this->exportPhpDocNode( - $fileName, - $classNode->namespacedName->toString(), - null, - $docComment !== null ? $docComment->getText() : null - ) - ); - } - if ($node instanceof Function_) { $functionName = $node->name->name; if (isset($node->namespacedName)) { @@ -349,4 +257,115 @@ private function exportPhpDocNode( return new ExportedPhpDocNode($text, $nameScope->getNamespace(), $nameScope->getUses()); } + /** + * @param Node\Stmt[] $statements + * @return ExportedNode[] + */ + private function exportClassStatements(array $statements, string $fileName, Node\Stmt\ClassLike $classNode, string $namespacedName): array + { + $exportedNodes = []; + foreach ($statements as $statement) { + $exportedNode = $this->exportClassStatement($statement, $fileName, $classNode, $namespacedName); + if ($exportedNode === null) { + continue; + } + + $exportedNodes[] = $exportedNode; + } + + return $exportedNodes; + } + + private function exportClassStatement(Node\Stmt $node, string $fileName, Node\Stmt\ClassLike $classNode, string $namespacedName): ?ExportedNode + { + if ($node instanceof ClassMethod) { + if ($node->isAbstract() || $node->isFinal() || !$node->isPrivate()) { + $methodName = $node->name->toString(); + $docComment = $node->getDocComment(); + + return new ExportedMethodNode( + $methodName, + $this->exportPhpDocNode( + $fileName, + $namespacedName, + $methodName, + $docComment !== null ? $docComment->getText() : null + ), + $node->byRef, + $node->isPublic(), + $node->isPrivate(), + $node->isAbstract(), + $node->isFinal(), + $node->isStatic(), + $this->printType($node->returnType), + $this->exportParameterNodes($node->params) + ); + } + } + + if ($node instanceof Node\Stmt\Property) { + if ($node->isPrivate()) { + return null; + } + + if (!$classNode instanceof Class_) { + return null; + } + + $docComment = $node->getDocComment(); + + return new ExportedPropertiesNode( + array_map(static function (Node\Stmt\PropertyProperty $prop): string { + return $prop->name->toString(); + }, $node->props), + $this->exportPhpDocNode( + $fileName, + $namespacedName, + null, + $docComment !== null ? $docComment->getText() : null + ), + $this->printType($node->type), + $node->isPublic(), + $node->isPrivate(), + $node->isStatic(), + $node->isReadonly() + ); + } + + if ($node instanceof Node\Stmt\ClassConst) { + if ($node->isPrivate()) { + return null; + } + + if (!$classNode instanceof Class_) { + return null; + } + + $docComment = $node->getDocComment(); + + $constants = []; + foreach ($node->consts as $const) { + $constants[] = new ExportedClassConstantNode( + $const->name->toString(), + $this->printer->prettyPrintExpr($const->value) + ); + } + + return new ExportedClassConstantsNode( + $constants, + $node->isPublic(), + $node->isPrivate(), + $node->isFinal(), + $this->exportPhpDocNode( + $fileName, + $namespacedName, + null, + $docComment !== null ? $docComment->getText() : null + ) + ); + } + + return null; + } + } From f28c7e9d06f65fff2ebcb954a581989078929a90 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 2 Nov 2021 10:32:47 +0100 Subject: [PATCH 0512/1284] DerivativeContainerFactory - pass all arguments to the derived container --- conf/config.neon | 3 +++ .../DerivativeContainerFactory.php | 19 +++++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index 1f727e5c3a..3ad1f94168 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -503,6 +503,9 @@ services: analysedPathsFromConfig: %analysedPathsFromConfig% usedLevel: %usedLevel% generateBaselineFile: %generateBaselineFile% + cliAutoloadFile: %cliAutoloadFile% + singleReflectionFile: %singleReflectionFile% + singleReflectionInsteadOfFile: %singleReflectionInsteadOfFile% - class: PHPStan\DependencyInjection\Reflection\ClassReflectionExtensionRegistryProvider diff --git a/src/DependencyInjection/DerivativeContainerFactory.php b/src/DependencyInjection/DerivativeContainerFactory.php index ada4f5f921..8023db7eef 100644 --- a/src/DependencyInjection/DerivativeContainerFactory.php +++ b/src/DependencyInjection/DerivativeContainerFactory.php @@ -25,6 +25,12 @@ class DerivativeContainerFactory private ?string $generateBaselineFile; + private ?string $cliAutoloadFile; + + private ?string $singleReflectionFile; + + private ?string $singleReflectionInsteadOfFile; + /** * @param string $currentWorkingDirectory * @param string $tempDirectory @@ -42,7 +48,10 @@ public function __construct( array $composerAutoloaderProjectPaths, array $analysedPathsFromConfig, string $usedLevel, - ?string $generateBaselineFile + ?string $generateBaselineFile, + ?string $cliAutoloadFile, + ?string $singleReflectionFile, + ?string $singleReflectionInsteadOfFile ) { $this->currentWorkingDirectory = $currentWorkingDirectory; @@ -53,6 +62,9 @@ public function __construct( $this->analysedPathsFromConfig = $analysedPathsFromConfig; $this->usedLevel = $usedLevel; $this->generateBaselineFile = $generateBaselineFile; + $this->cliAutoloadFile = $cliAutoloadFile; + $this->singleReflectionFile = $singleReflectionFile; + $this->singleReflectionInsteadOfFile = $singleReflectionInsteadOfFile; } /** @@ -72,7 +84,10 @@ public function create(array $additionalConfigFiles): Container $this->composerAutoloaderProjectPaths, $this->analysedPathsFromConfig, $this->usedLevel, - $this->generateBaselineFile + $this->generateBaselineFile, + $this->cliAutoloadFile, + $this->singleReflectionFile, + $this->singleReflectionInsteadOfFile ); } From ccff300d51ea1ee98c1c2865a18e7ca83ff34183 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 2 Nov 2021 10:58:06 +0100 Subject: [PATCH 0513/1284] Fix --- src/Dependency/ExportedNode/ExportedClassNode.php | 2 +- src/Dependency/ExportedNode/ExportedInterfaceNode.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Dependency/ExportedNode/ExportedClassNode.php b/src/Dependency/ExportedNode/ExportedClassNode.php index fe0428eccc..af41974342 100644 --- a/src/Dependency/ExportedNode/ExportedClassNode.php +++ b/src/Dependency/ExportedNode/ExportedClassNode.php @@ -178,7 +178,7 @@ public static function decode(array $data): ExportedNode $nodeType = $node['type']; return $nodeType::decode($node['data']); - }, $data['statements']), + }, $data['statements']) ); } diff --git a/src/Dependency/ExportedNode/ExportedInterfaceNode.php b/src/Dependency/ExportedNode/ExportedInterfaceNode.php index 9aa07a84d9..60bd360b8f 100644 --- a/src/Dependency/ExportedNode/ExportedInterfaceNode.php +++ b/src/Dependency/ExportedNode/ExportedInterfaceNode.php @@ -111,7 +111,7 @@ public static function decode(array $data): ExportedNode $nodeType = $node['type']; return $nodeType::decode($node['data']); - }, $data['statements']), + }, $data['statements']) ); } From d4efedbd50af2317b18a2628cbadac40d66eed6d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 2 Nov 2021 11:04:48 +0100 Subject: [PATCH 0514/1284] PhpStanNamespaceIn3rdPartyPackageRule - fix segfault when there's no composer.json --- src/Rules/Api/PhpStanNamespaceIn3rdPartyPackageRule.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Rules/Api/PhpStanNamespaceIn3rdPartyPackageRule.php b/src/Rules/Api/PhpStanNamespaceIn3rdPartyPackageRule.php index 81f4ac4c54..6130501b49 100644 --- a/src/Rules/Api/PhpStanNamespaceIn3rdPartyPackageRule.php +++ b/src/Rules/Api/PhpStanNamespaceIn3rdPartyPackageRule.php @@ -65,7 +65,12 @@ private function findComposerJsonContents(string $fromDirectory): ?array $composerJsonPath = $fromDirectory . '/composer.json'; if (!is_file($composerJsonPath)) { - return $this->findComposerJsonContents(dirname($fromDirectory)); + $dirName = dirname($fromDirectory); + if ($dirName !== $fromDirectory) { + return $this->findComposerJsonContents($dirName); + } + + return null; } try { From 95754d1c52c99a9cfebfb595d58413955bbe61d4 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 3 Nov 2021 13:29:38 +0100 Subject: [PATCH 0515/1284] Analyze Composer namespace only with static reflection, some classes in PHAR are unprefixed --- conf/config.neon | 1 + 1 file changed, 1 insertion(+) diff --git a/conf/config.neon b/conf/config.neon index 3ad1f94168..5a464ff3f5 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -94,6 +94,7 @@ parameters: - '#^PHPStan\\#' - '#^Hoa\\#' - '#^Symfony\\Polyfill\\Php80\\#' + - '#^Composer\\#' dynamicConstantNames: - ICONV_IMPL - LIBXML_VERSION From c59330c539a5cabba7a1c06a7f468d0c3a7dcfa7 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 3 Nov 2021 15:02:01 +0100 Subject: [PATCH 0516/1284] ArrayType - use BenevolentUnionType even if it contains StrictMixedType --- src/Type/ArrayType.php | 3 +++ .../CallToFunctionParametersRuleTest.php | 11 +++++++++++ tests/PHPStan/Rules/Functions/data/bug-5872.php | 15 +++++++++++++++ 3 files changed, 29 insertions(+) create mode 100644 tests/PHPStan/Rules/Functions/data/bug-5872.php diff --git a/src/Type/ArrayType.php b/src/Type/ArrayType.php index 76e5392f50..b8487cde25 100644 --- a/src/Type/ArrayType.php +++ b/src/Type/ArrayType.php @@ -176,6 +176,9 @@ public function getIterableKeyType(): Type if ($keyType instanceof MixedType && !$keyType instanceof TemplateMixedType) { return new BenevolentUnionType([new IntegerType(), new StringType()]); } + if ($keyType instanceof StrictMixedType) { + return new BenevolentUnionType([new IntegerType(), new StringType()]); + } return $keyType; } diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 6d0dd8a99d..2103a67369 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -876,4 +876,15 @@ public function testBug5661(): void $this->analyse([__DIR__ . '/data/bug-5661.php'], []); } + public function testBug5872(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/bug-5872.php'], [ + [ + 'Parameter #2 $array of function array_map expects array, mixed given.', + 12, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/bug-5872.php b/tests/PHPStan/Rules/Functions/data/bug-5872.php new file mode 100644 index 0000000000..ff16caea99 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-5872.php @@ -0,0 +1,15 @@ + Date: Wed, 3 Nov 2021 16:03:43 +0100 Subject: [PATCH 0517/1284] Unified the name of the second array_map parameter --- resources/functionMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 25e8e565b4..37435136c3 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -286,7 +286,7 @@ 'array_key_first' => ['int|string|null', 'array' => 'array'], 'array_key_last' => ['int|string|null', 'array' => 'array'], 'array_keys' => ['array', 'input'=>'array', 'search_value='=>'mixed', 'strict='=>'bool'], -'array_map' => ['array', 'callback'=>'?callable', 'input1'=>'array', '...args='=>'array'], +'array_map' => ['array', 'callback'=>'?callable', 'array'=>'array', '...args='=>'array'], 'array_merge' => ['array', 'arr1'=>'array', '...args='=>'array'], 'array_merge_recursive' => ['array', 'arr1'=>'array', '...args='=>'array'], 'array_multisort' => ['bool', '&rw_array1'=>'array', 'array1_sort_order='=>'array|int', 'array1_sort_flags='=>'array|int', '...args='=>'array|int'], From 57405afa5e7f42c21ff5994681a322b9ff09bd5b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 3 Nov 2021 16:07:48 +0100 Subject: [PATCH 0518/1284] Regression tests Closes https://github.com/phpstan/phpstan/issues/5834 Closes https://github.com/phpstan/phpstan/issues/5881 Closes https://github.com/phpstan/phpstan/issues/5872 Closes https://github.com/phpstan/phpstan/issues/5861 --- .../CallToFunctionParametersRuleTest.php | 18 ++++++++++++ .../PHPStan/Rules/Functions/data/bug-5834.php | 28 +++++++++++++++++++ .../PHPStan/Rules/Functions/data/bug-5861.php | 10 +++++++ .../PHPStan/Rules/Functions/data/bug-5872.php | 26 +++++++++++++++++ .../PHPStan/Rules/Functions/data/bug-5881.php | 11 ++++++++ 5 files changed, 93 insertions(+) create mode 100644 tests/PHPStan/Rules/Functions/data/bug-5834.php create mode 100644 tests/PHPStan/Rules/Functions/data/bug-5861.php create mode 100644 tests/PHPStan/Rules/Functions/data/bug-5881.php diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 2103a67369..4ac31e0499 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -887,4 +887,22 @@ public function testBug5872(): void ]); } + public function testBug5834(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/bug-5834.php'], []); + } + + public function testBug5881(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/bug-5881.php'], []); + } + + public function testBug5861(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/bug-5861.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/bug-5834.php b/tests/PHPStan/Rules/Functions/data/bug-5834.php new file mode 100644 index 0000000000..5cb124c2bf --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-5834.php @@ -0,0 +1,28 @@ +bar; + } +} + +class Foo +{ + public function getFoo(Bar $bar): void + { + $array = (array) $bar->getBar(); + $statusCode = array_key_exists('key', $array) ? (string) $array['key'] : null; + } +} diff --git a/tests/PHPStan/Rules/Functions/data/bug-5861.php b/tests/PHPStan/Rules/Functions/data/bug-5861.php new file mode 100644 index 0000000000..9f75e053b9 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-5861.php @@ -0,0 +1,10 @@ + + */ +class FooIterator implements \IteratorAggregate +{ + /** + * @return \Generator + */ + public function getIterator(): \Generator + { + yield 1; + yield 2; + yield 3; + } +} + +function (): void { + \array_map( + static function (int $i): string { + return (string) $i; + }, + \iterator_to_array(new FooIterator()) + ); +}; diff --git a/tests/PHPStan/Rules/Functions/data/bug-5881.php b/tests/PHPStan/Rules/Functions/data/bug-5881.php new file mode 100644 index 0000000000..d586c9e246 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-5881.php @@ -0,0 +1,11 @@ + Date: Fri, 15 Oct 2021 17:57:38 +0200 Subject: [PATCH 0519/1284] More precise connection_aborted() signature --- resources/functionMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 37435136c3..194e43f2e3 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -1060,7 +1060,7 @@ 'componere\cast' => ['Type', 'arg1'=>'', 'object'=>''], 'componere\cast_by_ref' => ['Type', 'arg1'=>'', 'object'=>''], 'confirm_pdo_ibm_compiled' => [''], -'connection_aborted' => ['int'], +'connection_aborted' => ['0|1'], 'connection_status' => ['int'], 'connection_timeout' => ['int'], 'constant' => ['mixed', 'const_name'=>'string'], From a3b209cf407dc83701aa4f3cd9c9364d186c3440 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 15 Oct 2021 18:14:15 +0200 Subject: [PATCH 0520/1284] More precise str_split signature --- resources/functionMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 194e43f2e3..dddd7b5d0d 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -11696,7 +11696,7 @@ 'str_replace' => ['string|array', 'search'=>'string|array', 'replace'=>'string|array', 'subject'=>'string|array', '&w_replace_count='=>'int'], 'str_rot13' => ['string', 'str'=>'string'], 'str_shuffle' => ['string', 'str'=>'string'], -'str_split' => ['non-empty-array|false', 'str'=>'string', 'split_length='=>'int'], +'str_split' => ['non-empty-array|false', 'str'=>'string', 'split_length='=>'positive-int'], 'str_word_count' => ['array|int|false', 'string'=>'string', 'format='=>'int', 'charlist='=>'string'], 'strcasecmp' => ['int', 'str1'=>'string', 'str2'=>'string'], 'strchr' => ['string|false', 'haystack'=>'string', 'needle'=>'string', 'before_needle='=>'bool'], From 9beb27ea1c0b695a32e64ff94c9c65606f190b0c Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 3 Nov 2021 16:22:10 +0100 Subject: [PATCH 0521/1284] DateFunctionReturnTypeExtension: support more precise single-char date-formats --- .../Php/DateFunctionReturnTypeExtension.php | 54 +++++++++++++++++++ .../Analyser/NodeScopeResolverTest.php | 1 + tests/PHPStan/Analyser/data/date.php | 34 ++++++++++++ 3 files changed, 89 insertions(+) create mode 100644 tests/PHPStan/Analyser/data/date.php diff --git a/src/Type/Php/DateFunctionReturnTypeExtension.php b/src/Type/Php/DateFunctionReturnTypeExtension.php index 5821cd1cea..874c914ca0 100644 --- a/src/Type/Php/DateFunctionReturnTypeExtension.php +++ b/src/Type/Php/DateFunctionReturnTypeExtension.php @@ -6,11 +6,13 @@ use PHPStan\Analyser\Scope; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\AccessoryNumericStringType; +use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\IntersectionType; use PHPStan\Type\StringType; use PHPStan\Type\Type; use PHPStan\Type\TypeUtils; +use PHPStan\Type\UnionType; class DateFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension { @@ -31,10 +33,45 @@ public function getTypeFromFunctionCall( } $argType = $scope->getType($functionCall->getArgs()[0]->value); $constantStrings = TypeUtils::getConstantStrings($argType); + if (count($constantStrings) === 0) { return new StringType(); } + if (count($constantStrings) === 1) { + $constantString = $constantStrings[0]->getValue(); + + // see see https://www.php.net/manual/en/datetime.format.php + switch ($constantString) { + case 'd': + return $this->buildNumericRangeType(1, 31, true); + case 'j': + return $this->buildNumericRangeType(1, 31, false); + case 'N': + return $this->buildNumericRangeType(1, 7, false); + case 'w': + return $this->buildNumericRangeType(0, 6, false); + case 'm': + return $this->buildNumericRangeType(1, 12, true); + case 'n': + return $this->buildNumericRangeType(1, 12, false); + case 't': + return $this->buildNumericRangeType(28, 31, false); + case 'L': + return $this->buildNumericRangeType(0, 1, false); + case 'g': + return $this->buildNumericRangeType(1, 12, false); + case 'G': + return $this->buildNumericRangeType(0, 23, false); + case 'h': + return $this->buildNumericRangeType(1, 12, true); + case 'H': + return $this->buildNumericRangeType(0, 23, true); + case 'I': + return $this->buildNumericRangeType(0, 1, false); + } + } + foreach ($constantStrings as $constantString) { $formattedDate = date($constantString->getValue()); if (!is_numeric($formattedDate)) { @@ -48,4 +85,21 @@ public function getTypeFromFunctionCall( ]); } + private function buildNumericRangeType(int $min, int $max, bool $zeroPad): Type + { + $types = []; + + for ($i = $min; $i <= $max; $i++) { + $string = (string) $i; + + if ($zeroPad) { + $string = sprintf('%02s', $string); + } + + $types[] = new ConstantStringType($string); + } + + return new UnionType($types); + } + } diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index ffca869fb5..7960acb2ef 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -27,6 +27,7 @@ public function dataFileAsserts(): iterable require_once __DIR__ . '/data/instanceof.php'; + yield from $this->gatherAssertTypes(__DIR__ . '/data/date.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/instanceof.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/integer-range-types.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/random-int.php'); diff --git a/tests/PHPStan/Analyser/data/date.php b/tests/PHPStan/Analyser/data/date.php new file mode 100644 index 0000000000..e734a3ebab --- /dev/null +++ b/tests/PHPStan/Analyser/data/date.php @@ -0,0 +1,34 @@ + Date: Fri, 29 Oct 2021 02:15:25 +0200 Subject: [PATCH 0522/1284] feat(type): support $preserve_keys argument for iterator_to_array() --- conf/config.neon | 5 ++ ...gumentBasedFunctionReturnTypeExtension.php | 1 - ...atorToArrayFunctionReturnTypeExtension.php | 49 +++++++++++++++++++ .../Analyser/data/iterator_to_array.php | 22 +++++++-- 4 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 src/Type/Php/IteratorToArrayFunctionReturnTypeExtension.php diff --git a/conf/config.neon b/conf/config.neon index 5a464ff3f5..940381e10c 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1365,6 +1365,11 @@ services: tags: - phpstan.typeSpecifier.functionTypeSpecifyingExtension + - + class: PHPStan\Type\Php\IteratorToArrayFunctionReturnTypeExtension + tags: + - phpstan.broker.dynamicFunctionReturnTypeExtension + - class: PHPStan\Type\Php\IsObjectFunctionTypeSpecifyingExtension tags: diff --git a/src/Type/Php/ArgumentBasedFunctionReturnTypeExtension.php b/src/Type/Php/ArgumentBasedFunctionReturnTypeExtension.php index b4e4942f8a..669277d93a 100644 --- a/src/Type/Php/ArgumentBasedFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArgumentBasedFunctionReturnTypeExtension.php @@ -34,7 +34,6 @@ class ArgumentBasedFunctionReturnTypeExtension implements \PHPStan\Type\DynamicF 'array_uintersect_assoc' => 0, 'array_uintersect_uassoc' => 0, 'array_uintersect' => 0, - 'iterator_to_array' => 0, ]; public function isFunctionSupported(FunctionReflection $functionReflection): bool diff --git a/src/Type/Php/IteratorToArrayFunctionReturnTypeExtension.php b/src/Type/Php/IteratorToArrayFunctionReturnTypeExtension.php new file mode 100644 index 0000000000..aadb1bb918 --- /dev/null +++ b/src/Type/Php/IteratorToArrayFunctionReturnTypeExtension.php @@ -0,0 +1,49 @@ +getName()) === 'iterator_to_array'; + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + $arguments = $functionCall->getArgs(); + + if ($arguments === []) { + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } + + $traversableType = $scope->getType($arguments[0]->value); + $arrayKeyType = $traversableType->getIterableKeyType(); + + if (isset($arguments[1])) { + $preserveKeysType = $scope->getType($arguments[1]->value); + + if ($preserveKeysType instanceof ConstantBooleanType && !$preserveKeysType->getValue()) { + $arrayKeyType = new IntegerType(); + } + } + + return new ArrayType( + $arrayKeyType, + $traversableType->getIterableValueType() + ); + } + +} diff --git a/tests/PHPStan/Analyser/data/iterator_to_array.php b/tests/PHPStan/Analyser/data/iterator_to_array.php index 0ef007f922..e72040ad8c 100644 --- a/tests/PHPStan/Analyser/data/iterator_to_array.php +++ b/tests/PHPStan/Analyser/data/iterator_to_array.php @@ -9,10 +9,26 @@ class Foo { /** - * @param Traversable $ints + * @param Traversable $foo */ - public function doFoo(Traversable $ints) + public function testDefaultBehavior(Traversable $foo) { - assertType('array', iterator_to_array($ints)); + assertType('array', iterator_to_array($foo)); + } + + /** + * @param Traversable $foo + */ + public function testExplicitlyPreserveKeys(Traversable $foo) + { + assertType('array', iterator_to_array($foo, true)); + } + + /** + * @param Traversable $foo + */ + public function testNotPreservingKeys(Traversable $foo) + { + assertType('array', iterator_to_array($foo, false)); } } From 6a64c8aa95ebdd15832cf482d10e543cbe18ae35 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 4 Nov 2021 14:25:21 +0100 Subject: [PATCH 0523/1284] Update nikic/php-parser to 4.13.1 --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 957e2d7afc..2035d2b3f2 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ "nette/neon": "^3.0", "nette/schema": "^1.0", "nette/utils": "^3.1.3", - "nikic/php-parser": "4.13.0", + "nikic/php-parser": "4.13.1", "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "4.3.74", "phpstan/php-8-stubs": "^0.1.23", diff --git a/composer.lock b/composer.lock index a6551ad9f5..b811e7c7c0 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "77399b1595ef35ee686bf5a93bc51913", + "content-hash": "caf3eedd59908a05f4cec1078128e6c7", "packages": [ { "name": "clue/block-react", @@ -1946,16 +1946,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.13.0", + "version": "v4.13.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "50953a2691a922aa1769461637869a0a2faa3f53" + "reference": "63a79e8daa781cac14e5195e63ed8ae231dd10fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/50953a2691a922aa1769461637869a0a2faa3f53", - "reference": "50953a2691a922aa1769461637869a0a2faa3f53", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/63a79e8daa781cac14e5195e63ed8ae231dd10fd", + "reference": "63a79e8daa781cac14e5195e63ed8ae231dd10fd", "shasum": "" }, "require": { @@ -1996,9 +1996,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.13.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.13.1" }, - "time": "2021-09-20T12:20:58+00:00" + "time": "2021-11-03T20:52:16+00:00" }, { "name": "ondram/ci-detector", From fbc59c75a5f31dc43c14c8964b6ca16192d98c08 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 4 Nov 2021 14:59:51 +0100 Subject: [PATCH 0524/1284] Regression test Closes https://github.com/phpstan/phpstan/issues/5458 --- .../Analyser/NodeScopeResolverTest.php | 1 + tests/PHPStan/Analyser/data/bug-5458.php | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 tests/PHPStan/Analyser/data/bug-5458.php diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 7960acb2ef..256ed7cc84 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -528,6 +528,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4743.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5017.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2760.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5458.php'); } /** diff --git a/tests/PHPStan/Analyser/data/bug-5458.php b/tests/PHPStan/Analyser/data/bug-5458.php new file mode 100644 index 0000000000..4dd1da8451 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-5458.php @@ -0,0 +1,23 @@ + Date: Thu, 4 Nov 2021 15:18:31 +0100 Subject: [PATCH 0525/1284] Fix --- tests/PHPStan/Analyser/NodeScopeResolverTest.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 256ed7cc84..85bb9c3461 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -527,8 +527,12 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5501.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4743.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5017.php'); + + if (PHP_VERSION_ID >= 70400) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5458.php'); + } + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2760.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5458.php'); } /** From 280175fedfaf7b6ca90505980c31c51864a1d98c Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Thu, 4 Nov 2021 15:33:43 +0000 Subject: [PATCH 0526/1284] functionMap: fix return type of get_loaded_extensions() --- resources/functionMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index dddd7b5d0d..b63ddbbb79 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -3316,7 +3316,7 @@ 'get_html_translation_table' => ['array', 'table='=>'int', 'flags='=>'int', 'encoding='=>'string'], 'get_include_path' => ['string|false'], 'get_included_files' => ['array'], -'get_loaded_extensions' => ['array', 'zend_extensions='=>'bool'], +'get_loaded_extensions' => ['list', 'zend_extensions='=>'bool'], 'get_magic_quotes_gpc' => ['bool'], 'get_magic_quotes_runtime' => ['bool'], 'get_meta_tags' => ['array|false', 'filename'=>'string', 'use_include_path='=>'bool'], From 59be92fd7e736217185b075c99d2fca21a6393b1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 4 Nov 2021 17:04:25 +0100 Subject: [PATCH 0527/1284] Support for native never return type --- src/Type/ParserNodeTypeToPHPStanType.php | 2 ++ src/Type/TypehintHelper.php | 5 +++++ .../Analyser/NodeScopeResolverTest.php | 4 ++++ tests/PHPStan/Analyser/data/never.php | 20 +++++++++++++++++++ 4 files changed, 31 insertions(+) create mode 100644 tests/PHPStan/Analyser/data/never.php diff --git a/src/Type/ParserNodeTypeToPHPStanType.php b/src/Type/ParserNodeTypeToPHPStanType.php index 4ed5f5b10a..e0f6c58ed3 100644 --- a/src/Type/ParserNodeTypeToPHPStanType.php +++ b/src/Type/ParserNodeTypeToPHPStanType.php @@ -82,6 +82,8 @@ public static function resolve($type, ?ClassReflection $classReflection): Type return new NullType(); } elseif ($type === 'mixed') { return new MixedType(true); + } elseif ($type === 'never') { + return new NeverType(true); } return new MixedType(); diff --git a/src/Type/TypehintHelper.php b/src/Type/TypehintHelper.php index 2ee54e7a55..934538ecc8 100644 --- a/src/Type/TypehintHelper.php +++ b/src/Type/TypehintHelper.php @@ -57,6 +57,8 @@ private static function getTypeObjectFromTypehint(string $typeString, ?string $s return new ErrorType(); case 'null': return new NullType(); + case 'never': + return new NeverType(true); default: return new ObjectType($typeString); } @@ -102,6 +104,9 @@ public static function decideTypeFromReflection( if (\Nette\Utils\Strings::endsWith(strtolower($reflectionTypeString), '\\null')) { $reflectionTypeString = 'null'; } + if (\Nette\Utils\Strings::endsWith(strtolower($reflectionTypeString), '\\never')) { + $reflectionTypeString = 'never'; + } $type = self::getTypeObjectFromTypehint($reflectionTypeString, $selfClass); if ($reflectionType->allowsNull()) { diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 85bb9c3461..db2cab80cf 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -532,6 +532,10 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5458.php'); } + if (PHP_VERSION_ID >= 80100 || self::$useStaticReflectionProvider) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/never.php'); + } + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2760.php'); } diff --git a/tests/PHPStan/Analyser/data/never.php b/tests/PHPStan/Analyser/data/never.php new file mode 100644 index 0000000000..7becca9eb1 --- /dev/null +++ b/tests/PHPStan/Analyser/data/never.php @@ -0,0 +1,20 @@ += 8.1 + +namespace NeverTest; + +use function PHPStan\Testing\assertType; + +class Foo +{ + + public function doFoo(): never + { + exit(); + } + + public function doBar() + { + assertType('*NEVER*', $this->doFoo()); + } + +} From be0555743559a43c183089417a390299a11b7a3b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 4 Nov 2021 17:09:45 +0100 Subject: [PATCH 0528/1284] Support for native pure intersection types --- composer.json | 2 +- composer.lock | 14 ++++----- conf/config.neon | 1 + .../ClassBlacklistReflectionProvider.php | 2 +- src/Type/TypehintHelper.php | 9 ++++++ stubs/runtime/ReflectionIntersectionType.php | 18 ++++++++++++ .../Analyser/NodeScopeResolverTest.php | 4 +++ .../Analyser/data/native-intersection.php | 29 +++++++++++++++++++ 8 files changed, 70 insertions(+), 9 deletions(-) create mode 100644 stubs/runtime/ReflectionIntersectionType.php create mode 100644 tests/PHPStan/Analyser/data/native-intersection.php diff --git a/composer.json b/composer.json index 2035d2b3f2..c0949cb8a2 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "nette/utils": "^3.1.3", "nikic/php-parser": "4.13.1", "ondram/ci-detector": "^3.4.0", - "ondrejmirtes/better-reflection": "4.3.74", + "ondrejmirtes/better-reflection": "4.3.75", "phpstan/php-8-stubs": "^0.1.23", "phpstan/phpdoc-parser": "^1.2.0", "react/child-process": "^0.6.4", diff --git a/composer.lock b/composer.lock index b811e7c7c0..e4afde790c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "caf3eedd59908a05f4cec1078128e6c7", + "content-hash": "03cdc019b10c39fb2268fbf1321e0e9d", "packages": [ { "name": "clue/block-react", @@ -2074,16 +2074,16 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "4.3.74", + "version": "4.3.75", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "c1e75769a4901b18546b239fefaa58ad9f854859" + "reference": "23524f006346313e0371b3a7d938b1a40a50c6b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/c1e75769a4901b18546b239fefaa58ad9f854859", - "reference": "c1e75769a4901b18546b239fefaa58ad9f854859", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/23524f006346313e0371b3a7d938b1a40a50c6b0", + "reference": "23524f006346313e0371b3a7d938b1a40a50c6b0", "shasum": "" }, "require": { @@ -2138,9 +2138,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/4.3.74" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/4.3.75" }, - "time": "2021-10-26T20:17:23+00:00" + "time": "2021-11-04T15:58:23+00:00" }, { "name": "phpstan/php-8-stubs", diff --git a/conf/config.neon b/conf/config.neon index 940381e10c..1c48087861 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -5,6 +5,7 @@ parameters: - ../stubs/runtime/ReflectionUnionType.php - ../stubs/runtime/ReflectionAttribute.php - ../stubs/runtime/Attribute.php + - ../stubs/runtime/ReflectionIntersectionType.php excludes_analyse: [] excludePaths: null level: null diff --git a/src/Reflection/ReflectionProvider/ClassBlacklistReflectionProvider.php b/src/Reflection/ReflectionProvider/ClassBlacklistReflectionProvider.php index ce896640a0..1d6c2ce2e9 100644 --- a/src/Reflection/ReflectionProvider/ClassBlacklistReflectionProvider.php +++ b/src/Reflection/ReflectionProvider/ClassBlacklistReflectionProvider.php @@ -79,7 +79,7 @@ private function isClassBlacklisted(string $className): bool if (!class_exists($className, false)) { return true; } - if (in_array(strtolower($className), ['reflectionuniontype', 'attribute', 'returntypewillchange'], true)) { + if (in_array(strtolower($className), ['reflectionuniontype', 'attribute', 'returntypewillchange', 'reflectionintersectiontype'], true)) { return true; } $reflection = new \ReflectionClass($className); diff --git a/src/Type/TypehintHelper.php b/src/Type/TypehintHelper.php index 934538ecc8..368fc118aa 100644 --- a/src/Type/TypehintHelper.php +++ b/src/Type/TypehintHelper.php @@ -5,6 +5,7 @@ use PHPStan\Reflection\ReflectionProviderStaticAccessor; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Generic\TemplateTypeHelper; +use ReflectionIntersectionType; use ReflectionNamedType; use ReflectionType; use ReflectionUnionType; @@ -87,6 +88,14 @@ public static function decideTypeFromReflection( return self::decideType($type, $phpDocType); } + if ($reflectionType instanceof ReflectionIntersectionType) { + $type = TypeCombinator::intersect(...array_map(static function (ReflectionType $type) use ($selfClass): Type { + return self::decideTypeFromReflection($type, null, $selfClass, false); + }, $reflectionType->getTypes())); + + return self::decideType($type, $phpDocType); + } + if (!$reflectionType instanceof ReflectionNamedType) { throw new \PHPStan\ShouldNotHappenException(sprintf('Unexpected type: %s', get_class($reflectionType))); } diff --git a/stubs/runtime/ReflectionIntersectionType.php b/stubs/runtime/ReflectionIntersectionType.php new file mode 100644 index 0000000000..764a9101a7 --- /dev/null +++ b/stubs/runtime/ReflectionIntersectionType.php @@ -0,0 +1,18 @@ +gatherAssertTypes(__DIR__ . '/data/never.php'); } + if (PHP_VERSION_ID >= 80100 || self::$useStaticReflectionProvider) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/native-intersection.php'); + } + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2760.php'); } diff --git a/tests/PHPStan/Analyser/data/native-intersection.php b/tests/PHPStan/Analyser/data/native-intersection.php new file mode 100644 index 0000000000..c7dcb7c71c --- /dev/null +++ b/tests/PHPStan/Analyser/data/native-intersection.php @@ -0,0 +1,29 @@ += 8.1 + +namespace NativeIntersection; + +use function PHPStan\Testing\assertType; + +interface A +{ + +} + +interface B +{ + +} + +class Foo +{ + + private A&B $prop; + + public function doFoo(A&B $ab): A&B + { + assertType('NativeIntersection\A&NativeIntersection\B', $this->prop); + assertType('NativeIntersection\A&NativeIntersection\B', $ab); + assertType('NativeIntersection\A&NativeIntersection\B', $this->doFoo($ab)); + } + +} From 3c34721fcdf294de3f0437c10c11f2dda0cb98ab Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 4 Nov 2021 17:21:52 +0100 Subject: [PATCH 0529/1284] Fix --- build/composer-require-checker.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/composer-require-checker.json b/build/composer-require-checker.json index 45fb391f8d..8eebc812d5 100644 --- a/build/composer-require-checker.json +++ b/build/composer-require-checker.json @@ -8,7 +8,7 @@ "FILTER_SANITIZE_EMAIL", "FILTER_SANITIZE_EMAIL", "FILTER_SANITIZE_ENCODED", "FILTER_SANITIZE_MAGIC_QUOTES", "FILTER_SANITIZE_NUMBER_FLOAT", "FILTER_SANITIZE_NUMBER_INT", "FILTER_SANITIZE_SPECIAL_CHARS", "FILTER_SANITIZE_STRING", "FILTER_SANITIZE_URL", "FILTER_VALIDATE_BOOLEAN", "FILTER_VALIDATE_EMAIL", "FILTER_VALIDATE_FLOAT", "FILTER_VALIDATE_INT", "FILTER_VALIDATE_IP", "FILTER_VALIDATE_MAC", "FILTER_VALIDATE_REGEXP", - "FILTER_VALIDATE_URL", "FILTER_NULL_ON_FAILURE", "FILTER_FORCE_ARRAY", "FILTER_SANITIZE_ADD_SLASHES", "FILTER_DEFAULT", "FILTER_UNSAFE_RAW", "opcache_invalidate", "ValueError", "ReflectionUnionType", "Attribute", + "FILTER_VALIDATE_URL", "FILTER_NULL_ON_FAILURE", "FILTER_FORCE_ARRAY", "FILTER_SANITIZE_ADD_SLASHES", "FILTER_DEFAULT", "FILTER_UNSAFE_RAW", "opcache_invalidate", "ValueError", "ReflectionUnionType", "ReflectionIntersectionType", "Attribute", "Clue\\React\\Block\\await" ], "php-core-extensions" : [ From b3a0db6b056cbb267565c5cfe0d5a3499d1ddf53 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 4 Nov 2021 17:31:20 +0100 Subject: [PATCH 0530/1284] Fix --- tests/PHPStan/Command/CommandHelperTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/PHPStan/Command/CommandHelperTest.php b/tests/PHPStan/Command/CommandHelperTest.php index 5f72cadaee..ed0509486e 100644 --- a/tests/PHPStan/Command/CommandHelperTest.php +++ b/tests/PHPStan/Command/CommandHelperTest.php @@ -169,6 +169,7 @@ public function dataParameters(): array realpath(__DIR__ . '/../../../stubs/runtime/ReflectionUnionType.php'), realpath(__DIR__ . '/../../../stubs/runtime/ReflectionAttribute.php'), realpath(__DIR__ . '/../../../stubs/runtime/Attribute.php'), + realpath(__DIR__ . '/../../../stubs/runtime/ReflectionIntersectionType.php'), __DIR__ . DIRECTORY_SEPARATOR . 'relative-paths' . DIRECTORY_SEPARATOR . 'here.php', ], 'scanFiles' => [ From 49a77b6e1726dd5363c067ae4c6ba8dcce45ba0b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 4 Nov 2021 17:42:21 +0100 Subject: [PATCH 0531/1284] Do not prefix ReflectionIntersectionType in the PHAR --- compiler/build/scoper.inc.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/compiler/build/scoper.inc.php b/compiler/build/scoper.inc.php index e8a2d3d755..8104e4e35b 100644 --- a/compiler/build/scoper.inc.php +++ b/compiler/build/scoper.inc.php @@ -196,6 +196,16 @@ function (string $filePath, string $prefix, string $content): string { return str_replace(sprintf('%s\\ReflectionUnionType', $prefix), 'ReflectionUnionType', $content); }, + function (string $filePath, string $prefix, string $content): string { + if (!in_array($filePath, [ + 'src/Type/TypehintHelper.php', + 'vendor/ondrejmirtes/better-reflection/src/Reflection/Adapter/ReflectionIntersectionType.php', + ], true)) { + return $content; + } + + return str_replace(sprintf('%s\\ReflectionIntersectionType', $prefix), 'ReflectionIntersectionType', $content); + }, function (string $filePath, string $prefix, string $content): string { if (strpos($filePath, 'src/') !== 0) { return $content; From bbd50d2110e63d92d08e1e2ce788e156f4562f6e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 5 Nov 2021 10:43:53 +0100 Subject: [PATCH 0532/1284] Test native never type --- tests/PHPStan/Analyser/data/never.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/PHPStan/Analyser/data/never.php b/tests/PHPStan/Analyser/data/never.php index 7becca9eb1..d09728b2f3 100644 --- a/tests/PHPStan/Analyser/data/never.php +++ b/tests/PHPStan/Analyser/data/never.php @@ -17,4 +17,13 @@ public function doBar() assertType('*NEVER*', $this->doFoo()); } + public function doBaz(?int $i) + { + if ($i === null) { + $this->doFoo(); + } + + assertType('int', $i); + } + } From 0e98150eff1d71de28c683a8de115c4efef83b8a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 5 Nov 2021 11:23:57 +0100 Subject: [PATCH 0533/1284] Update BetterReflection with support for tentative return types --- composer.json | 2 +- composer.lock | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index c0949cb8a2..6de66de73e 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "nette/utils": "^3.1.3", "nikic/php-parser": "4.13.1", "ondram/ci-detector": "^3.4.0", - "ondrejmirtes/better-reflection": "4.3.75", + "ondrejmirtes/better-reflection": "4.3.76", "phpstan/php-8-stubs": "^0.1.23", "phpstan/phpdoc-parser": "^1.2.0", "react/child-process": "^0.6.4", diff --git a/composer.lock b/composer.lock index e4afde790c..cf7c004645 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "03cdc019b10c39fb2268fbf1321e0e9d", + "content-hash": "cce39014acc6ed91f935ff1d09084570", "packages": [ { "name": "clue/block-react", @@ -2074,21 +2074,21 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "4.3.75", + "version": "4.3.76", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "23524f006346313e0371b3a7d938b1a40a50c6b0" + "reference": "4710a2da24f67fde925a454a57036abacc9bbfa3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/23524f006346313e0371b3a7d938b1a40a50c6b0", - "reference": "23524f006346313e0371b3a7d938b1a40a50c6b0", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/4710a2da24f67fde925a454a57036abacc9bbfa3", + "reference": "4710a2da24f67fde925a454a57036abacc9bbfa3", "shasum": "" }, "require": { "ext-json": "*", - "jetbrains/phpstorm-stubs": "dev-master#0a73df114cdea7f30c8b5f6fbfbf8e6839a89e88", + "jetbrains/phpstorm-stubs": "dev-master#fdcc30312221ce08f3a4413588e2df4b77f843fc", "nikic/php-parser": "^4.13.0", "php": ">=7.1.0" }, @@ -2138,9 +2138,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/4.3.75" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/4.3.76" }, - "time": "2021-11-04T15:58:23+00:00" + "time": "2021-11-05T10:21:18+00:00" }, { "name": "phpstan/php-8-stubs", From 546e87c61ff1b8591c7300d16484093870410a8c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 5 Nov 2021 11:27:36 +0100 Subject: [PATCH 0534/1284] Update jetbrains/phpstorm-stubs --- composer.json | 2 +- composer.lock | 12 ++++++------ patches/SessionHandler.patch | 20 ++++++++++---------- resources/functionMetadata.php | 14 ++++++++++---- 4 files changed, 27 insertions(+), 21 deletions(-) diff --git a/composer.json b/composer.json index 6de66de73e..4af3ba847d 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,7 @@ "hoa/exception": "^1.0", "hoa/regex": "1.17.01.13", "jean85/pretty-package-versions": "^1.0.3", - "jetbrains/phpstorm-stubs": "dev-master#82595d7a426c4b3d1e3a7d604ad3f99534784599", + "jetbrains/phpstorm-stubs": "dev-master#fdcc30312221ce08f3a4413588e2df4b77f843fc", "nette/bootstrap": "^3.0", "nette/di": "^3.0.5", "nette/finder": "^2.5", diff --git a/composer.lock b/composer.lock index cf7c004645..a4bb64d351 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "cce39014acc6ed91f935ff1d09084570", + "content-hash": "cb4eb6b4504a6c36ea81c50ccb02b59c", "packages": [ { "name": "clue/block-react", @@ -1335,16 +1335,16 @@ "source": { "type": "git", "url": "https://github.com/JetBrains/phpstorm-stubs.git", - "reference": "82595d7a426c4b3d1e3a7d604ad3f99534784599" + "reference": "fdcc30312221ce08f3a4413588e2df4b77f843fc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/82595d7a426c4b3d1e3a7d604ad3f99534784599", - "reference": "82595d7a426c4b3d1e3a7d604ad3f99534784599", + "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/fdcc30312221ce08f3a4413588e2df4b77f843fc", + "reference": "fdcc30312221ce08f3a4413588e2df4b77f843fc", "shasum": "" }, "require-dev": { - "friendsofphp/php-cs-fixer": "@stable", + "friendsofphp/php-cs-fixer": "dev-master", "nikic/php-parser": "@stable", "php": "^8.0", "phpdocumentor/reflection-docblock": "@stable", @@ -1376,7 +1376,7 @@ "support": { "source": "https://github.com/JetBrains/phpstorm-stubs/tree/master" }, - "time": "2021-09-09T16:57:58+00:00" + "time": "2021-11-04T13:27:30+00:00" }, { "name": "nette/bootstrap", diff --git a/patches/SessionHandler.patch b/patches/SessionHandler.patch index 5b05a02df5..598c201951 100644 --- a/patches/SessionHandler.patch +++ b/patches/SessionHandler.patch @@ -1,23 +1,23 @@ @package jetbrains/phpstorm-stubs @version dev-master ---- session/SessionHandler.php 2021-09-09 18:57:58.000000000 +0200 -+++ session/SessionHandler.php 2021-09-12 19:00:13.000000000 +0200 -@@ -137,7 +137,7 @@ - * Note this value is returned internally to PHP for processing. +--- session/SessionHandler.php 2021-11-04 14:27:30.000000000 +0100 ++++ session/SessionHandler.php 2021-11-05 11:26:14.000000000 +0100 +@@ -147,7 +147,7 @@ *

*/ -- public function validateId(string $id); -+ public function validateId($id); + #[TentativeType] +- public function validateId(string $id): bool; ++ public function validateId($id): bool; /** * Update timestamp of a session -@@ -152,7 +152,7 @@ - *

+@@ -163,7 +163,7 @@ * @return bool */ -- public function updateTimestamp(string $id, string $data); -+ public function updateTimestamp($id, $data); + #[TentativeType] +- public function updateTimestamp(string $id, string $data): bool; ++ public function updateTimestamp($id, $data): bool; } /** diff --git a/resources/functionMetadata.php b/resources/functionMetadata.php index aa006df990..e3f29358af 100644 --- a/resources/functionMetadata.php +++ b/resources/functionMetadata.php @@ -812,6 +812,7 @@ 'deg2rad' => ['hasSideEffects' => false], 'dirname' => ['hasSideEffects' => false], 'disk_free_space' => ['hasSideEffects' => false], + 'disk_total_space' => ['hasSideEffects' => false], 'diskfreespace' => ['hasSideEffects' => false], 'dngettext' => ['hasSideEffects' => false], 'doubleval' => ['hasSideEffects' => false], @@ -823,7 +824,10 @@ 'expm1' => ['hasSideEffects' => false], 'extension_loaded' => ['hasSideEffects' => false], 'fdiv' => ['hasSideEffects' => false], + 'feof' => ['hasSideEffects' => false], + 'file' => ['hasSideEffects' => false], 'file_exists' => ['hasSideEffects' => false], + 'file_get_contents' => ['hasSideEffects' => false], 'fileatime' => ['hasSideEffects' => false], 'filectime' => ['hasSideEffects' => false], 'filegroup' => ['hasSideEffects' => false], @@ -845,6 +849,9 @@ 'floatval' => ['hasSideEffects' => false], 'floor' => ['hasSideEffects' => false], 'fmod' => ['hasSideEffects' => false], + 'fnmatch' => ['hasSideEffects' => false], + 'fstat' => ['hasSideEffects' => false], + 'ftell' => ['hasSideEffects' => false], 'ftok' => ['hasSideEffects' => false], 'func_get_arg' => ['hasSideEffects' => false], 'func_get_args' => ['hasSideEffects' => false], @@ -954,9 +961,6 @@ 'gmp_pow' => ['hasSideEffects' => false], 'gmp_powm' => ['hasSideEffects' => false], 'gmp_prob_prime' => ['hasSideEffects' => false], - 'gmp_random' => ['hasSideEffects' => false], - 'gmp_random_bits' => ['hasSideEffects' => false], - 'gmp_random_range' => ['hasSideEffects' => false], 'gmp_root' => ['hasSideEffects' => false], 'gmp_rootrem' => ['hasSideEffects' => false], 'gmp_scan0' => ['hasSideEffects' => false], @@ -1298,10 +1302,12 @@ 'numfmt_get_text_attribute' => ['hasSideEffects' => false], 'numfmt_parse' => ['hasSideEffects' => false], 'ob_etaghandler' => ['hasSideEffects' => false], + 'ob_get_contents' => ['hasSideEffects' => false], 'ob_iconv_handler' => ['hasSideEffects' => false], 'octdec' => ['hasSideEffects' => false], 'ord' => ['hasSideEffects' => false], 'pack' => ['hasSideEffects' => false], + 'parse_ini_file' => ['hasSideEffects' => false], 'parse_ini_string' => ['hasSideEffects' => false], 'parse_url' => ['hasSideEffects' => false], 'pathinfo' => ['hasSideEffects' => false], @@ -1369,6 +1375,7 @@ 'range' => ['hasSideEffects' => false], 'rawurldecode' => ['hasSideEffects' => false], 'rawurlencode' => ['hasSideEffects' => false], + 'readlink' => ['hasSideEffects' => false], 'realpath' => ['hasSideEffects' => false], 'realpath_cache_get' => ['hasSideEffects' => false], 'realpath_cache_size' => ['hasSideEffects' => false], @@ -1397,7 +1404,6 @@ 'str_pad' => ['hasSideEffects' => false], 'str_repeat' => ['hasSideEffects' => false], 'str_rot13' => ['hasSideEffects' => false], - 'str_shuffle' => ['hasSideEffects' => false], 'str_split' => ['hasSideEffects' => false], 'str_starts_with' => ['hasSideEffects' => false], 'str_word_count' => ['hasSideEffects' => false], From 762fc470a2b5308d4d0469457bac809e6ddc2196 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 5 Nov 2021 13:36:14 +0100 Subject: [PATCH 0535/1284] Support for tentative return types --- src/Php/PhpVersion.php | 5 +++ src/Reflection/MethodPrototypeReflection.php | 14 +++++- .../Native/NativeMethodReflection.php | 9 +++- .../Php/BuiltinMethodReflection.php | 2 + .../Php/FakeBuiltinMethodReflection.php | 5 +++ .../Php/NativeBuiltinMethodReflection.php | 9 ++++ src/Reflection/Php/PhpMethodReflection.php | 8 +++- src/Rules/Methods/OverridingMethodRule.php | 35 ++++++++++++++- .../Methods/OverridingMethodRuleTest.php | 38 ++++++++++++++++ .../Methods/data/tentative-return-types.php | 45 +++++++++++++++++++ 10 files changed, 165 insertions(+), 5 deletions(-) create mode 100644 tests/PHPStan/Rules/Methods/data/tentative-return-types.php diff --git a/src/Php/PhpVersion.php b/src/Php/PhpVersion.php index f085f65192..fb61ba1d01 100644 --- a/src/Php/PhpVersion.php +++ b/src/Php/PhpVersion.php @@ -152,4 +152,9 @@ public function supportsCaseInsensitiveConstantNames(): bool return $this->versionId < 80000; } + public function hasTentativeReturnTypes(): bool + { + return $this->versionId >= 80100; + } + } diff --git a/src/Reflection/MethodPrototypeReflection.php b/src/Reflection/MethodPrototypeReflection.php index cb2ecabc58..e9283fbe75 100644 --- a/src/Reflection/MethodPrototypeReflection.php +++ b/src/Reflection/MethodPrototypeReflection.php @@ -2,6 +2,8 @@ namespace PHPStan\Reflection; +use PHPStan\Type\Type; + class MethodPrototypeReflection implements ClassMemberReflection { @@ -22,6 +24,8 @@ class MethodPrototypeReflection implements ClassMemberReflection /** @var ParametersAcceptor[] */ private array $variants; + private ?Type $tentativeReturnType; + /** * @param string $name * @param ClassReflection $declaringClass @@ -31,6 +35,7 @@ class MethodPrototypeReflection implements ClassMemberReflection * @param bool $isAbstract * @param bool $isFinal * @param ParametersAcceptor[] $variants + * @param ?Type $tentativeReturnType */ public function __construct( string $name, @@ -40,7 +45,8 @@ public function __construct( bool $isPublic, bool $isAbstract, bool $isFinal, - array $variants + array $variants, + ?Type $tentativeReturnType ) { $this->name = $name; @@ -51,6 +57,7 @@ public function __construct( $this->isAbstract = $isAbstract; $this->isFinal = $isFinal; $this->variants = $variants; + $this->tentativeReturnType = $tentativeReturnType; } public function getName(): string @@ -101,4 +108,9 @@ public function getVariants(): array return $this->variants; } + public function getTentativeReturnType(): ?Type + { + return $this->tentativeReturnType; + } + } diff --git a/src/Reflection/Native/NativeMethodReflection.php b/src/Reflection/Native/NativeMethodReflection.php index 1410a99583..b4a55b824a 100644 --- a/src/Reflection/Native/NativeMethodReflection.php +++ b/src/Reflection/Native/NativeMethodReflection.php @@ -10,6 +10,7 @@ use PHPStan\Reflection\ReflectionProvider; use PHPStan\TrinaryLogic; use PHPStan\Type\Type; +use PHPStan\Type\TypehintHelper; use PHPStan\Type\VoidType; class NativeMethodReflection implements MethodReflection @@ -89,6 +90,11 @@ public function getPrototype(): ClassMemberReflection $prototypeMethod = $this->reflection->getPrototype(); $prototypeDeclaringClass = $this->reflectionProvider->getClass($prototypeMethod->getDeclaringClass()->getName()); + $tentativeReturnType = null; + if ($prototypeMethod->getTentativeReturnType() !== null) { + $tentativeReturnType = TypehintHelper::decideTypeFromReflection($prototypeMethod->getTentativeReturnType()); + } + return new MethodPrototypeReflection( $prototypeMethod->getName(), $prototypeDeclaringClass, @@ -97,7 +103,8 @@ public function getPrototype(): ClassMemberReflection $prototypeMethod->isPublic(), $prototypeMethod->isAbstract(), $prototypeMethod->isFinal(), - $prototypeDeclaringClass->getNativeMethod($prototypeMethod->getName())->getVariants() + $prototypeDeclaringClass->getNativeMethod($prototypeMethod->getName())->getVariants(), + $tentativeReturnType ); } catch (\ReflectionException $e) { return $this; diff --git a/src/Reflection/Php/BuiltinMethodReflection.php b/src/Reflection/Php/BuiltinMethodReflection.php index 266d002075..83153873b0 100644 --- a/src/Reflection/Php/BuiltinMethodReflection.php +++ b/src/Reflection/Php/BuiltinMethodReflection.php @@ -35,6 +35,8 @@ public function isVariadic(): bool; public function getReturnType(): ?\ReflectionType; + public function getTentativeReturnType(): ?\ReflectionType; + /** * @return \ReflectionParameter[] */ diff --git a/src/Reflection/Php/FakeBuiltinMethodReflection.php b/src/Reflection/Php/FakeBuiltinMethodReflection.php index cfb2d1b831..e6bd53c354 100644 --- a/src/Reflection/Php/FakeBuiltinMethodReflection.php +++ b/src/Reflection/Php/FakeBuiltinMethodReflection.php @@ -105,6 +105,11 @@ public function getReturnType(): ?\ReflectionType return null; } + public function getTentativeReturnType(): ?\ReflectionType + { + return null; + } + /** * @return \ReflectionParameter[] */ diff --git a/src/Reflection/Php/NativeBuiltinMethodReflection.php b/src/Reflection/Php/NativeBuiltinMethodReflection.php index 692c06a010..681a7c4429 100644 --- a/src/Reflection/Php/NativeBuiltinMethodReflection.php +++ b/src/Reflection/Php/NativeBuiltinMethodReflection.php @@ -124,6 +124,15 @@ public function getReturnType(): ?\ReflectionType return $this->reflection->getReturnType(); } + public function getTentativeReturnType(): ?\ReflectionType + { + if (method_exists($this->reflection, 'getTentativeReturnType')) { + return $this->reflection->getTentativeReturnType(); + } + + return null; + } + /** * @return \ReflectionParameter[] */ diff --git a/src/Reflection/Php/PhpMethodReflection.php b/src/Reflection/Php/PhpMethodReflection.php index 94ce869502..b39acc065e 100644 --- a/src/Reflection/Php/PhpMethodReflection.php +++ b/src/Reflection/Php/PhpMethodReflection.php @@ -162,6 +162,11 @@ public function getPrototype(): ClassMemberReflection $prototypeMethod = $this->reflection->getPrototype(); $prototypeDeclaringClass = $this->reflectionProvider->getClass($prototypeMethod->getDeclaringClass()->getName()); + $tentativeReturnType = null; + if ($prototypeMethod->getTentativeReturnType() !== null) { + $tentativeReturnType = TypehintHelper::decideTypeFromReflection($prototypeMethod->getTentativeReturnType()); + } + return new MethodPrototypeReflection( $prototypeMethod->getName(), $prototypeDeclaringClass, @@ -170,7 +175,8 @@ public function getPrototype(): ClassMemberReflection $prototypeMethod->isPublic(), $prototypeMethod->isAbstract(), $prototypeMethod->isFinal(), - $prototypeDeclaringClass->getNativeMethod($prototypeMethod->getName())->getVariants() + $prototypeDeclaringClass->getNativeMethod($prototypeMethod->getName())->getVariants(), + $tentativeReturnType ); } catch (\ReflectionException $e) { return $this; diff --git a/src/Rules/Methods/OverridingMethodRule.php b/src/Rules/Methods/OverridingMethodRule.php index 56cdd11762..e19ef957b0 100644 --- a/src/Rules/Methods/OverridingMethodRule.php +++ b/src/Rules/Methods/OverridingMethodRule.php @@ -145,8 +145,28 @@ public function processNode(Node $node, Scope $scope): array $prototypeVariant = $prototypeVariants[0]; $methodVariant = ParametersAcceptorSelector::selectSingle($method->getVariants()); + $methodReturnType = $methodVariant->getNativeReturnType(); $methodParameters = $methodVariant->getParameters(); + if ( + $this->phpVersion->hasTentativeReturnTypes() + && $prototype->getTentativeReturnType() !== null + && !$this->hasReturnTypeWillChangeAttribute($node->getOriginalNode()) + ) { + + if (!$this->isTypeCompatible($prototype->getTentativeReturnType(), $methodVariant->getNativeReturnType(), true)) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Return type %s of method %s::%s() is not covariant with tentative return type %s of method %s::%s().', + $methodReturnType->describe(VerbosityLevel::typeOnly()), + $method->getDeclaringClass()->getDisplayName(), + $method->getName(), + $prototype->getTentativeReturnType()->describe(VerbosityLevel::typeOnly()), + $prototype->getDeclaringClass()->getDisplayName(), + $prototype->getName() + ))->tip('Make it covariant, or use the #[\ReturnTypeWillChange] attribute to temporarily suppress the error.')->nonIgnorable()->build(); + } + } + $prototypeAfterVariadic = false; foreach ($prototypeVariant->getParameters() as $i => $prototypeParameter) { if (!array_key_exists($i, $methodParameters)) { @@ -380,8 +400,6 @@ public function processNode(Node $node, Scope $scope): array } } - $methodReturnType = $methodVariant->getNativeReturnType(); - if (!$prototypeVariant instanceof FunctionVariantWithPhpDocs) { return $this->addErrors($messages, $node, $scope); } @@ -466,4 +484,17 @@ private function addErrors( return $this->methodSignatureRule->processNode($classMethod, $scope); } + private function hasReturnTypeWillChangeAttribute(Node\Stmt\ClassMethod $method): bool + { + foreach ($method->attrGroups as $attrGroup) { + foreach ($attrGroup->attrs as $attr) { + if ($attr->name->toLowerString() === 'returntypewillchange') { + return true; + } + } + } + + return false; + } + } diff --git a/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php b/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php index 939caa2cd4..d9f25a83dc 100644 --- a/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php +++ b/tests/PHPStan/Rules/Methods/OverridingMethodRuleTest.php @@ -516,4 +516,42 @@ public function testBug4516(): void $this->analyse([__DIR__ . '/data/bug-4516.php'], []); } + public function dataTentativeReturnTypes(): array + { + return [ + [70400, []], + [80000, []], + [ + 80100, + [ + [ + 'Return type mixed of method TentativeReturnTypes\Foo::getIterator() is not covariant with tentative return type Traversable of method IteratorAggregate::getIterator().', + 8, + 'Make it covariant, or use the #[\ReturnTypeWillChange] attribute to temporarily suppress the error.', + ], + [ + 'Return type string of method TentativeReturnTypes\Lorem::getIterator() is not covariant with tentative return type Traversable of method IteratorAggregate::getIterator().', + 40, + 'Make it covariant, or use the #[\ReturnTypeWillChange] attribute to temporarily suppress the error.', + ], + ], + ], + ]; + } + + /** + * @dataProvider dataTentativeReturnTypes + * @param int $phpVersionId + * @param mixed[] $errors + */ + public function testTentativeReturnTypes(int $phpVersionId, array $errors): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires static reflection.'); + } + + $this->phpVersionId = $phpVersionId; + $this->analyse([__DIR__ . '/data/tentative-return-types.php'], $errors); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/tentative-return-types.php b/tests/PHPStan/Rules/Methods/data/tentative-return-types.php new file mode 100644 index 0000000000..d42f319ae0 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/tentative-return-types.php @@ -0,0 +1,45 @@ + Date: Fri, 5 Nov 2021 14:33:54 +0100 Subject: [PATCH 0536/1284] Updated BetterReflection with support for PhpStormStubsElementAvailable --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 4af3ba847d..c9790751ab 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "nette/utils": "^3.1.3", "nikic/php-parser": "4.13.1", "ondram/ci-detector": "^3.4.0", - "ondrejmirtes/better-reflection": "4.3.76", + "ondrejmirtes/better-reflection": "4.3.78", "phpstan/php-8-stubs": "^0.1.23", "phpstan/phpdoc-parser": "^1.2.0", "react/child-process": "^0.6.4", diff --git a/composer.lock b/composer.lock index a4bb64d351..e5e1d68501 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "cb4eb6b4504a6c36ea81c50ccb02b59c", + "content-hash": "00797e8204d090a2f6e032deddd1eddd", "packages": [ { "name": "clue/block-react", @@ -2074,16 +2074,16 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "4.3.76", + "version": "4.3.78", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "4710a2da24f67fde925a454a57036abacc9bbfa3" + "reference": "151421cd34bc441c6b1e4ff5c89bf9de2310cff9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/4710a2da24f67fde925a454a57036abacc9bbfa3", - "reference": "4710a2da24f67fde925a454a57036abacc9bbfa3", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/151421cd34bc441c6b1e4ff5c89bf9de2310cff9", + "reference": "151421cd34bc441c6b1e4ff5c89bf9de2310cff9", "shasum": "" }, "require": { @@ -2138,9 +2138,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/4.3.76" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/4.3.78" }, - "time": "2021-11-05T10:21:18+00:00" + "time": "2021-11-05T13:42:53+00:00" }, { "name": "phpstan/php-8-stubs", From cd5a51d25de18ce73b7028e508638f56f9a01934 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 5 Nov 2021 15:32:48 +0100 Subject: [PATCH 0537/1284] Update PHP 8.1 baseline --- build/baseline-8.1.neon | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/build/baseline-8.1.neon b/build/baseline-8.1.neon index ce5b48c1c0..dd3aea97f7 100644 --- a/build/baseline-8.1.neon +++ b/build/baseline-8.1.neon @@ -10,3 +10,8 @@ parameters: count: 1 path: ../src/Reflection/Php/PhpPropertyReflection.php + - + message: "#^Call to function method_exists\\(\\) with ReflectionMethod and 'getTentativeReturnT…' will always evaluate to true\\.$#" + count: 1 + path: ../src/Reflection/Php/NativeBuiltinMethodReflection.php + From 194ff6e8546b7922bb1f579975f268d1e9822a69 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 5 Nov 2021 15:34:39 +0100 Subject: [PATCH 0538/1284] Fix test --- .../PHPStan/Reflection/ReflectionProviderTest.php | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/tests/PHPStan/Reflection/ReflectionProviderTest.php b/tests/PHPStan/Reflection/ReflectionProviderTest.php index 322d881ecb..b3231e2894 100644 --- a/tests/PHPStan/Reflection/ReflectionProviderTest.php +++ b/tests/PHPStan/Reflection/ReflectionProviderTest.php @@ -26,10 +26,17 @@ public function dataFunctionThrowType(): iterable ]; } - yield [ - 'bcdiv', - new ObjectType('DivisionByZeroError'), - ]; + if (PHP_VERSION_ID >= 80000) { + yield [ + 'bcdiv', + new ObjectType('DivisionByZeroError'), + ]; + } else { + yield [ + 'bcdiv', + null, + ]; + } yield [ 'GEOSRelateMatch', From 021e25ed2b6da96401ef754455ac5589db8eeda1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 5 Nov 2021 16:48:01 +0100 Subject: [PATCH 0539/1284] Intersection types - check for unresolvable type --- src/Php/PhpVersion.php | 5 ++ src/PhpDoc/StubValidator.php | 5 +- src/Rules/FunctionDefinitionCheck.php | 66 +++++++++++++++---- ...ingClassesInArrowFunctionTypehintsRule.php | 4 +- .../ExistingClassesInClosureTypehintsRule.php | 4 +- .../ExistingClassesInTypehintsRule.php | 10 ++- .../ExistingClassesInTypehintsRule.php | 12 +++- .../ExistingClassesInPropertiesRule.php | 21 ++++++ src/Type/ParserNodeTypeToPHPStanType.php | 7 +- src/Type/TypehintHelper.php | 14 ++-- ...lassesInArrowFunctionTypehintsRuleTest.php | 47 ++++++++++++- ...stingClassesInClosureTypehintsRuleTest.php | 47 ++++++++++++- .../ExistingClassesInTypehintsRuleTest.php | 47 ++++++++++++- .../arrow-function-intersection-types.php | 29 ++++++++ .../data/closure-intersection-types.php | 38 +++++++++++ .../Functions/data/intersection-types.php | 38 +++++++++++ .../ExistingClassesInTypehintsRuleTest.php | 47 ++++++++++++- .../Rules/Methods/data/intersection-types.php | 43 ++++++++++++ .../ExistingClassesInPropertiesRuleTest.php | 44 +++++++++++++ .../Properties/data/intersection-types.php | 34 ++++++++++ 20 files changed, 536 insertions(+), 26 deletions(-) create mode 100644 tests/PHPStan/Rules/Functions/data/arrow-function-intersection-types.php create mode 100644 tests/PHPStan/Rules/Functions/data/closure-intersection-types.php create mode 100644 tests/PHPStan/Rules/Functions/data/intersection-types.php create mode 100644 tests/PHPStan/Rules/Methods/data/intersection-types.php create mode 100644 tests/PHPStan/Rules/Properties/data/intersection-types.php diff --git a/src/Php/PhpVersion.php b/src/Php/PhpVersion.php index fb61ba1d01..4e6b02dab1 100644 --- a/src/Php/PhpVersion.php +++ b/src/Php/PhpVersion.php @@ -147,6 +147,11 @@ public function supportsEnums(): bool return $this->versionId >= 80100; } + public function supportsPureIntersectionTypes(): bool + { + return $this->versionId >= 80100; + } + public function supportsCaseInsensitiveConstantNames(): bool { return $this->versionId < 80000; diff --git a/src/PhpDoc/StubValidator.php b/src/PhpDoc/StubValidator.php index 41e3c06cd8..bc12a8b352 100644 --- a/src/PhpDoc/StubValidator.php +++ b/src/PhpDoc/StubValidator.php @@ -137,6 +137,7 @@ private function getRuleRegistry(Container $container): Registry $missingTypehintCheck = $container->getByType(MissingTypehintCheck::class); $unresolvableTypeHelper = $container->getByType(UnresolvableTypeHelper::class); $crossCheckInterfacesHelper = $container->getByType(CrossCheckInterfacesHelper::class); + $phpVersion = $container->getByType(PhpVersion::class); $rules = [ // level 0 @@ -146,8 +147,8 @@ private function getRuleRegistry(Container $container): Registry new ExistingClassInTraitUseRule($classCaseSensitivityCheck, $reflectionProvider), new ExistingClassesInTypehintsRule($functionDefinitionCheck), new \PHPStan\Rules\Functions\ExistingClassesInTypehintsRule($functionDefinitionCheck), - new ExistingClassesInPropertiesRule($reflectionProvider, $classCaseSensitivityCheck, true, false), - new OverridingMethodRule($container->getByType(PhpVersion::class), new MethodSignatureRule(true, true), true), + new ExistingClassesInPropertiesRule($reflectionProvider, $classCaseSensitivityCheck, $unresolvableTypeHelper, $phpVersion, true, false), + new OverridingMethodRule($phpVersion, new MethodSignatureRule(true, true), true), // level 2 new ClassAncestorsRule($fileTypeMapper, $genericAncestorsCheck, $crossCheckInterfacesHelper), diff --git a/src/Rules/FunctionDefinitionCheck.php b/src/Rules/FunctionDefinitionCheck.php index db0d5b9c3a..6020cc24c0 100644 --- a/src/Rules/FunctionDefinitionCheck.php +++ b/src/Rules/FunctionDefinitionCheck.php @@ -19,8 +19,10 @@ use PHPStan\Reflection\ParametersAcceptorWithPhpDocs; use PHPStan\Reflection\Php\PhpMethodFromParserNodeReflection; use PHPStan\Reflection\ReflectionProvider; +use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper; use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\NonexistentParentClassType; +use PHPStan\Type\ParserNodeTypeToPHPStanType; use PHPStan\Type\Type; use PHPStan\Type\TypeTraverser; use PHPStan\Type\VerbosityLevel; @@ -33,6 +35,8 @@ class FunctionDefinitionCheck private \PHPStan\Rules\ClassCaseSensitivityCheck $classCaseSensitivityCheck; + private UnresolvableTypeHelper $unresolvableTypeHelper; + private PhpVersion $phpVersion; private bool $checkClassCaseSensitivity; @@ -42,6 +46,7 @@ class FunctionDefinitionCheck public function __construct( ReflectionProvider $reflectionProvider, ClassCaseSensitivityCheck $classCaseSensitivityCheck, + UnresolvableTypeHelper $unresolvableTypeHelper, PhpVersion $phpVersion, bool $checkClassCaseSensitivity, bool $checkThisOnly @@ -49,6 +54,7 @@ public function __construct( { $this->reflectionProvider = $reflectionProvider; $this->classCaseSensitivityCheck = $classCaseSensitivityCheck; + $this->unresolvableTypeHelper = $unresolvableTypeHelper; $this->phpVersion = $phpVersion; $this->checkClassCaseSensitivity = $checkClassCaseSensitivity; $this->checkThisOnly = $checkThisOnly; @@ -68,7 +74,9 @@ public function checkFunction( string $parameterMessage, string $returnMessage, string $unionTypesMessage, - string $templateTypeMissingInParameterMessage + string $templateTypeMissingInParameterMessage, + string $unresolvableParameterTypeMessage, + string $unresolvableReturnTypeMessage ): array { $parametersAcceptor = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants()); @@ -79,7 +87,9 @@ public function checkFunction( $parameterMessage, $returnMessage, $unionTypesMessage, - $templateTypeMissingInParameterMessage + $templateTypeMissingInParameterMessage, + $unresolvableParameterTypeMessage, + $unresolvableReturnTypeMessage ); } @@ -98,7 +108,9 @@ public function checkAnonymousFunction( $returnTypeNode, string $parameterMessage, string $returnMessage, - string $unionTypesMessage + string $unionTypesMessage, + string $unresolvableParameterTypeMessage, + string $unresolvableReturnTypeMessage ): array { $errors = []; @@ -123,6 +135,13 @@ public function checkAnonymousFunction( if ($type instanceof VoidType) { $errors[] = RuleErrorBuilder::message(sprintf($parameterMessage, $param->var->name, 'void'))->line($param->type->getLine())->nonIgnorable()->build(); } + if ( + $this->phpVersion->supportsPureIntersectionTypes() + && $this->unresolvableTypeHelper->containsUnresolvableType($type) + ) { + $errors[] = RuleErrorBuilder::message(sprintf($unresolvableParameterTypeMessage, $param->var->name))->line($param->type->getLine())->nonIgnorable()->build(); + } + foreach ($type->getReferencedClasses() as $class) { if (!$this->reflectionProvider->hasClass($class) || $this->reflectionProvider->getClass($class)->isTrait()) { $errors[] = RuleErrorBuilder::message(sprintf($parameterMessage, $param->var->name, $class))->line($param->type->getLine())->build(); @@ -154,6 +173,13 @@ public function checkAnonymousFunction( } $returnType = $scope->getFunctionType($returnTypeNode, false, false); + if ( + $this->phpVersion->supportsPureIntersectionTypes() + && $this->unresolvableTypeHelper->containsUnresolvableType($returnType) + ) { + $errors[] = RuleErrorBuilder::message($unresolvableReturnTypeMessage)->line($returnTypeNode->getLine())->nonIgnorable()->build(); + } + foreach ($returnType->getReferencedClasses() as $returnTypeClass) { if (!$this->reflectionProvider->hasClass($returnTypeClass) || $this->reflectionProvider->getClass($returnTypeClass)->isTrait()) { $errors[] = RuleErrorBuilder::message(sprintf($returnMessage, $returnTypeClass))->line($returnTypeNode->getLine())->build(); @@ -185,7 +211,9 @@ public function checkClassMethod( string $parameterMessage, string $returnMessage, string $unionTypesMessage, - string $templateTypeMissingInParameterMessage + string $templateTypeMissingInParameterMessage, + string $unresolvableParameterTypeMessage, + string $unresolvableReturnTypeMessage ): array { /** @var \PHPStan\Reflection\ParametersAcceptorWithPhpDocs $parametersAcceptor */ @@ -197,7 +225,9 @@ public function checkClassMethod( $parameterMessage, $returnMessage, $unionTypesMessage, - $templateTypeMissingInParameterMessage + $templateTypeMissingInParameterMessage, + $unresolvableParameterTypeMessage, + $unresolvableReturnTypeMessage ); } @@ -216,7 +246,9 @@ private function checkParametersAcceptor( string $parameterMessage, string $returnMessage, string $unionTypesMessage, - string $templateTypeMissingInParameterMessage + string $templateTypeMissingInParameterMessage, + string $unresolvableParameterTypeMessage, + string $unresolvableReturnTypeMessage ): array { $errors = []; @@ -253,15 +285,20 @@ private function checkParametersAcceptor( return $parameterNode; }; - if ( - $parameter instanceof ParameterReflectionWithPhpDocs - && $parameter->getNativeType() instanceof VoidType - ) { + if ($parameter instanceof ParameterReflectionWithPhpDocs) { $parameterVar = $parameterNodeCallback()->var; if (!$parameterVar instanceof Variable || !is_string($parameterVar->name)) { throw new \PHPStan\ShouldNotHappenException(); } - $errors[] = RuleErrorBuilder::message(sprintf($parameterMessage, $parameterVar->name, 'void'))->line($parameterNodeCallback()->getLine())->nonIgnorable()->build(); + if ($parameter->getNativeType() instanceof VoidType) { + $errors[] = RuleErrorBuilder::message(sprintf($parameterMessage, $parameterVar->name, 'void'))->line($parameterNodeCallback()->getLine())->nonIgnorable()->build(); + } + if ( + $this->phpVersion->supportsPureIntersectionTypes() + && $this->unresolvableTypeHelper->containsUnresolvableType($parameter->getNativeType()) + ) { + $errors[] = RuleErrorBuilder::message(sprintf($unresolvableParameterTypeMessage, $parameterVar->name))->line($parameterNodeCallback()->getLine())->nonIgnorable()->build(); + } } foreach ($referencedClasses as $class) { if ($this->reflectionProvider->hasClass($class) && !$this->reflectionProvider->getClass($class)->isTrait()) { @@ -290,6 +327,13 @@ private function checkParametersAcceptor( $errors[] = RuleErrorBuilder::message(sprintf($parameterMessage, $parameter->getName(), $parameter->getType()->describe(VerbosityLevel::typeOnly())))->line($parameterNodeCallback()->getLine())->build(); } + if ($this->phpVersion->supportsPureIntersectionTypes() && $functionNode->getReturnType() !== null) { + $nativeReturnType = ParserNodeTypeToPHPStanType::resolve($functionNode->getReturnType(), null); + if ($this->unresolvableTypeHelper->containsUnresolvableType($nativeReturnType)) { + $errors[] = RuleErrorBuilder::message($unresolvableReturnTypeMessage)->nonIgnorable()->line($returnTypeNode->getLine())->build(); + } + } + $returnTypeReferencedClasses = $this->getReturnTypeReferencedClasses($parametersAcceptor); foreach ($returnTypeReferencedClasses as $class) { diff --git a/src/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRule.php b/src/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRule.php index 85de1d6b5c..37449cb4a0 100644 --- a/src/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRule.php +++ b/src/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRule.php @@ -32,7 +32,9 @@ public function processNode(Node $node, Scope $scope): array $node->getReturnType(), 'Parameter $%s of anonymous function has invalid type %s.', 'Anonymous function has invalid return type %s.', - 'Anonymous function uses native union types but they\'re supported only on PHP 8.0 and later.' + 'Anonymous function uses native union types but they\'re supported only on PHP 8.0 and later.', + 'Parameter $%s of anonymous function has unresolvable native type.', + 'Anonymous function has unresolvable native return type.' ); } diff --git a/src/Rules/Functions/ExistingClassesInClosureTypehintsRule.php b/src/Rules/Functions/ExistingClassesInClosureTypehintsRule.php index 9033078f9b..e774fb077f 100644 --- a/src/Rules/Functions/ExistingClassesInClosureTypehintsRule.php +++ b/src/Rules/Functions/ExistingClassesInClosureTypehintsRule.php @@ -33,7 +33,9 @@ public function processNode(Node $node, Scope $scope): array $node->getReturnType(), 'Parameter $%s of anonymous function has invalid type %s.', 'Anonymous function has invalid return type %s.', - 'Anonymous function uses native union types but they\'re supported only on PHP 8.0 and later.' + 'Anonymous function uses native union types but they\'re supported only on PHP 8.0 and later.', + 'Parameter $%s of anonymous function has unresolvable native type.', + 'Anonymous function has unresolvable native return type.' ); } diff --git a/src/Rules/Functions/ExistingClassesInTypehintsRule.php b/src/Rules/Functions/ExistingClassesInTypehintsRule.php index 04256e7074..4acd9bc82e 100644 --- a/src/Rules/Functions/ExistingClassesInTypehintsRule.php +++ b/src/Rules/Functions/ExistingClassesInTypehintsRule.php @@ -47,7 +47,15 @@ public function processNode(Node $node, Scope $scope): array $functionName ), sprintf('Function %s() uses native union types but they\'re supported only on PHP 8.0 and later.', $functionName), - sprintf('Template type %%s of function %s() is not referenced in a parameter.', $functionName) + sprintf('Template type %%s of function %s() is not referenced in a parameter.', $functionName), + sprintf( + 'Parameter $%%s of function %s() has unresolvable native type.', + $functionName + ), + sprintf( + 'Function %s() has unresolvable native return type.', + $functionName + ), ); } diff --git a/src/Rules/Methods/ExistingClassesInTypehintsRule.php b/src/Rules/Methods/ExistingClassesInTypehintsRule.php index 7632ef1489..eabad466c5 100644 --- a/src/Rules/Methods/ExistingClassesInTypehintsRule.php +++ b/src/Rules/Methods/ExistingClassesInTypehintsRule.php @@ -54,7 +54,17 @@ public function processNode(Node $node, Scope $scope): array $methodName ), sprintf('Method %s::%s() uses native union types but they\'re supported only on PHP 8.0 and later.', $className, $methodName), - sprintf('Template type %%s of method %s::%s() is not referenced in a parameter.', $className, $methodName) + sprintf('Template type %%s of method %s::%s() is not referenced in a parameter.', $className, $methodName), + sprintf( + 'Parameter $%%s of method %s::%s() has unresolvable native type.', + $className, + $methodName + ), + sprintf( + 'Method %s::%s() has unresolvable native return type.', + $className, + $methodName + ), ); } diff --git a/src/Rules/Properties/ExistingClassesInPropertiesRule.php b/src/Rules/Properties/ExistingClassesInPropertiesRule.php index 6a4c1f1cf1..df06ed3f0c 100644 --- a/src/Rules/Properties/ExistingClassesInPropertiesRule.php +++ b/src/Rules/Properties/ExistingClassesInPropertiesRule.php @@ -5,9 +5,11 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; use PHPStan\Node\ClassPropertyNode; +use PHPStan\Php\PhpVersion; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\ClassCaseSensitivityCheck; use PHPStan\Rules\ClassNameNodePair; +use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper; use PHPStan\Rules\RuleErrorBuilder; /** @@ -20,6 +22,10 @@ class ExistingClassesInPropertiesRule implements \PHPStan\Rules\Rule private \PHPStan\Rules\ClassCaseSensitivityCheck $classCaseSensitivityCheck; + private UnresolvableTypeHelper $unresolvableTypeHelper; + + private PhpVersion $phpVersion; + private bool $checkClassCaseSensitivity; private bool $checkThisOnly; @@ -27,12 +33,16 @@ class ExistingClassesInPropertiesRule implements \PHPStan\Rules\Rule public function __construct( ReflectionProvider $reflectionProvider, ClassCaseSensitivityCheck $classCaseSensitivityCheck, + UnresolvableTypeHelper $unresolvableTypeHelper, + PhpVersion $phpVersion, bool $checkClassCaseSensitivity, bool $checkThisOnly ) { $this->reflectionProvider = $reflectionProvider; $this->classCaseSensitivityCheck = $classCaseSensitivityCheck; + $this->unresolvableTypeHelper = $unresolvableTypeHelper; + $this->phpVersion = $phpVersion; $this->checkClassCaseSensitivity = $checkClassCaseSensitivity; $this->checkThisOnly = $checkThisOnly; } @@ -89,6 +99,17 @@ public function processNode(Node $node, Scope $scope): array ); } + if ( + $this->phpVersion->supportsPureIntersectionTypes() + && $this->unresolvableTypeHelper->containsUnresolvableType($propertyReflection->getNativeType()) + ) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Property %s::$%s has unresolvable native type.', + $propertyReflection->getDeclaringClass()->getDisplayName(), + $node->getName() + ))->build(); + } + return $errors; } diff --git a/src/Type/ParserNodeTypeToPHPStanType.php b/src/Type/ParserNodeTypeToPHPStanType.php index e0f6c58ed3..4b07cefe92 100644 --- a/src/Type/ParserNodeTypeToPHPStanType.php +++ b/src/Type/ParserNodeTypeToPHPStanType.php @@ -49,7 +49,12 @@ public static function resolve($type, ?ClassReflection $classReflection): Type } elseif ($type instanceof \PhpParser\Node\IntersectionType) { $types = []; foreach ($type->types as $intersectionTypeType) { - $types[] = self::resolve($intersectionTypeType, $classReflection); + $innerType = self::resolve($intersectionTypeType, $classReflection); + if (!$innerType instanceof ObjectType) { + return new NeverType(); + } + + $types[] = $innerType; } return TypeCombinator::intersect(...$types); diff --git a/src/Type/TypehintHelper.php b/src/Type/TypehintHelper.php index 368fc118aa..d94bcaf8b5 100644 --- a/src/Type/TypehintHelper.php +++ b/src/Type/TypehintHelper.php @@ -89,11 +89,17 @@ public static function decideTypeFromReflection( } if ($reflectionType instanceof ReflectionIntersectionType) { - $type = TypeCombinator::intersect(...array_map(static function (ReflectionType $type) use ($selfClass): Type { - return self::decideTypeFromReflection($type, null, $selfClass, false); - }, $reflectionType->getTypes())); + $types = []; + foreach ($reflectionType->getTypes() as $innerReflectionType) { + $innerType = self::decideTypeFromReflection($innerReflectionType, null, $selfClass, false); + if (!$innerType instanceof ObjectType) { + return new NeverType(); + } - return self::decideType($type, $phpDocType); + $types[] = $innerType; + } + + return self::decideType(TypeCombinator::intersect(...$types), $phpDocType); } if (!$reflectionType instanceof ReflectionNamedType) { diff --git a/tests/PHPStan/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRuleTest.php b/tests/PHPStan/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRuleTest.php index 20fac6acad..2d77a54fc3 100644 --- a/tests/PHPStan/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ExistingClassesInArrowFunctionTypehintsRuleTest.php @@ -5,6 +5,7 @@ use PHPStan\Php\PhpVersion; use PHPStan\Rules\ClassCaseSensitivityCheck; use PHPStan\Rules\FunctionDefinitionCheck; +use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper; /** * @extends \PHPStan\Testing\RuleTestCase @@ -18,7 +19,7 @@ class ExistingClassesInArrowFunctionTypehintsRuleTest extends \PHPStan\Testing\R protected function getRule(): \PHPStan\Rules\Rule { $broker = $this->createReflectionProvider(); - return new ExistingClassesInArrowFunctionTypehintsRule(new FunctionDefinitionCheck($broker, new ClassCaseSensitivityCheck($broker, true), new PhpVersion($this->phpVersionId), true, false)); + return new ExistingClassesInArrowFunctionTypehintsRule(new FunctionDefinitionCheck($broker, new ClassCaseSensitivityCheck($broker, true), new UnresolvableTypeHelper(), new PhpVersion($this->phpVersionId), true, false)); } public function testRule(): void @@ -118,4 +119,48 @@ public function testRequiredParameterAfterOptional(int $phpVersionId, array $err $this->analyse([__DIR__ . '/data/required-parameter-after-optional-arrow.php'], $errors); } + public function dataIntersectionTypes(): array + { + return [ + [80000, []], + [ + 80100, + [ + [ + 'Parameter $a of anonymous function has unresolvable native type.', + 27, + ], + [ + 'Anonymous function has unresolvable native return type.', + 27, + ], + [ + 'Parameter $a of anonymous function has unresolvable native type.', + 29, + ], + [ + 'Anonymous function has unresolvable native return type.', + 29, + ], + ], + ], + ]; + } + + /** + * @dataProvider dataIntersectionTypes + * @param int $phpVersion + * @param mixed[] $errors + */ + public function testIntersectionTypes(int $phpVersion, array $errors): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + + $this->phpVersionId = $phpVersion; + + $this->analyse([__DIR__ . '/data/arrow-function-intersection-types.php'], $errors); + } + } diff --git a/tests/PHPStan/Rules/Functions/ExistingClassesInClosureTypehintsRuleTest.php b/tests/PHPStan/Rules/Functions/ExistingClassesInClosureTypehintsRuleTest.php index 9f4d2fb267..f6a4e6092b 100644 --- a/tests/PHPStan/Rules/Functions/ExistingClassesInClosureTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ExistingClassesInClosureTypehintsRuleTest.php @@ -5,6 +5,7 @@ use PHPStan\Php\PhpVersion; use PHPStan\Rules\ClassCaseSensitivityCheck; use PHPStan\Rules\FunctionDefinitionCheck; +use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper; /** * @extends \PHPStan\Testing\RuleTestCase @@ -18,7 +19,7 @@ class ExistingClassesInClosureTypehintsRuleTest extends \PHPStan\Testing\RuleTes protected function getRule(): \PHPStan\Rules\Rule { $broker = $this->createReflectionProvider(); - return new ExistingClassesInClosureTypehintsRule(new FunctionDefinitionCheck($broker, new ClassCaseSensitivityCheck($broker, true), new PhpVersion($this->phpVersionId), true, false)); + return new ExistingClassesInClosureTypehintsRule(new FunctionDefinitionCheck($broker, new ClassCaseSensitivityCheck($broker, true), new UnresolvableTypeHelper(), new PhpVersion($this->phpVersionId), true, false)); } public function testExistingClassInTypehint(): void @@ -162,4 +163,48 @@ public function testRequiredParameterAfterOptional(int $phpVersionId, array $err $this->analyse([__DIR__ . '/data/required-parameter-after-optional-closures.php'], $errors); } + public function dataIntersectionTypes(): array + { + return [ + [80000, []], + [ + 80100, + [ + [ + 'Parameter $a of anonymous function has unresolvable native type.', + 30, + ], + [ + 'Anonymous function has unresolvable native return type.', + 30, + ], + [ + 'Parameter $a of anonymous function has unresolvable native type.', + 35, + ], + [ + 'Anonymous function has unresolvable native return type.', + 35, + ], + ], + ], + ]; + } + + /** + * @dataProvider dataIntersectionTypes + * @param int $phpVersion + * @param mixed[] $errors + */ + public function testIntersectionTypes(int $phpVersion, array $errors): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + + $this->phpVersionId = $phpVersion; + + $this->analyse([__DIR__ . '/data/closure-intersection-types.php'], $errors); + } + } diff --git a/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php b/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php index efe0695a38..d2cbd941fa 100644 --- a/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ExistingClassesInTypehintsRuleTest.php @@ -5,6 +5,7 @@ use PHPStan\Php\PhpVersion; use PHPStan\Rules\ClassCaseSensitivityCheck; use PHPStan\Rules\FunctionDefinitionCheck; +use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper; use const PHP_VERSION_ID; /** @@ -19,7 +20,7 @@ class ExistingClassesInTypehintsRuleTest extends \PHPStan\Testing\RuleTestCase protected function getRule(): \PHPStan\Rules\Rule { $broker = $this->createReflectionProvider(); - return new ExistingClassesInTypehintsRule(new FunctionDefinitionCheck($broker, new ClassCaseSensitivityCheck($broker, true), new PhpVersion($this->phpVersionId), true, false)); + return new ExistingClassesInTypehintsRule(new FunctionDefinitionCheck($broker, new ClassCaseSensitivityCheck($broker, true), new UnresolvableTypeHelper(), new PhpVersion($this->phpVersionId), true, false)); } public function testExistingClassInTypehint(): void @@ -241,4 +242,48 @@ public function testRequiredParameterAfterOptional(int $phpVersionId, array $err $this->analyse([__DIR__ . '/data/required-parameter-after-optional.php'], $errors); } + public function dataIntersectionTypes(): array + { + return [ + [80000, []], + [ + 80100, + [ + [ + 'Parameter $a of function FunctionIntersectionTypes\doBar() has unresolvable native type.', + 30, + ], + [ + 'Function FunctionIntersectionTypes\doBar() has unresolvable native return type.', + 30, + ], + [ + 'Parameter $a of function FunctionIntersectionTypes\doBaz() has unresolvable native type.', + 35, + ], + [ + 'Function FunctionIntersectionTypes\doBaz() has unresolvable native return type.', + 35, + ], + ], + ], + ]; + } + + /** + * @dataProvider dataIntersectionTypes + * @param int $phpVersion + * @param mixed[] $errors + */ + public function testIntersectionTypes(int $phpVersion, array $errors): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + + $this->phpVersionId = $phpVersion; + + $this->analyse([__DIR__ . '/data/intersection-types.php'], $errors); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/arrow-function-intersection-types.php b/tests/PHPStan/Rules/Functions/data/arrow-function-intersection-types.php new file mode 100644 index 0000000000..246d426fb3 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/arrow-function-intersection-types.php @@ -0,0 +1,29 @@ += 8.1 + +namespace ArrowFunctionIntersectionTypes; + +interface Foo +{ + +} + +interface Bar +{ + +} + +class Lorem +{ + +} + +class Ipsum +{ + +} + +fn(Foo&Bar $a): Foo&Bar => 1; + +fn(Lorem&Ipsum $a): Lorem&Ipsum => 2; + +fn(int&mixed $a): int&mixed => 3; diff --git a/tests/PHPStan/Rules/Functions/data/closure-intersection-types.php b/tests/PHPStan/Rules/Functions/data/closure-intersection-types.php new file mode 100644 index 0000000000..90051d4342 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/closure-intersection-types.php @@ -0,0 +1,38 @@ += 8.1 + +namespace ClosureIntersectionTypes; + +interface Foo +{ + +} + +interface Bar +{ + +} + +class Lorem +{ + +} + +class Ipsum +{ + +} + +function(Foo&Bar $a): Foo&Bar +{ + +}; + +function(Lorem&Ipsum $a): Lorem&Ipsum +{ + +}; + +function(int&mixed $a): int&mixed +{ + +}; diff --git a/tests/PHPStan/Rules/Functions/data/intersection-types.php b/tests/PHPStan/Rules/Functions/data/intersection-types.php new file mode 100644 index 0000000000..ab7e1228fc --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/intersection-types.php @@ -0,0 +1,38 @@ += 8.1 + +namespace FunctionIntersectionTypes; + +interface Foo +{ + +} + +interface Bar +{ + +} + +class Lorem +{ + +} + +class Ipsum +{ + +} + +function doFoo(Foo&Bar $a): Foo&Bar +{ + +} + +function doBar(Lorem&Ipsum $a): Lorem&Ipsum +{ + +} + +function doBaz(int&mixed $a): int&mixed +{ + +} diff --git a/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php b/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php index 6a414bec53..5680bd32a9 100644 --- a/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php @@ -5,6 +5,7 @@ use PHPStan\Php\PhpVersion; use PHPStan\Rules\ClassCaseSensitivityCheck; use PHPStan\Rules\FunctionDefinitionCheck; +use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper; use const PHP_VERSION_ID; /** @@ -19,7 +20,7 @@ class ExistingClassesInTypehintsRuleTest extends \PHPStan\Testing\RuleTestCase protected function getRule(): \PHPStan\Rules\Rule { $broker = $this->createReflectionProvider(); - return new ExistingClassesInTypehintsRule(new FunctionDefinitionCheck($broker, new ClassCaseSensitivityCheck($broker, true), new PhpVersion($this->phpVersionId), true, false)); + return new ExistingClassesInTypehintsRule(new FunctionDefinitionCheck($broker, new ClassCaseSensitivityCheck($broker, true), new UnresolvableTypeHelper(), new PhpVersion($this->phpVersionId), true, false)); } public function testExistingClassInTypehint(): void @@ -248,4 +249,48 @@ public function testBug4641(): void ]); } + public function dataIntersectionTypes(): array + { + return [ + [80000, []], + [ + 80100, + [ + [ + 'Parameter $a of method MethodIntersectionTypes\Foo::doBar() has unresolvable native type.', + 33, + ], + [ + 'Method MethodIntersectionTypes\Foo::doBar() has unresolvable native return type.', + 33, + ], + [ + 'Parameter $a of method MethodIntersectionTypes\Foo::doBaz() has unresolvable native type.', + 38, + ], + [ + 'Method MethodIntersectionTypes\Foo::doBaz() has unresolvable native return type.', + 38, + ], + ], + ], + ]; + } + + /** + * @dataProvider dataIntersectionTypes + * @param int $phpVersion + * @param mixed[] $errors + */ + public function testIntersectionTypes(int $phpVersion, array $errors): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + + $this->phpVersionId = $phpVersion; + + $this->analyse([__DIR__ . '/data/intersection-types.php'], $errors); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/intersection-types.php b/tests/PHPStan/Rules/Methods/data/intersection-types.php new file mode 100644 index 0000000000..6a3987d9b6 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/intersection-types.php @@ -0,0 +1,43 @@ += 8.1 + +namespace MethodIntersectionTypes; + +interface Foo +{ + +} + +interface Bar +{ + +} + +class Lorem +{ + +} + +class Ipsum +{ + +} + +class Foo +{ + + public function doFoo(Foo&Bar $a): Foo&Bar + { + + } + + public function doBar(Lorem&Ipsum $a): Lorem&Ipsum + { + + } + + public function doBaz(int&mixed $a): int&mixed + { + + } + +} diff --git a/tests/PHPStan/Rules/Properties/ExistingClassesInPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/ExistingClassesInPropertiesRuleTest.php index 86d1bee508..f4f17be99c 100644 --- a/tests/PHPStan/Rules/Properties/ExistingClassesInPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/ExistingClassesInPropertiesRuleTest.php @@ -2,8 +2,11 @@ namespace PHPStan\Rules\Properties; +use PHPStan\Php\PhpVersion; use PHPStan\Rules\ClassCaseSensitivityCheck; +use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper; use PHPStan\Rules\Rule; +use const PHP_VERSION_ID; /** * @extends \PHPStan\Testing\RuleTestCase @@ -11,12 +14,17 @@ class ExistingClassesInPropertiesRuleTest extends \PHPStan\Testing\RuleTestCase { + /** @var int */ + private $phpVersion = PHP_VERSION_ID; + protected function getRule(): Rule { $broker = $this->createReflectionProvider(); return new ExistingClassesInPropertiesRule( $broker, new ClassCaseSensitivityCheck($broker, true), + new UnresolvableTypeHelper(), + new PhpVersion($this->phpVersion), true, false ); @@ -138,4 +146,40 @@ public function testPromotedProperties(): void ]); } + public function dataIntersectionTypes(): array + { + return [ + [80000, []], + [ + 80100, + [ + [ + 'Property PropertyIntersectionTypes\Test::$prop2 has unresolvable native type.', + 30, + ], + [ + 'Property PropertyIntersectionTypes\Test::$prop3 has unresolvable native type.', + 32, + ], + ], + ], + ]; + } + + /** + * @dataProvider dataIntersectionTypes + * @param int $phpVersion + * @param mixed[] $errors + */ + public function testIntersectionTypes(int $phpVersion, array $errors): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + + $this->phpVersion = $phpVersion; + + $this->analyse([__DIR__ . '/data/intersection-types.php'], $errors); + } + } diff --git a/tests/PHPStan/Rules/Properties/data/intersection-types.php b/tests/PHPStan/Rules/Properties/data/intersection-types.php new file mode 100644 index 0000000000..5afb8fae08 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/intersection-types.php @@ -0,0 +1,34 @@ += 8.1 + +namespace PropertyIntersectionTypes; + +interface Foo +{ + +} + +interface Bar +{ + +} + +class Lorem +{ + +} + +class Ipsum +{ + +} + +class Test +{ + + private Foo&Bar $prop1; + + private Lorem&Ipsum $prop2; + + private int&mixed $prop3; + +} From 473c5363b0838933db5e4116b524b0a2af84ace9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 5 Nov 2021 21:59:29 +0100 Subject: [PATCH 0540/1284] Fixes --- Makefile | 1 + src/Rules/Functions/ExistingClassesInTypehintsRule.php | 2 +- src/Rules/Methods/ExistingClassesInTypehintsRule.php | 2 +- tests/PHPStan/Rules/Properties/data/intersection-types.php | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 6680f57e3e..0dba251bfa 100644 --- a/Makefile +++ b/Makefile @@ -44,6 +44,7 @@ lint: --exclude tests/PHPStan/Rules/Properties/data/read-only-property.php \ --exclude tests/PHPStan/Rules/Properties/data/overriding-property.php \ --exclude tests/PHPStan/Rules/Constants/data/overriding-final-constant.php \ + --exclude tests/PHPStan/Rules/Properties/data/intersection-types.php \ src tests compiler/src cs: diff --git a/src/Rules/Functions/ExistingClassesInTypehintsRule.php b/src/Rules/Functions/ExistingClassesInTypehintsRule.php index 4acd9bc82e..3bfb4e6968 100644 --- a/src/Rules/Functions/ExistingClassesInTypehintsRule.php +++ b/src/Rules/Functions/ExistingClassesInTypehintsRule.php @@ -55,7 +55,7 @@ public function processNode(Node $node, Scope $scope): array sprintf( 'Function %s() has unresolvable native return type.', $functionName - ), + ) ); } diff --git a/src/Rules/Methods/ExistingClassesInTypehintsRule.php b/src/Rules/Methods/ExistingClassesInTypehintsRule.php index eabad466c5..3729dfafd9 100644 --- a/src/Rules/Methods/ExistingClassesInTypehintsRule.php +++ b/src/Rules/Methods/ExistingClassesInTypehintsRule.php @@ -64,7 +64,7 @@ public function processNode(Node $node, Scope $scope): array 'Method %s::%s() has unresolvable native return type.', $className, $methodName - ), + ) ); } diff --git a/tests/PHPStan/Rules/Properties/data/intersection-types.php b/tests/PHPStan/Rules/Properties/data/intersection-types.php index 5afb8fae08..715551fbcf 100644 --- a/tests/PHPStan/Rules/Properties/data/intersection-types.php +++ b/tests/PHPStan/Rules/Properties/data/intersection-types.php @@ -1,4 +1,4 @@ -= 8.1 + Date: Thu, 4 Nov 2021 15:17:58 -0500 Subject: [PATCH 0541/1284] call_user_func_array support named arguments --- resources/functionMap_php80delta.php | 1 + .../CallToFunctionParametersRuleTest.php | 15 +++++++++++++++ .../Rules/Functions/data/call-user-func-array.php | 3 +++ 3 files changed, 19 insertions(+) create mode 100644 tests/PHPStan/Rules/Functions/data/call-user-func-array.php diff --git a/resources/functionMap_php80delta.php b/resources/functionMap_php80delta.php index 6353bcee2e..e49e72ee28 100644 --- a/resources/functionMap_php80delta.php +++ b/resources/functionMap_php80delta.php @@ -25,6 +25,7 @@ 'bcdiv' => ['string', 'dividend'=>'string', 'divisor'=>'string', 'scale='=>'int'], 'bcmod' => ['string', 'dividend'=>'string', 'divisor'=>'string', 'scale='=>'int'], 'bcpowmod' => ['string', 'base'=>'string', 'exponent'=>'string', 'modulus'=>'string', 'scale='=>'int'], + 'call_user_func_array' => ['mixed', 'function'=>'callable', 'parameters'=>'array'], 'com_load_typelib' => ['bool', 'typelib_name'=>'string', 'case_insensitive='=>'true'], 'count_chars' => ['array|string', 'input'=>'string', 'mode='=>'int'], 'date_add' => ['DateTime', 'object'=>'DateTime', 'interval'=>'DateInterval'], diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 4ac31e0499..e0b0bbf1f4 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -905,4 +905,19 @@ public function testBug5861(): void $this->analyse([__DIR__ . '/data/bug-5861.php'], []); } + public function testCallUserFuncArray(): void + { + if (PHP_VERSION_ID >= 80000) { + $errors = []; + } else { + $errors = [ + [ + 'Parameter #2 $parameters of function call_user_func_array expects array, array> given.', + 3, + ], + ]; + } + $this->analyse([__DIR__ . '/data/call-user-func-array.php'], $errors); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/call-user-func-array.php b/tests/PHPStan/Rules/Functions/data/call-user-func-array.php new file mode 100644 index 0000000000..dd9f615517 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/call-user-func-array.php @@ -0,0 +1,3 @@ + ['bar' => 2]]); From 5a907319eda0dcd86b4e130867f820fa30185095 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Thu, 4 Nov 2021 15:22:00 +0000 Subject: [PATCH 0542/1284] phpversion() does not return false when no arguments are given --- resources/functionMap.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index b63ddbbb79..51a7c99426 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -8741,7 +8741,8 @@ 'phpdbg_prompt' => ['', 'prompt'=>'string'], 'phpdbg_start_oplog' => [''], 'phpinfo' => ['bool', 'what='=>'int'], -'phpversion' => ['string|false', 'extension='=>'string'], +'phpversion' => ['string'], +'phpversion\'1' => ['string|false', 'extension'=>'string'], 'pht\AtomicInteger::__construct' => ['void', 'value='=>'int'], 'pht\AtomicInteger::dec' => ['void'], 'pht\AtomicInteger::get' => ['int'], From e3a0e0d1b968c4338d2a50d76562c49e49521565 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 6 Nov 2021 16:33:44 +0100 Subject: [PATCH 0543/1284] ReflectionEnum runtime stubs (PHP 8.0+) --- src/Command/CommandHelper.php | 8 +++++ .../ClassBlacklistReflectionProvider.php | 12 ++++++- src/Testing/PHPStanTestCase.php | 8 +++++ stubs/runtime/Enum/BackedEnum.php | 14 ++++++++ stubs/runtime/Enum/ReflectionEnum.php | 32 +++++++++++++++++++ .../runtime/Enum/ReflectionEnumBackedCase.php | 14 ++++++++ stubs/runtime/Enum/ReflectionEnumUnitCase.php | 25 +++++++++++++++ stubs/runtime/Enum/UnitEnum.php | 15 +++++++++ 8 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 stubs/runtime/Enum/BackedEnum.php create mode 100644 stubs/runtime/Enum/ReflectionEnum.php create mode 100644 stubs/runtime/Enum/ReflectionEnumBackedCase.php create mode 100644 stubs/runtime/Enum/ReflectionEnumUnitCase.php create mode 100644 stubs/runtime/Enum/UnitEnum.php diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php index 6eecec6300..9039c480f0 100644 --- a/src/Command/CommandHelper.php +++ b/src/Command/CommandHelper.php @@ -326,6 +326,14 @@ public static function begin( self::executeBootstrapFile($bootstrapFileFromArray, $container, $errorOutput, $debugEnabled); } + if (PHP_VERSION_ID >= 80000) { + require_once __DIR__ . '/../../stubs/runtime/Enum/UnitEnum.php'; + require_once __DIR__ . '/../../stubs/runtime/Enum/BackedEnum.php'; + require_once __DIR__ . '/../../stubs/runtime/Enum/ReflectionEnum.php'; + require_once __DIR__ . '/../../stubs/runtime/Enum/ReflectionEnumUnitCase.php'; + require_once __DIR__ . '/../../stubs/runtime/Enum/ReflectionEnumBackedCase.php'; + } + foreach ($container->getParameter('scanFiles') as $scannedFile) { if (is_file($scannedFile)) { continue; diff --git a/src/Reflection/ReflectionProvider/ClassBlacklistReflectionProvider.php b/src/Reflection/ReflectionProvider/ClassBlacklistReflectionProvider.php index 1d6c2ce2e9..a53a309438 100644 --- a/src/Reflection/ReflectionProvider/ClassBlacklistReflectionProvider.php +++ b/src/Reflection/ReflectionProvider/ClassBlacklistReflectionProvider.php @@ -79,7 +79,17 @@ private function isClassBlacklisted(string $className): bool if (!class_exists($className, false)) { return true; } - if (in_array(strtolower($className), ['reflectionuniontype', 'attribute', 'returntypewillchange', 'reflectionintersectiontype'], true)) { + if (in_array(strtolower($className), [ + 'reflectionuniontype', + 'attribute', + 'returntypewillchange', + 'reflectionintersectiontype', + 'unitenum', + 'backedenum', + 'reflectionenum', + 'reflectionenumunitcase', + 'reflectionenumbackedcase', + ], true)) { return true; } $reflection = new \ReflectionClass($className); diff --git a/src/Testing/PHPStanTestCase.php b/src/Testing/PHPStanTestCase.php index b38b0fd015..2dbf880070 100644 --- a/src/Testing/PHPStanTestCase.php +++ b/src/Testing/PHPStanTestCase.php @@ -65,6 +65,14 @@ public static function getContainer(): Container require_once $file; })($bootstrapFile); } + + if (PHP_VERSION_ID >= 80000) { + require_once __DIR__ . '/../../stubs/runtime/Enum/UnitEnum.php'; + require_once __DIR__ . '/../../stubs/runtime/Enum/BackedEnum.php'; + require_once __DIR__ . '/../../stubs/runtime/Enum/ReflectionEnum.php'; + require_once __DIR__ . '/../../stubs/runtime/Enum/ReflectionEnumUnitCase.php'; + require_once __DIR__ . '/../../stubs/runtime/Enum/ReflectionEnumBackedCase.php'; + } } return self::$containers[$cacheKey]; diff --git a/stubs/runtime/Enum/BackedEnum.php b/stubs/runtime/Enum/BackedEnum.php new file mode 100644 index 0000000000..c830ea24ff --- /dev/null +++ b/stubs/runtime/Enum/BackedEnum.php @@ -0,0 +1,14 @@ + Date: Sat, 6 Nov 2021 16:54:25 +0100 Subject: [PATCH 0544/1284] Fix chaining nullsafe operator --- src/Analyser/NullsafeOperatorHelper.php | 15 ++++++++ .../NonexistentOffsetInArrayDimFetchCheck.php | 2 +- .../NonexistentOffsetInArrayDimFetchRule.php | 2 +- .../Arrays/OffsetAccessAssignmentRule.php | 2 +- src/Rules/Classes/ClassConstantRule.php | 2 +- src/Rules/Functions/CallCallablesRule.php | 2 +- src/Rules/Methods/CallMethodsRule.php | 3 +- src/Rules/Methods/CallStaticMethodsRule.php | 2 +- ...oMethodStatementWithoutSideEffectsRule.php | 2 +- ...cMethodStatementWithoutSideEffectsRule.php | 2 +- src/Rules/Properties/AccessPropertiesRule.php | 2 +- .../Properties/AccessStaticPropertiesRule.php | 2 +- .../Rules/Methods/CallMethodsRuleTest.php | 25 +++++++++++++ tests/PHPStan/Rules/Methods/data/bug-5868.php | 30 +++++++++++++++ .../Properties/AccessPropertiesRuleTest.php | 28 ++++++++++++++ .../Rules/Properties/data/bug-5868.php | 37 +++++++++++++++++++ 16 files changed, 147 insertions(+), 11 deletions(-) create mode 100644 tests/PHPStan/Rules/Methods/data/bug-5868.php create mode 100644 tests/PHPStan/Rules/Properties/data/bug-5868.php diff --git a/src/Analyser/NullsafeOperatorHelper.php b/src/Analyser/NullsafeOperatorHelper.php index a74dd6e426..9b34346264 100644 --- a/src/Analyser/NullsafeOperatorHelper.php +++ b/src/Analyser/NullsafeOperatorHelper.php @@ -3,10 +3,25 @@ namespace PHPStan\Analyser; use PhpParser\Node\Expr; +use PHPStan\Type\TypeCombinator; class NullsafeOperatorHelper { + public static function getNullsafeShortcircuitedExprRespectingScope(Scope $scope, Expr $expr): Expr + { + if (!TypeCombinator::containsNull($scope->getType($expr))) { + // We're in most likely in context of a null-safe operator ($scope->moreSpecificType is defined for $expr) + // Modifying the expression would not bring any value or worse ruin the context information + return $expr; + } + + return self::getNullsafeShortcircuitedExpr($expr); + } + + /** + * @internal Use NullsafeOperatorHelper::getNullsafeShortcircuitedExprRespectingScope + */ public static function getNullsafeShortcircuitedExpr(Expr $expr): Expr { if ($expr instanceof Expr\NullsafeMethodCall) { diff --git a/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchCheck.php b/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchCheck.php index 84d91b14d0..4005b2041e 100644 --- a/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchCheck.php +++ b/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchCheck.php @@ -44,7 +44,7 @@ public function check( { $typeResult = $this->ruleLevelHelper->findTypeToCheck( $scope, - NullsafeOperatorHelper::getNullsafeShortcircuitedExpr($var), + NullsafeOperatorHelper::getNullsafeShortcircuitedExprRespectingScope($scope, $var), $unknownClassPattern, static function (Type $type) use ($dimType): bool { return $type->hasOffsetValueType($dimType)->yes(); diff --git a/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchRule.php b/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchRule.php index 105f846047..3c1f872262 100644 --- a/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchRule.php +++ b/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchRule.php @@ -51,7 +51,7 @@ public function processNode(\PhpParser\Node $node, Scope $scope): array $isOffsetAccessibleTypeResult = $this->ruleLevelHelper->findTypeToCheck( $scope, - NullsafeOperatorHelper::getNullsafeShortcircuitedExpr($node->var), + NullsafeOperatorHelper::getNullsafeShortcircuitedExprRespectingScope($scope, $node->var), $unknownClassPattern, static function (Type $type): bool { return $type->isOffsetAccessible()->yes(); diff --git a/src/Rules/Arrays/OffsetAccessAssignmentRule.php b/src/Rules/Arrays/OffsetAccessAssignmentRule.php index f84b52bfd9..8d5137b0fc 100644 --- a/src/Rules/Arrays/OffsetAccessAssignmentRule.php +++ b/src/Rules/Arrays/OffsetAccessAssignmentRule.php @@ -42,7 +42,7 @@ public function processNode(\PhpParser\Node $node, Scope $scope): array $varTypeResult = $this->ruleLevelHelper->findTypeToCheck( $scope, - NullsafeOperatorHelper::getNullsafeShortcircuitedExpr($node->var), + NullsafeOperatorHelper::getNullsafeShortcircuitedExprRespectingScope($scope, $node->var), '', static function (Type $varType) use ($potentialDimType): bool { $arrayDimType = $varType->setOffsetValueType($potentialDimType, new MixedType()); diff --git a/src/Rules/Classes/ClassConstantRule.php b/src/Rules/Classes/ClassConstantRule.php index f9c8bdc908..23e404fd5b 100644 --- a/src/Rules/Classes/ClassConstantRule.php +++ b/src/Rules/Classes/ClassConstantRule.php @@ -119,7 +119,7 @@ public function processNode(Node $node, Scope $scope): array } else { $classTypeResult = $this->ruleLevelHelper->findTypeToCheck( $scope, - NullsafeOperatorHelper::getNullsafeShortcircuitedExpr($class), + NullsafeOperatorHelper::getNullsafeShortcircuitedExprRespectingScope($scope, $class), sprintf('Access to constant %s on an unknown class %%s.', SprintfHelper::escapeFormatString($constantName)), static function (Type $type) use ($constantName): bool { return $type->canAccessConstants()->yes() && $type->hasConstant($constantName)->yes(); diff --git a/src/Rules/Functions/CallCallablesRule.php b/src/Rules/Functions/CallCallablesRule.php index d1a51c1d0e..81ca54f9b9 100644 --- a/src/Rules/Functions/CallCallablesRule.php +++ b/src/Rules/Functions/CallCallablesRule.php @@ -54,7 +54,7 @@ public function processNode( $typeResult = $this->ruleLevelHelper->findTypeToCheck( $scope, - NullsafeOperatorHelper::getNullsafeShortcircuitedExpr($node->name), + NullsafeOperatorHelper::getNullsafeShortcircuitedExprRespectingScope($scope, $node->name), 'Invoking callable on an unknown class %s.', static function (Type $type): bool { return $type->isCallable()->yes(); diff --git a/src/Rules/Methods/CallMethodsRule.php b/src/Rules/Methods/CallMethodsRule.php index b6c9c6953a..399dbed758 100644 --- a/src/Rules/Methods/CallMethodsRule.php +++ b/src/Rules/Methods/CallMethodsRule.php @@ -62,12 +62,13 @@ public function processNode(Node $node, Scope $scope): array $typeResult = $this->ruleLevelHelper->findTypeToCheck( $scope, - NullsafeOperatorHelper::getNullsafeShortcircuitedExpr($node->var), + NullsafeOperatorHelper::getNullsafeShortcircuitedExprRespectingScope($scope, $node->var), sprintf('Call to method %s() on an unknown class %%s.', SprintfHelper::escapeFormatString($name)), static function (Type $type) use ($name): bool { return $type->canCallMethods()->yes() && $type->hasMethod($name)->yes(); } ); + $type = $typeResult->getType(); if ($type instanceof ErrorType) { return $typeResult->getUnknownClassErrors(); diff --git a/src/Rules/Methods/CallStaticMethodsRule.php b/src/Rules/Methods/CallStaticMethodsRule.php index d535942bde..70f942ac6c 100644 --- a/src/Rules/Methods/CallStaticMethodsRule.php +++ b/src/Rules/Methods/CallStaticMethodsRule.php @@ -151,7 +151,7 @@ public function processNode(Node $node, Scope $scope): array } else { $classTypeResult = $this->ruleLevelHelper->findTypeToCheck( $scope, - NullsafeOperatorHelper::getNullsafeShortcircuitedExpr($class), + NullsafeOperatorHelper::getNullsafeShortcircuitedExprRespectingScope($scope, $class), sprintf('Call to static method %s() on an unknown class %%s.', SprintfHelper::escapeFormatString($methodName)), static function (Type $type) use ($methodName): bool { return $type->canCallMethods()->yes() && $type->hasMethod($methodName)->yes(); diff --git a/src/Rules/Methods/CallToMethodStatementWithoutSideEffectsRule.php b/src/Rules/Methods/CallToMethodStatementWithoutSideEffectsRule.php index 9a70df628c..474910e2c6 100644 --- a/src/Rules/Methods/CallToMethodStatementWithoutSideEffectsRule.php +++ b/src/Rules/Methods/CallToMethodStatementWithoutSideEffectsRule.php @@ -47,7 +47,7 @@ public function processNode(Node $node, Scope $scope): array $typeResult = $this->ruleLevelHelper->findTypeToCheck( $scope, - NullsafeOperatorHelper::getNullsafeShortcircuitedExpr($methodCall->var), + NullsafeOperatorHelper::getNullsafeShortcircuitedExprRespectingScope($scope, $methodCall->var), '', static function (Type $type) use ($methodName): bool { return $type->canCallMethods()->yes() && $type->hasMethod($methodName)->yes(); diff --git a/src/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRule.php b/src/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRule.php index ffbc18b185..f6e68c6996 100644 --- a/src/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRule.php +++ b/src/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRule.php @@ -61,7 +61,7 @@ public function processNode(Node $node, Scope $scope): array } else { $typeResult = $this->ruleLevelHelper->findTypeToCheck( $scope, - NullsafeOperatorHelper::getNullsafeShortcircuitedExpr($staticCall->class), + NullsafeOperatorHelper::getNullsafeShortcircuitedExprRespectingScope($scope, $staticCall->class), '', static function (Type $type) use ($methodName): bool { return $type->canCallMethods()->yes() && $type->hasMethod($methodName)->yes(); diff --git a/src/Rules/Properties/AccessPropertiesRule.php b/src/Rules/Properties/AccessPropertiesRule.php index 1838be2af1..1b35c15fbb 100644 --- a/src/Rules/Properties/AccessPropertiesRule.php +++ b/src/Rules/Properties/AccessPropertiesRule.php @@ -73,7 +73,7 @@ private function processSingleProperty(Scope $scope, PropertyFetch $node, string { $typeResult = $this->ruleLevelHelper->findTypeToCheck( $scope, - NullsafeOperatorHelper::getNullsafeShortcircuitedExpr($node->var), + NullsafeOperatorHelper::getNullsafeShortcircuitedExprRespectingScope($scope, $node->var), sprintf('Access to property $%s on an unknown class %%s.', SprintfHelper::escapeFormatString($name)), static function (Type $type) use ($name): bool { return $type->canAccessProperties()->yes() && $type->hasProperty($name)->yes(); diff --git a/src/Rules/Properties/AccessStaticPropertiesRule.php b/src/Rules/Properties/AccessStaticPropertiesRule.php index b269eb5bc7..84ae44bd4c 100644 --- a/src/Rules/Properties/AccessStaticPropertiesRule.php +++ b/src/Rules/Properties/AccessStaticPropertiesRule.php @@ -147,7 +147,7 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node, } else { $classTypeResult = $this->ruleLevelHelper->findTypeToCheck( $scope, - NullsafeOperatorHelper::getNullsafeShortcircuitedExpr($node->class), + NullsafeOperatorHelper::getNullsafeShortcircuitedExprRespectingScope($scope, $node->class), sprintf('Access to static property $%s on an unknown class %%s.', SprintfHelper::escapeFormatString($name)), static function (Type $type) use ($name): bool { return $type->canAccessProperties()->yes() && $type->hasProperty($name)->yes(); diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index d03a9fbb00..b83d3930ae 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -2193,4 +2193,29 @@ public function testBug3465(): void $this->analyse([__DIR__ . '/data/bug-3465.php'], []); } + public function testBug5868(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0'); + } + + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/bug-5868.php'], [ + [ + 'Cannot call method nullable1() on Bug5868\HelloWorld|null.', + 14, + ], + [ + 'Cannot call method nullable2() on Bug5868\HelloWorld|null.', + 15, + ], + [ + 'Cannot call method nullable3() on Bug5868\HelloWorld|null.', + 16, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-5868.php b/tests/PHPStan/Rules/Methods/data/bug-5868.php new file mode 100644 index 0000000000..ea9b7ec72f --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-5868.php @@ -0,0 +1,30 @@ += 8.0 + +namespace Bug5868; + +class HelloWorld +{ + public function nullable1(): ?self + { + // OK + $tmp = $this->nullable1()?->nullable1()?->nullable2(); + $tmp = $this->nullable1()?->nullable3()->nullable2()?->nullable3()->nullable1(); + + // Error + $tmp = $this->nullable1()->nullable1()?->nullable2(); + $tmp = $this->nullable1()?->nullable1()->nullable2(); + $tmp = $this->nullable1()?->nullable3()->nullable2()->nullable3()->nullable1(); + + return $this->nullable1()?->nullable3(); + } + + public function nullable2(): ?self + { + return $this; + } + + public function nullable3(): self + { + return $this; + } +} diff --git a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php index 877e8e1fb0..62766266b8 100644 --- a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php @@ -491,4 +491,32 @@ public function testBug4808(): void $this->analyse([__DIR__ . '/data/bug-4808.php'], []); } + + public function testBug5868(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0'); + } + $this->checkThisOnly = false; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/bug-5868.php'], [ + [ + 'Cannot access property $child on Bug5868PropertyFetch\Foo|null.', + 31, + ], + [ + 'Cannot access property $child on Bug5868PropertyFetch\Child|null.', + 32, + ], + [ + 'Cannot access property $existingChild on Bug5868PropertyFetch\Child|null.', + 33, + ], + [ + 'Cannot access property $existingChild on Bug5868PropertyFetch\Child|null.', + 34, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Properties/data/bug-5868.php b/tests/PHPStan/Rules/Properties/data/bug-5868.php new file mode 100644 index 0000000000..4359f8b5bf --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/bug-5868.php @@ -0,0 +1,37 @@ += 8.0 + +namespace Bug5868PropertyFetch; + +class Child +{ + + public ?self $child; + + public self $existingChild; + +} + +class Foo +{ + public ?Child $child; +} + +class HelloWorld +{ + + function getAttributeInNode(?Foo $node): ?Child + { + // Ok + $tmp = $node?->child; + $tmp = $node?->child?->child?->child; + $tmp = $node?->child?->existingChild->child; + $tmp = $node?->child?->existingChild->child?->existingChild; + + // Errors + $tmp = $node->child; + $tmp = $node?->child->child; + $tmp = $node?->child->existingChild->child; + $tmp = $node?->child?->existingChild->child->existingChild; + } + +} From 28e8c11a2bdf11eede53a152825fd3bd7733d95d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 6 Nov 2021 22:14:57 +0100 Subject: [PATCH 0545/1284] Do not report non-existent function after function_exists() check --- conf/config.neon | 5 ++ src/Analyser/MutatingScope.php | 10 ++++ src/Analyser/Scope.php | 2 + .../CallToNonExistentFunctionRule.php | 4 ++ ...nExistsFunctionTypeSpecifyingExtension.php | 56 +++++++++++++++++++ .../CallToNonExistentFunctionRuleTest.php | 36 ++++++++++++ .../PHPStan/Rules/Functions/data/bug-3576.php | 45 +++++++++++++++ 7 files changed, 158 insertions(+) create mode 100644 src/Type/Php/FunctionExistsFunctionTypeSpecifyingExtension.php create mode 100644 tests/PHPStan/Rules/Functions/data/bug-3576.php diff --git a/conf/config.neon b/conf/config.neon index 1c48087861..c9ec011238 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1306,6 +1306,11 @@ services: tags: - phpstan.typeSpecifier.functionTypeSpecifyingExtension + - + class: PHPStan\Type\Php\FunctionExistsFunctionTypeSpecifyingExtension + tags: + - phpstan.typeSpecifier.functionTypeSpecifyingExtension + - class: PHPStan\Type\Php\InArrayFunctionTypeSpecifyingExtension tags: diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index b9bf542f8f..3d1a384a44 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -2765,6 +2765,16 @@ public function isInClassExists(string $className): bool return (new ConstantBooleanType(true))->isSuperTypeOf($this->getType($expr))->yes(); } + /** @api */ + public function isInFunctionExists(string $functionName): bool + { + $expr = new FuncCall(new FullyQualified('function_exists'), [ + new Arg(new String_(ltrim($functionName, '\\'))), + ]); + + return (new ConstantBooleanType(true))->isSuperTypeOf($this->getType($expr))->yes(); + } + /** @api */ public function enterClass(ClassReflection $classReflection): self { diff --git a/src/Analyser/Scope.php b/src/Analyser/Scope.php index bcd205a9a7..bf9a2f5e80 100644 --- a/src/Analyser/Scope.php +++ b/src/Analyser/Scope.php @@ -89,6 +89,8 @@ public function isSpecified(Expr $node): bool; public function isInClassExists(string $className): bool; + public function isInFunctionExists(string $functionName): bool; + public function isInClosureBind(): bool; public function isParameterValueNullable(Param $parameter): bool; diff --git a/src/Rules/Functions/CallToNonExistentFunctionRule.php b/src/Rules/Functions/CallToNonExistentFunctionRule.php index b4a10083f2..121402da04 100644 --- a/src/Rules/Functions/CallToNonExistentFunctionRule.php +++ b/src/Rules/Functions/CallToNonExistentFunctionRule.php @@ -39,6 +39,10 @@ public function processNode(Node $node, Scope $scope): array } if (!$this->reflectionProvider->hasFunction($node->name, $scope)) { + if ($scope->isInFunctionExists($node->name->toString())) { + return []; + } + return [ RuleErrorBuilder::message(sprintf('Function %s not found.', (string) $node->name))->discoveringSymbolsTip()->build(), ]; diff --git a/src/Type/Php/FunctionExistsFunctionTypeSpecifyingExtension.php b/src/Type/Php/FunctionExistsFunctionTypeSpecifyingExtension.php new file mode 100644 index 0000000000..32dd08b7ad --- /dev/null +++ b/src/Type/Php/FunctionExistsFunctionTypeSpecifyingExtension.php @@ -0,0 +1,56 @@ +getName() === 'function_exists' && isset($node->getArgs()[0]) && $context->truthy(); + } + + public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes + { + $argType = $scope->getType($node->getArgs()[0]->value); + if ($argType instanceof ConstantStringType) { + return $this->typeSpecifier->create( + new FuncCall(new FullyQualified('function_exists'), [ + new Arg(new String_(ltrim($argType->getValue(), '\\'))), + ]), + new ConstantBooleanType(true), + $context, + false, + $scope + ); + } + + return new SpecifiedTypes(); + } + + public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void + { + $this->typeSpecifier = $typeSpecifier; + } + +} diff --git a/tests/PHPStan/Rules/Functions/CallToNonExistentFunctionRuleTest.php b/tests/PHPStan/Rules/Functions/CallToNonExistentFunctionRuleTest.php index ef1abc2ea3..80fb0f6965 100644 --- a/tests/PHPStan/Rules/Functions/CallToNonExistentFunctionRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToNonExistentFunctionRuleTest.php @@ -121,4 +121,40 @@ public function testCreateFunctionPhp7(): void $this->analyse([__DIR__ . '/data/create_function.php'], []); } + public function testBug3576(): void + { + $this->analyse([__DIR__ . '/data/bug-3576.php'], [ + [ + 'Function bug3576 not found.', + 14, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Function bug3576 not found.', + 17, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Function bug3576 not found.', + 26, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Function bug3576 not found.', + 29, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Function bug3576 not found.', + 38, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Function bug3576 not found.', + 41, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/bug-3576.php b/tests/PHPStan/Rules/Functions/data/bug-3576.php new file mode 100644 index 0000000000..9f73084f22 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-3576.php @@ -0,0 +1,45 @@ + Date: Sat, 6 Nov 2021 22:31:07 +0100 Subject: [PATCH 0546/1284] Make function_exists() similar to is_callable() --- .../FunctionExistsFunctionTypeSpecifyingExtension.php | 9 ++++++++- .../PHPStan/Rules/Functions/CallCallablesRuleTest.php | 5 +++++ tests/PHPStan/Rules/Functions/data/bug-1849.php | 11 +++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Rules/Functions/data/bug-1849.php diff --git a/src/Type/Php/FunctionExistsFunctionTypeSpecifyingExtension.php b/src/Type/Php/FunctionExistsFunctionTypeSpecifyingExtension.php index 32dd08b7ad..65d10db35c 100644 --- a/src/Type/Php/FunctionExistsFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/FunctionExistsFunctionTypeSpecifyingExtension.php @@ -12,6 +12,7 @@ use PHPStan\Analyser\TypeSpecifierAwareExtension; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\Reflection\FunctionReflection; +use PHPStan\Type\CallableType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\FunctionTypeSpecifyingExtension; @@ -45,7 +46,13 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n ); } - return new SpecifiedTypes(); + return $this->typeSpecifier->create( + $node->getArgs()[0]->value, + new CallableType(), + $context, + false, + $scope + ); } public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void diff --git a/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php b/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php index 0bcb356abe..30e51389a4 100644 --- a/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php @@ -213,4 +213,9 @@ public function testRuleWithNullsafeVariant(): void ]); } + public function testBug1849(): void + { + $this->analyse([__DIR__ . '/data/bug-1849.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/bug-1849.php b/tests/PHPStan/Rules/Functions/data/bug-1849.php new file mode 100644 index 0000000000..1978c8232f --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-1849.php @@ -0,0 +1,11 @@ + Date: Sat, 6 Nov 2021 22:36:58 +0100 Subject: [PATCH 0547/1284] Fixed ignore annotations sometimes not working because of AST --- conf/config.neon | 1 + src/Analyser/FileAnalyser.php | 44 +++++++++++++++++++++-- src/Parser/RichParser.php | 46 ++++++++++++++++++++++++- tests/PHPStan/Analyser/AnalyserTest.php | 1 + 4 files changed, 88 insertions(+), 4 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index c9ec011238..a43e199216 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1537,6 +1537,7 @@ services: class: PHPStan\Parser\RichParser arguments: parser: @currentPhpVersionPhpParser + lexer: @currentPhpVersionLexer autowired: no currentPhpVersionSimpleParser: diff --git a/src/Analyser/FileAnalyser.php b/src/Analyser/FileAnalyser.php index c13f5429b3..81b971af3f 100644 --- a/src/Analyser/FileAnalyser.php +++ b/src/Analyser/FileAnalyser.php @@ -69,9 +69,18 @@ public function analyseFile( if (is_file($file)) { try { $parserNodes = $this->parser->parseFile($file); - $linesToIgnore = []; + $linesToIgnore = $this->getLinesToIgnoreFromTokens($file, $parserNodes); $temporaryFileErrors = []; $nodeCallback = function (\PhpParser\Node $node, Scope $scope) use (&$fileErrors, &$fileDependencies, &$exportedNodes, $file, $registry, $outerNodeCallback, $analysedFiles, &$linesToIgnore, &$temporaryFileErrors): void { + if ($node instanceof Node\Stmt\Trait_) { + foreach (array_keys($linesToIgnore[$file] ?? []) as $lineToIgnore) { + if ($lineToIgnore < $node->getStartLine() || $lineToIgnore > $node->getEndLine()) { + continue; + } + + unset($linesToIgnore[$file][$lineToIgnore]); + } + } if ($outerNodeCallback !== null) { $outerNodeCallback($node, $scope); } @@ -163,8 +172,16 @@ public function analyseFile( } } - foreach ($this->getLinesToIgnore($node) as $lineToIgnore) { - $linesToIgnore[$scope->getFileDescription()][$lineToIgnore] = true; + if ($scope->isInTrait()) { + $sameTraitFile = $file === $scope->getTraitReflection()->getFileName(); + foreach ($this->getLinesToIgnore($node) as $lineToIgnore) { + $linesToIgnore[$scope->getFileDescription()][$lineToIgnore] = true; + if (!$sameTraitFile) { + continue; + } + + unset($linesToIgnore[$file][$lineToIgnore]); + } } try { @@ -277,6 +294,27 @@ private function getLinesToIgnore(Node $node): array return $lines; } + /** + * @param string $file + * @param \PhpParser\Node[] $nodes + * @return array> + */ + private function getLinesToIgnoreFromTokens(string $file, array $nodes): array + { + if (!isset($nodes[0])) { + return []; + } + + /** @var int[] $tokenLines */ + $tokenLines = $nodes[0]->getAttribute('linesToIgnore', []); + $lines = []; + foreach ($tokenLines as $tokenLine) { + $lines[$file][$tokenLine] = true; + } + + return $lines; + } + private function findLineToIgnoreComment(Comment $comment): ?int { $text = $comment->getText(); diff --git a/src/Parser/RichParser.php b/src/Parser/RichParser.php index 2a5fc5d56c..6a71b15b1b 100644 --- a/src/Parser/RichParser.php +++ b/src/Parser/RichParser.php @@ -3,6 +3,7 @@ namespace PHPStan\Parser; use PhpParser\ErrorHandler\Collecting; +use PhpParser\Lexer; use PhpParser\NodeTraverser; use PhpParser\NodeVisitor\NameResolver; use PhpParser\NodeVisitor\NodeConnectingVisitor; @@ -14,6 +15,8 @@ class RichParser implements Parser private \PhpParser\Parser $parser; + private Lexer $lexer; + private NameResolver $nameResolver; private NodeConnectingVisitor $nodeConnectingVisitor; @@ -22,12 +25,14 @@ class RichParser implements Parser public function __construct( \PhpParser\Parser $parser, + Lexer $lexer, NameResolver $nameResolver, NodeConnectingVisitor $nodeConnectingVisitor, StatementOrderVisitor $statementOrderVisitor ) { $this->parser = $parser; + $this->lexer = $lexer; $this->nameResolver = $nameResolver; $this->nodeConnectingVisitor = $nodeConnectingVisitor; $this->statementOrderVisitor = $statementOrderVisitor; @@ -54,6 +59,7 @@ public function parseString(string $sourceCode): array { $errorHandler = new Collecting(); $nodes = $this->parser->parse($sourceCode, $errorHandler); + $tokens = $this->lexer->getTokens(); if ($errorHandler->hasErrors()) { throw new \PHPStan\Parser\ParserErrorsException($errorHandler->getErrors(), null); } @@ -67,7 +73,45 @@ public function parseString(string $sourceCode): array $nodeTraverser->addVisitor($this->statementOrderVisitor); /** @var array<\PhpParser\Node\Stmt> */ - return $nodeTraverser->traverse($nodes); + $nodes = $nodeTraverser->traverse($nodes); + if (isset($nodes[0])) { + $nodes[0]->setAttribute('linesToIgnore', $this->getLinesToIgnore($tokens)); + } + + return $nodes; + } + + /** + * @param mixed[] $tokens + * @return int[] + */ + private function getLinesToIgnore(array $tokens): array + { + $lines = []; + foreach ($tokens as $token) { + if (is_string($token)) { + continue; + } + + $type = $token[0]; + if ($type !== T_COMMENT && $type !== T_DOC_COMMENT) { + continue; + } + + $text = $token[1]; + $line = $token[2]; + if (strpos($text, '@phpstan-ignore-next-line') !== false) { + $line++; + } elseif (strpos($text, '@phpstan-ignore-line') === false) { + continue; + } + + $line += substr_count($token[1], "\n"); + + $lines[] = $line; + } + + return $lines; } } diff --git a/tests/PHPStan/Analyser/AnalyserTest.php b/tests/PHPStan/Analyser/AnalyserTest.php index 0de0a0f1a3..238b0e930d 100644 --- a/tests/PHPStan/Analyser/AnalyserTest.php +++ b/tests/PHPStan/Analyser/AnalyserTest.php @@ -514,6 +514,7 @@ private function createAnalyser(bool $reportUnmatchedIgnoredErrors): \PHPStan\An $nodeScopeResolver, new RichParser( new \PhpParser\Parser\Php7($lexer), + $lexer, new \PhpParser\NodeVisitor\NameResolver(), new NodeConnectingVisitor(), new StatementOrderVisitor() From 9b5387833e1d95c4294edaa03141f7ad427c76aa Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 6 Nov 2021 23:33:34 +0100 Subject: [PATCH 0548/1284] Update BC check --- .github/workflows/backward-compatibility.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/backward-compatibility.yml b/.github/workflows/backward-compatibility.yml index fd0c7f37a4..f540320b0d 100644 --- a/.github/workflows/backward-compatibility.yml +++ b/.github/workflows/backward-compatibility.yml @@ -37,7 +37,7 @@ jobs: run: | composer global config minimum-stability dev composer global config prefer-stable true - composer global require --dev ondrejmirtes/backward-compatibility-check:^5.0.7 + composer global require --dev ondrejmirtes/backward-compatibility-check:^5.0.8 - name: "Check" run: "$(composer global config bin-dir --absolute)/roave-backward-compatibility-check" From 2427b83fc13d8151034ae57cc625e525897af748 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 8 Nov 2021 13:53:39 +0100 Subject: [PATCH 0549/1284] Do not attempt to clear old containers if the directory does not exist --- src/DependencyInjection/ContainerFactory.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/DependencyInjection/ContainerFactory.php b/src/DependencyInjection/ContainerFactory.php index 1e58172194..d74cbe6ddb 100644 --- a/src/DependencyInjection/ContainerFactory.php +++ b/src/DependencyInjection/ContainerFactory.php @@ -141,8 +141,13 @@ public function clearOldContainers(string $tempDirectory): void $configurator->setDebugMode(true); $configurator->setTempDirectory($tempDirectory); + $containerDirectory = $configurator->getContainerCacheDirectory(); + if (!is_dir($containerDirectory)) { + return; + } + $finder = new Finder(); - $finder->name('Container_*')->in($configurator->getContainerCacheDirectory()); + $finder->name('Container_*')->in($containerDirectory); $twoDaysAgo = time() - 24 * 60 * 60 * 2; foreach ($finder as $containerFile) { From 96a90cc3bbd6cd09737394e84a816b32ed073c49 Mon Sep 17 00:00:00 2001 From: Jean-Luc Herren Date: Sun, 17 Oct 2021 12:05:41 +0200 Subject: [PATCH 0550/1284] Mark file resource functions as having side effects Fixes phpstan/phpstan#5461 --- bin/functionMetadata_original.php | 38 +++++++++++ resources/functionMetadata.php | 38 +++++++++++ .../Analyser/NodeScopeResolverTest.php | 2 + .../Analyser/data/filesystem-functions.php | 68 +++++++++++++++++++ 4 files changed, 146 insertions(+) create mode 100644 tests/PHPStan/Analyser/data/filesystem-functions.php diff --git a/bin/functionMetadata_original.php b/bin/functionMetadata_original.php index b7505a6f6f..8004c6dcda 100644 --- a/bin/functionMetadata_original.php +++ b/bin/functionMetadata_original.php @@ -61,8 +61,46 @@ 'bcmod' => ['hasSideEffects' => false], 'bcmul' => ['hasSideEffects' => false], // continue functionMap.php, line 424 + 'chgrp' => ['hasSideEffects' => true], + 'chmod' => ['hasSideEffects' => true], + 'chown' => ['hasSideEffects' => true], + 'copy' => ['hasSideEffects' => true], 'count' => ['hasSideEffects' => false], + 'fclose' => ['hasSideEffects' => true], + 'fflush' => ['hasSideEffects' => true], + 'fgetc' => ['hasSideEffects' => true], + 'fgetcsv' => ['hasSideEffects' => true], + 'fgets' => ['hasSideEffects' => true], + 'fgetss' => ['hasSideEffects' => true], + 'file_put_contents' => ['hasSideEffects' => true], + 'flock' => ['hasSideEffects' => true], + 'fopen' => ['hasSideEffects' => true], + 'fpassthru' => ['hasSideEffects' => true], + 'fputcsv' => ['hasSideEffects' => true], + 'fputs' => ['hasSideEffects' => true], + 'fread' => ['hasSideEffects' => true], + 'fscanf' => ['hasSideEffects' => true], + 'fseek' => ['hasSideEffects' => true], + 'ftruncate' => ['hasSideEffects' => true], + 'fwrite' => ['hasSideEffects' => true], + 'lchgrp' => ['hasSideEffects' => true], + 'lchown' => ['hasSideEffects' => true], + 'link' => ['hasSideEffects' => true], + 'mkdir' => ['hasSideEffects' => true], + 'move_uploaded_file' => ['hasSideEffects' => true], + 'pclose' => ['hasSideEffects' => true], + 'popen' => ['hasSideEffects' => true], + 'readfile' => ['hasSideEffects' => true], + 'rename' => ['hasSideEffects' => true], + 'rewind' => ['hasSideEffects' => true], + 'rmdir' => ['hasSideEffects' => true], 'sprintf' => ['hasSideEffects' => false], + 'symlink' => ['hasSideEffects' => true], + 'tempnam' => ['hasSideEffects' => true], + 'tmpfile' => ['hasSideEffects' => true], + 'touch' => ['hasSideEffects' => true], + 'umask' => ['hasSideEffects' => true], + 'unlink' => ['hasSideEffects' => true], // random functions, do not have side effects but are not deterministic 'mt_rand' => ['hasSideEffects' => true], diff --git a/resources/functionMetadata.php b/resources/functionMetadata.php index e3f29358af..37ac4d49ce 100644 --- a/resources/functionMetadata.php +++ b/resources/functionMetadata.php @@ -717,7 +717,10 @@ 'ceil' => ['hasSideEffects' => false], 'checkdate' => ['hasSideEffects' => false], 'checkdnsrr' => ['hasSideEffects' => false], + 'chgrp' => ['hasSideEffects' => true], + 'chmod' => ['hasSideEffects' => true], 'chop' => ['hasSideEffects' => false], + 'chown' => ['hasSideEffects' => true], 'chr' => ['hasSideEffects' => false], 'chunk_split' => ['hasSideEffects' => false], 'class_implements' => ['hasSideEffects' => false], @@ -738,6 +741,7 @@ 'convert_cyr_string' => ['hasSideEffects' => false], 'convert_uudecode' => ['hasSideEffects' => false], 'convert_uuencode' => ['hasSideEffects' => false], + 'copy' => ['hasSideEffects' => true], 'cos' => ['hasSideEffects' => false], 'cosh' => ['hasSideEffects' => false], 'count' => ['hasSideEffects' => false], @@ -823,11 +827,18 @@ 'explode' => ['hasSideEffects' => false], 'expm1' => ['hasSideEffects' => false], 'extension_loaded' => ['hasSideEffects' => false], + 'fclose' => ['hasSideEffects' => true], 'fdiv' => ['hasSideEffects' => false], 'feof' => ['hasSideEffects' => false], + 'fflush' => ['hasSideEffects' => true], + 'fgetc' => ['hasSideEffects' => true], + 'fgetcsv' => ['hasSideEffects' => true], + 'fgets' => ['hasSideEffects' => true], + 'fgetss' => ['hasSideEffects' => true], 'file' => ['hasSideEffects' => false], 'file_exists' => ['hasSideEffects' => false], 'file_get_contents' => ['hasSideEffects' => false], + 'file_put_contents' => ['hasSideEffects' => true], 'fileatime' => ['hasSideEffects' => false], 'filectime' => ['hasSideEffects' => false], 'filegroup' => ['hasSideEffects' => false], @@ -847,16 +858,26 @@ 'finfo::buffer' => ['hasSideEffects' => false], 'finfo::file' => ['hasSideEffects' => false], 'floatval' => ['hasSideEffects' => false], + 'flock' => ['hasSideEffects' => true], 'floor' => ['hasSideEffects' => false], 'fmod' => ['hasSideEffects' => false], 'fnmatch' => ['hasSideEffects' => false], + 'fopen' => ['hasSideEffects' => true], + 'fpassthru' => ['hasSideEffects' => true], + 'fputcsv' => ['hasSideEffects' => true], + 'fputs' => ['hasSideEffects' => true], + 'fread' => ['hasSideEffects' => true], + 'fscanf' => ['hasSideEffects' => true], + 'fseek' => ['hasSideEffects' => true], 'fstat' => ['hasSideEffects' => false], 'ftell' => ['hasSideEffects' => false], 'ftok' => ['hasSideEffects' => false], + 'ftruncate' => ['hasSideEffects' => true], 'func_get_arg' => ['hasSideEffects' => false], 'func_get_args' => ['hasSideEffects' => false], 'func_num_args' => ['hasSideEffects' => false], 'function_exists' => ['hasSideEffects' => false], + 'fwrite' => ['hasSideEffects' => true], 'gc_enabled' => ['hasSideEffects' => false], 'gc_status' => ['hasSideEffects' => false], 'gd_info' => ['hasSideEffects' => false], @@ -1182,8 +1203,11 @@ 'key' => ['hasSideEffects' => false], 'key_exists' => ['hasSideEffects' => false], 'lcfirst' => ['hasSideEffects' => false], + 'lchgrp' => ['hasSideEffects' => true], + 'lchown' => ['hasSideEffects' => true], 'libxml_get_errors' => ['hasSideEffects' => false], 'libxml_get_last_error' => ['hasSideEffects' => false], + 'link' => ['hasSideEffects' => true], 'linkinfo' => ['hasSideEffects' => false], 'locale_accept_from_http' => ['hasSideEffects' => false], 'locale_canonicalize' => ['hasSideEffects' => false], @@ -1270,7 +1294,9 @@ 'mhash_keygen_s2k' => ['hasSideEffects' => false], 'microtime' => ['hasSideEffects' => false], 'min' => ['hasSideEffects' => false], + 'mkdir' => ['hasSideEffects' => true], 'mktime' => ['hasSideEffects' => false], + 'move_uploaded_file' => ['hasSideEffects' => true], 'msgfmt_create' => ['hasSideEffects' => false], 'msgfmt_format' => ['hasSideEffects' => false], 'msgfmt_format_message' => ['hasSideEffects' => false], @@ -1311,6 +1337,7 @@ 'parse_ini_string' => ['hasSideEffects' => false], 'parse_url' => ['hasSideEffects' => false], 'pathinfo' => ['hasSideEffects' => false], + 'pclose' => ['hasSideEffects' => true], 'pcntl_errno' => ['hasSideEffects' => false], 'pcntl_get_last_error' => ['hasSideEffects' => false], 'pcntl_getpriority' => ['hasSideEffects' => false], @@ -1331,6 +1358,7 @@ 'php_uname' => ['hasSideEffects' => false], 'phpversion' => ['hasSideEffects' => false], 'pi' => ['hasSideEffects' => false], + 'popen' => ['hasSideEffects' => true], 'pos' => ['hasSideEffects' => false], 'posix_ctermid' => ['hasSideEffects' => false], 'posix_errno' => ['hasSideEffects' => false], @@ -1375,16 +1403,20 @@ 'range' => ['hasSideEffects' => false], 'rawurldecode' => ['hasSideEffects' => false], 'rawurlencode' => ['hasSideEffects' => false], + 'readfile' => ['hasSideEffects' => true], 'readlink' => ['hasSideEffects' => false], 'realpath' => ['hasSideEffects' => false], 'realpath_cache_get' => ['hasSideEffects' => false], 'realpath_cache_size' => ['hasSideEffects' => false], + 'rename' => ['hasSideEffects' => true], 'resourcebundle_count' => ['hasSideEffects' => false], 'resourcebundle_create' => ['hasSideEffects' => false], 'resourcebundle_get' => ['hasSideEffects' => false], 'resourcebundle_get_error_code' => ['hasSideEffects' => false], 'resourcebundle_get_error_message' => ['hasSideEffects' => false], 'resourcebundle_locales' => ['hasSideEffects' => false], + 'rewind' => ['hasSideEffects' => true], + 'rmdir' => ['hasSideEffects' => true], 'round' => ['hasSideEffects' => false], 'rtrim' => ['hasSideEffects' => false], 'sha1' => ['hasSideEffects' => false], @@ -1445,9 +1477,11 @@ 'substr_compare' => ['hasSideEffects' => false], 'substr_count' => ['hasSideEffects' => false], 'substr_replace' => ['hasSideEffects' => false], + 'symlink' => ['hasSideEffects' => true], 'sys_getloadavg' => ['hasSideEffects' => false], 'tan' => ['hasSideEffects' => false], 'tanh' => ['hasSideEffects' => false], + 'tempnam' => ['hasSideEffects' => true], 'timezone_abbreviations_list' => ['hasSideEffects' => false], 'timezone_identifiers_list' => ['hasSideEffects' => false], 'timezone_location_get' => ['hasSideEffects' => false], @@ -1457,8 +1491,10 @@ 'timezone_open' => ['hasSideEffects' => false], 'timezone_transitions_get' => ['hasSideEffects' => false], 'timezone_version_get' => ['hasSideEffects' => false], + 'tmpfile' => ['hasSideEffects' => true], 'token_get_all' => ['hasSideEffects' => false], 'token_name' => ['hasSideEffects' => false], + 'touch' => ['hasSideEffects' => true], 'transliterator_create' => ['hasSideEffects' => false], 'transliterator_create_from_rules' => ['hasSideEffects' => false], 'transliterator_create_inverse' => ['hasSideEffects' => false], @@ -1469,7 +1505,9 @@ 'trim' => ['hasSideEffects' => false], 'ucfirst' => ['hasSideEffects' => false], 'ucwords' => ['hasSideEffects' => false], + 'umask' => ['hasSideEffects' => true], 'uniqid' => ['hasSideEffects' => false], + 'unlink' => ['hasSideEffects' => true], 'unpack' => ['hasSideEffects' => false], 'urldecode' => ['hasSideEffects' => false], 'urlencode' => ['hasSideEffects' => false], diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index e1c39c2f67..962886fce5 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -541,6 +541,8 @@ public function dataFileAsserts(): iterable } yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2760.php'); + + yield from $this->gatherAssertTypes(__DIR__ . '/data/filesystem-functions.php'); } /** diff --git a/tests/PHPStan/Analyser/data/filesystem-functions.php b/tests/PHPStan/Analyser/data/filesystem-functions.php new file mode 100644 index 0000000000..988fbc8dc3 --- /dev/null +++ b/tests/PHPStan/Analyser/data/filesystem-functions.php @@ -0,0 +1,68 @@ + Date: Tue, 9 Nov 2021 09:07:53 +0100 Subject: [PATCH 0551/1284] Fix ServiceLocatorDynamicReturnTypeExtension --- .../ServiceLocatorDynamicReturnTypeExtension.php | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/build/PHPStan/Build/ServiceLocatorDynamicReturnTypeExtension.php b/build/PHPStan/Build/ServiceLocatorDynamicReturnTypeExtension.php index 1a1207e723..7984c8d5f2 100644 --- a/build/PHPStan/Build/ServiceLocatorDynamicReturnTypeExtension.php +++ b/build/PHPStan/Build/ServiceLocatorDynamicReturnTypeExtension.php @@ -6,9 +6,12 @@ use PHPStan\Analyser\Scope; use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\ParametersAcceptorSelector; +use PHPStan\Type\ClassStringType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantStringType; +use PHPStan\Type\Generic\GenericClassStringType; use PHPStan\Type\ObjectType; +use PHPStan\Type\ObjectWithoutClassType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; @@ -31,14 +34,19 @@ public function isMethodSupported(MethodReflection $methodReflection): bool public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type { if (count($methodCall->getArgs()) === 0) { - return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + return ParametersAcceptorSelector::selectFromArgs($scope, $methodCall->args, $methodReflection->getVariants())->getReturnType(); } $argType = $scope->getType($methodCall->getArgs()[0]->value); - if (!$argType instanceof ConstantStringType) { - return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + if ($argType instanceof ConstantStringType) { + $type = new ObjectType($argType->getValue()); + } elseif ($argType instanceof GenericClassStringType) { + $type = $argType->getGenericType(); + } elseif ($argType instanceof ClassStringType) { + $type = new ObjectWithoutClassType(); + } else { + return ParametersAcceptorSelector::selectFromArgs($scope, $methodCall->args, $methodReflection->getVariants())->getReturnType(); } - $type = new ObjectType($argType->getValue()); if ($methodReflection->getName() === 'getByType' && count($methodCall->getArgs()) >= 2) { $argType = $scope->getType($methodCall->getArgs()[1]->value); if ($argType instanceof ConstantBooleanType && $argType->getValue()) { From 78b00d0664fca965d58ad3c3f069d1a4e51c0093 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 9 Nov 2021 09:09:25 +0100 Subject: [PATCH 0552/1284] Fix --- .../Build/ServiceLocatorDynamicReturnTypeExtension.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/PHPStan/Build/ServiceLocatorDynamicReturnTypeExtension.php b/build/PHPStan/Build/ServiceLocatorDynamicReturnTypeExtension.php index 7984c8d5f2..8a222129c0 100644 --- a/build/PHPStan/Build/ServiceLocatorDynamicReturnTypeExtension.php +++ b/build/PHPStan/Build/ServiceLocatorDynamicReturnTypeExtension.php @@ -34,7 +34,7 @@ public function isMethodSupported(MethodReflection $methodReflection): bool public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type { if (count($methodCall->getArgs()) === 0) { - return ParametersAcceptorSelector::selectFromArgs($scope, $methodCall->args, $methodReflection->getVariants())->getReturnType(); + return ParametersAcceptorSelector::selectFromArgs($scope, $methodCall->getArgs(), $methodReflection->getVariants())->getReturnType(); } $argType = $scope->getType($methodCall->getArgs()[0]->value); if ($argType instanceof ConstantStringType) { @@ -44,7 +44,7 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method } elseif ($argType instanceof ClassStringType) { $type = new ObjectWithoutClassType(); } else { - return ParametersAcceptorSelector::selectFromArgs($scope, $methodCall->args, $methodReflection->getVariants())->getReturnType(); + return ParametersAcceptorSelector::selectFromArgs($scope, $methodCall->getArgs(), $methodReflection->getVariants())->getReturnType(); } if ($methodReflection->getName() === 'getByType' && count($methodCall->getArgs()) >= 2) { From b5e44f7936d54accf7c5f5e5df9941b884f5b779 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 9 Nov 2021 09:37:00 +0100 Subject: [PATCH 0553/1284] Fixed internal error with attributes referencing self:: --- composer.json | 2 +- composer.lock | 14 ++++----- .../Analyser/AnalyserIntegrationTest.php | 9 ++++++ tests/PHPStan/Analyser/data/bug-5951.php | 31 +++++++++++++++++++ 4 files changed, 48 insertions(+), 8 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/bug-5951.php diff --git a/composer.json b/composer.json index c9790751ab..e73f0ddf0f 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "nette/utils": "^3.1.3", "nikic/php-parser": "4.13.1", "ondram/ci-detector": "^3.4.0", - "ondrejmirtes/better-reflection": "4.3.78", + "ondrejmirtes/better-reflection": "4.3.79", "phpstan/php-8-stubs": "^0.1.23", "phpstan/phpdoc-parser": "^1.2.0", "react/child-process": "^0.6.4", diff --git a/composer.lock b/composer.lock index e5e1d68501..05e3cfc55e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "00797e8204d090a2f6e032deddd1eddd", + "content-hash": "f02e0a2bf75a18142c2b237f5b10272c", "packages": [ { "name": "clue/block-react", @@ -2074,16 +2074,16 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "4.3.78", + "version": "4.3.79", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "151421cd34bc441c6b1e4ff5c89bf9de2310cff9" + "reference": "fffbbc5d6d9bbb03d26af11b153847e561c4d1cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/151421cd34bc441c6b1e4ff5c89bf9de2310cff9", - "reference": "151421cd34bc441c6b1e4ff5c89bf9de2310cff9", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/fffbbc5d6d9bbb03d26af11b153847e561c4d1cb", + "reference": "fffbbc5d6d9bbb03d26af11b153847e561c4d1cb", "shasum": "" }, "require": { @@ -2138,9 +2138,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/4.3.78" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/4.3.79" }, - "time": "2021-11-05T13:42:53+00:00" + "time": "2021-11-09T09:09:38+00:00" }, { "name": "phpstan/php-8-stubs", diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 1ab24aa64d..b6a9a638b8 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -453,6 +453,15 @@ public function testBug5657(): void $this->assertCount(0, $errors); } + public function testBug5951(): void + { + if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + $errors = $this->runAnalyse(__DIR__ . '/data/bug-5951.php'); + $this->assertCount(0, $errors); + } + /** * @param string $file * @return \PHPStan\Analyser\Error[] diff --git a/tests/PHPStan/Analyser/data/bug-5951.php b/tests/PHPStan/Analyser/data/bug-5951.php new file mode 100644 index 0000000000..a774868adc --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-5951.php @@ -0,0 +1,31 @@ += 8.0 + +namespace Bug5951; + +#[\Attribute] +class Route +{ + + /** @param string[] $methods */ + public function __construct(public string $path, public string $name, public array $methods) + { + + } + +} + +class Response +{ + +} + +final class SomeController +{ + public const ROUTE_INDEX = 'some_index'; + + #[Route('/some', name: self::ROUTE_INDEX, methods: ['GET'])] + public function index(): Response + { + return new Response(); + } +} From 9384783ffe29e1646bcbdce9ba3c06e9a400ebc8 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 7 Nov 2021 18:35:06 +0100 Subject: [PATCH 0554/1284] More precise phpdocs in Container-Interface --- ...rviceLocatorDynamicReturnTypeExtension.php | 21 ++++--------------- build/phpstan.neon | 1 + build/stubs/NetteDIContainer.stub | 15 +++++++++++++ phpcs.xml | 1 + src/DependencyInjection/Container.php | 6 ++++-- .../MemoizingContainer.php | 13 ------------ .../Nette/NetteContainer.php | 6 ++++-- 7 files changed, 29 insertions(+), 34 deletions(-) create mode 100644 build/stubs/NetteDIContainer.stub diff --git a/build/PHPStan/Build/ServiceLocatorDynamicReturnTypeExtension.php b/build/PHPStan/Build/ServiceLocatorDynamicReturnTypeExtension.php index 8a222129c0..da67c961e6 100644 --- a/build/PHPStan/Build/ServiceLocatorDynamicReturnTypeExtension.php +++ b/build/PHPStan/Build/ServiceLocatorDynamicReturnTypeExtension.php @@ -6,12 +6,7 @@ use PHPStan\Analyser\Scope; use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\ParametersAcceptorSelector; -use PHPStan\Type\ClassStringType; use PHPStan\Type\Constant\ConstantBooleanType; -use PHPStan\Type\Constant\ConstantStringType; -use PHPStan\Type\Generic\GenericClassStringType; -use PHPStan\Type\ObjectType; -use PHPStan\Type\ObjectWithoutClassType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; @@ -36,25 +31,17 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method if (count($methodCall->getArgs()) === 0) { return ParametersAcceptorSelector::selectFromArgs($scope, $methodCall->getArgs(), $methodReflection->getVariants())->getReturnType(); } - $argType = $scope->getType($methodCall->getArgs()[0]->value); - if ($argType instanceof ConstantStringType) { - $type = new ObjectType($argType->getValue()); - } elseif ($argType instanceof GenericClassStringType) { - $type = $argType->getGenericType(); - } elseif ($argType instanceof ClassStringType) { - $type = new ObjectWithoutClassType(); - } else { - return ParametersAcceptorSelector::selectFromArgs($scope, $methodCall->getArgs(), $methodReflection->getVariants())->getReturnType(); - } + + $returnType = ParametersAcceptorSelector::selectFromArgs($scope, $methodCall->getArgs(), $methodReflection->getVariants())->getReturnType(); if ($methodReflection->getName() === 'getByType' && count($methodCall->getArgs()) >= 2) { $argType = $scope->getType($methodCall->getArgs()[1]->value); if ($argType instanceof ConstantBooleanType && $argType->getValue()) { - $type = TypeCombinator::addNull($type); + $returnType = TypeCombinator::addNull($returnType); } } - return $type; + return $returnType; } } diff --git a/build/phpstan.neon b/build/phpstan.neon index 8d5e8fb692..a233ed286d 100644 --- a/build/phpstan.neon +++ b/build/phpstan.neon @@ -88,6 +88,7 @@ parameters: stubFiles: - stubs/ReactChildProcess.stub - stubs/ReactStreams.stub + - stubs/NetteDIContainer.stub services: - class: PHPStan\Build\ServiceLocatorDynamicReturnTypeExtension diff --git a/build/stubs/NetteDIContainer.stub b/build/stubs/NetteDIContainer.stub new file mode 100644 index 0000000000..455eb43455 --- /dev/null +++ b/build/stubs/NetteDIContainer.stub @@ -0,0 +1,15 @@ + $type + * @return T + */ + public function getByType(string $type); + +} diff --git a/phpcs.xml b/phpcs.xml index 99137a707d..c17c53c9e1 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -50,6 +50,7 @@
+
src/Command/CommandHelper.php diff --git a/src/DependencyInjection/Container.php b/src/DependencyInjection/Container.php index 024b6e4cc9..4ce9550e5f 100644 --- a/src/DependencyInjection/Container.php +++ b/src/DependencyInjection/Container.php @@ -15,13 +15,15 @@ public function hasService(string $serviceName): bool; public function getService(string $serviceName); /** - * @param string $className + * @phpstan-template T of object + * @phpstan-param class-string $className + * @phpstan-return T * @return mixed */ public function getByType(string $className); /** - * @param string $className + * @param class-string $className * @return string[] */ public function findServiceNamesByType(string $className): array; diff --git a/src/DependencyInjection/MemoizingContainer.php b/src/DependencyInjection/MemoizingContainer.php index d43641705c..b09c36311f 100644 --- a/src/DependencyInjection/MemoizingContainer.php +++ b/src/DependencyInjection/MemoizingContainer.php @@ -20,19 +20,11 @@ public function hasService(string $serviceName): bool return $this->originalContainer->hasService($serviceName); } - /** - * @param string $serviceName - * @return mixed - */ public function getService(string $serviceName) { return $this->originalContainer->getService($serviceName); } - /** - * @param string $className - * @return mixed - */ public function getByType(string $className) { if (array_key_exists($className, $this->servicesByType)) { @@ -65,11 +57,6 @@ public function hasParameter(string $parameterName): bool return $this->originalContainer->hasParameter($parameterName); } - /** - * @param string $parameterName - * @return mixed - * @throws ParameterNotFoundException - */ public function getParameter(string $parameterName) { return $this->originalContainer->getParameter($parameterName); diff --git a/src/DependencyInjection/Nette/NetteContainer.php b/src/DependencyInjection/Nette/NetteContainer.php index 5e57025649..dbbe36f884 100644 --- a/src/DependencyInjection/Nette/NetteContainer.php +++ b/src/DependencyInjection/Nette/NetteContainer.php @@ -32,7 +32,9 @@ public function getService(string $serviceName) } /** - * @param string $className + * @phpstan-template T of object + * @phpstan-param class-string $className + * @phpstan-return T * @return mixed */ public function getByType(string $className) @@ -41,7 +43,7 @@ public function getByType(string $className) } /** - * @param string $className + * @param class-string $className * @return string[] */ public function findServiceNamesByType(string $className): array From 74787be3fe520a72febf3b779f12e9ad9e471c85 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 9 Nov 2021 10:41:43 +0100 Subject: [PATCH 0555/1284] Properties are read when using AssignRef too --- src/Node/ClassStatementsGatherer.php | 4 ++ .../UnusedPrivatePropertyRuleTest.php | 7 +++ .../PHPStan/Rules/DeadCode/data/bug-5935.php | 45 +++++++++++++++++++ 3 files changed, 56 insertions(+) create mode 100644 tests/PHPStan/Rules/DeadCode/data/bug-5935.php diff --git a/src/Node/ClassStatementsGatherer.php b/src/Node/ClassStatementsGatherer.php index 14baad972b..58b9763140 100644 --- a/src/Node/ClassStatementsGatherer.php +++ b/src/Node/ClassStatementsGatherer.php @@ -155,6 +155,10 @@ private function gatherNodes(\PhpParser\Node $node, Scope $scope): void $this->gatherNodes($node->var, $scope); return; } + if ($node instanceof Expr\AssignRef) { + $this->gatherNodes($node->expr, $scope); + return; + } if ($node instanceof \PhpParser\Node\Scalar\EncapsedStringPart) { return; } diff --git a/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php b/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php index 0df99db626..5b8b717227 100644 --- a/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php @@ -190,4 +190,11 @@ public function testNullsafe(): void $this->analyse([__DIR__ . '/data/nullsafe-unused-private-property.php'], []); } + public function testBug5935(): void + { + $this->alwaysWrittenTags = []; + $this->alwaysReadTags = []; + $this->analyse([__DIR__ . '/data/bug-5935.php'], []); + } + } diff --git a/tests/PHPStan/Rules/DeadCode/data/bug-5935.php b/tests/PHPStan/Rules/DeadCode/data/bug-5935.php new file mode 100644 index 0000000000..7a842a8fb7 --- /dev/null +++ b/tests/PHPStan/Rules/DeadCode/data/bug-5935.php @@ -0,0 +1,45 @@ +arr; + if(isset($arr[$id])) + { + return $arr[$id]; + } + else + { + return $arr[$id] = time(); + } + } +} + +class Test2 +{ + /** + * @var int[] + */ + private $arr; + + public function test(int $id): int + { + $arr = &$this->arr; + if(isset($arr[$id])) + { + return $arr[$id]; + } + else + { + return $arr[$id] = time(); + } + } +} From b92c18bc21adfe0014956f07f2f302b0a4e7454d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 9 Nov 2021 10:48:13 +0100 Subject: [PATCH 0556/1284] Regression test Closes https://github.com/phpstan/phpstan/issues/5460 --- .../PHPStan/Rules/Methods/CallMethodsRuleTest.php | 12 ++++++++++++ tests/PHPStan/Rules/Methods/data/bug-5460.php | 15 +++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 tests/PHPStan/Rules/Methods/data/bug-5460.php diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index b83d3930ae..0d53417621 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -2218,4 +2218,16 @@ public function testBug5868(): void ]); } + public function testBug5460(): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires static reflection.'); + } + + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/bug-5460.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-5460.php b/tests/PHPStan/Rules/Methods/data/bug-5460.php new file mode 100644 index 0000000000..d4c041649f --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-5460.php @@ -0,0 +1,15 @@ +setOptions(["timeout" => 1]); + + $request->getBody()->append(""); + + $client = new \http\Client(); + $client->enqueue($request)->send(); + + $response = $client->getResponse($request); +}; From d24243f4ad486ec4517b05c607b183f011004b7a Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 9 Nov 2021 09:22:03 +0100 Subject: [PATCH 0557/1284] Use positive integers for various time functions --- resources/functionMap.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 51a7c99426..b898c2b20f 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -10250,7 +10250,7 @@ 'sin' => ['float', 'number'=>'float'], 'sinh' => ['float', 'number'=>'float'], 'sizeof' => ['int', 'var'=>'Countable|array', 'mode='=>'int'], -'sleep' => ['int|false', 'seconds'=>'int'], +'sleep' => ['int|false', 'seconds'=>'positive-int'], 'snmp2_get' => ['string|false', 'host'=>'string', 'community'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'], 'snmp2_getnext' => ['string|false', 'host'=>'string', 'community'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'], 'snmp2_real_walk' => ['array|false', 'host'=>'string', 'community'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'], @@ -12305,8 +12305,8 @@ 'tidyNode::isJste' => ['bool'], 'tidyNode::isPhp' => ['bool'], 'tidyNode::isText' => ['bool'], -'time' => ['int'], -'time_nanosleep' => ['array{0:int,1:int}|bool', 'seconds'=>'int', 'nanoseconds'=>'int'], +'time' => ['positive-int'], +'time_nanosleep' => ['array{0:0|positive-int,1:0|positive-int}|bool', 'seconds'=>'positive-int', 'nanoseconds'=>'positive-int'], 'time_sleep_until' => ['bool', 'timestamp'=>'float'], 'timezone_abbreviations_list' => ['array'], 'timezone_identifiers_list' => ['array', 'what='=>'int', 'country='=>'?string'], @@ -12694,7 +12694,7 @@ 'urldecode' => ['string', 'str'=>'string'], 'urlencode' => ['string', 'str'=>'string'], 'use_soap_error_handler' => ['bool', 'handler='=>'bool'], -'usleep' => ['void', 'micro_seconds'=>'int'], +'usleep' => ['void', 'micro_seconds'=>'positive-int'], 'usort' => ['bool', '&rw_array_arg'=>'array', 'callback'=>'callable(mixed,mixed):int'], 'utf8_decode' => ['string', 'data'=>'string'], 'utf8_encode' => ['string', 'data'=>'string'], From 822a9b4fc721ed87590e1258c7afadf4871014bc Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 9 Nov 2021 10:49:49 +0100 Subject: [PATCH 0558/1284] Update functionMap.php --- resources/functionMap.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index b898c2b20f..57ab6c1bfc 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -10250,7 +10250,7 @@ 'sin' => ['float', 'number'=>'float'], 'sinh' => ['float', 'number'=>'float'], 'sizeof' => ['int', 'var'=>'Countable|array', 'mode='=>'int'], -'sleep' => ['int|false', 'seconds'=>'positive-int'], +'sleep' => ['int|false', 'seconds'=>'int'], 'snmp2_get' => ['string|false', 'host'=>'string', 'community'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'], 'snmp2_getnext' => ['string|false', 'host'=>'string', 'community'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'], 'snmp2_real_walk' => ['array|false', 'host'=>'string', 'community'=>'string', 'object_id'=>'string', 'timeout='=>'int', 'retries='=>'int'], @@ -12306,7 +12306,7 @@ 'tidyNode::isPhp' => ['bool'], 'tidyNode::isText' => ['bool'], 'time' => ['positive-int'], -'time_nanosleep' => ['array{0:0|positive-int,1:0|positive-int}|bool', 'seconds'=>'positive-int', 'nanoseconds'=>'positive-int'], +'time_nanosleep' => ['array{0:0|positive-int,1:0|positive-int}|bool', 'seconds'=>'int', 'nanoseconds'=>'int'], 'time_sleep_until' => ['bool', 'timestamp'=>'float'], 'timezone_abbreviations_list' => ['array'], 'timezone_identifiers_list' => ['array', 'what='=>'int', 'country='=>'?string'], @@ -12694,7 +12694,7 @@ 'urldecode' => ['string', 'str'=>'string'], 'urlencode' => ['string', 'str'=>'string'], 'use_soap_error_handler' => ['bool', 'handler='=>'bool'], -'usleep' => ['void', 'micro_seconds'=>'positive-int'], +'usleep' => ['void', 'micro_seconds'=>'int'], 'usort' => ['bool', '&rw_array_arg'=>'array', 'callback'=>'callable(mixed,mixed):int'], 'utf8_decode' => ['string', 'data'=>'string'], 'utf8_encode' => ['string', 'data'=>'string'], From 9488d34977f5e63728b4f182d17ccbfc426d1b4d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 9 Nov 2021 11:22:37 +0100 Subject: [PATCH 0559/1284] Fix --- tests/PHPStan/Analyser/AnalyserIntegrationTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index b6a9a638b8..91be28d604 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -455,7 +455,7 @@ public function testBug5657(): void public function testBug5951(): void { - if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) { + if (PHP_VERSION_ID < 80000) { $this->markTestSkipped('Test requires PHP 8.0.'); } $errors = $this->runAnalyse(__DIR__ . '/data/bug-5951.php'); From 1a102fec28a2901927ce703a540fb82e47d17143 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 9 Nov 2021 16:15:18 +0100 Subject: [PATCH 0560/1284] Support for new in initializers --- composer.json | 2 +- composer.lock | 14 +++--- src/Type/ConstantTypeHelper.php | 2 + .../Analyser/NodeScopeResolverTest.php | 9 ++++ .../data/new-in-initializers-runtime.php | 7 +++ .../Analyser/data/new-in-initializers.php | 46 +++++++++++++++++++ .../OptimizedSingleFileSourceLocatorTest.php | 6 ++- .../SourceLocator/data/const.php | 2 + ...compatibleDefaultParameterTypeRuleTest.php | 14 ++++++ .../Methods/data/new-in-initializers.php | 16 +++++++ 10 files changed, 109 insertions(+), 9 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/new-in-initializers-runtime.php create mode 100644 tests/PHPStan/Analyser/data/new-in-initializers.php create mode 100644 tests/PHPStan/Rules/Methods/data/new-in-initializers.php diff --git a/composer.json b/composer.json index e73f0ddf0f..66c8349458 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "nette/utils": "^3.1.3", "nikic/php-parser": "4.13.1", "ondram/ci-detector": "^3.4.0", - "ondrejmirtes/better-reflection": "4.3.79", + "ondrejmirtes/better-reflection": "4.3.80", "phpstan/php-8-stubs": "^0.1.23", "phpstan/phpdoc-parser": "^1.2.0", "react/child-process": "^0.6.4", diff --git a/composer.lock b/composer.lock index 05e3cfc55e..4dc0bce021 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f02e0a2bf75a18142c2b237f5b10272c", + "content-hash": "6155dc11ecc4374c1dda15c039fdf1ae", "packages": [ { "name": "clue/block-react", @@ -2074,16 +2074,16 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "4.3.79", + "version": "4.3.80", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "fffbbc5d6d9bbb03d26af11b153847e561c4d1cb" + "reference": "56f215f08c4421f69e2d0e82096601b0b4fbaa2c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/fffbbc5d6d9bbb03d26af11b153847e561c4d1cb", - "reference": "fffbbc5d6d9bbb03d26af11b153847e561c4d1cb", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/56f215f08c4421f69e2d0e82096601b0b4fbaa2c", + "reference": "56f215f08c4421f69e2d0e82096601b0b4fbaa2c", "shasum": "" }, "require": { @@ -2138,9 +2138,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/4.3.79" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/4.3.80" }, - "time": "2021-11-09T09:09:38+00:00" + "time": "2021-11-11T09:24:16+00:00" }, { "name": "phpstan/php-8-stubs", diff --git a/src/Type/ConstantTypeHelper.php b/src/Type/ConstantTypeHelper.php index 837a7f5f26..dbf53d5a3c 100644 --- a/src/Type/ConstantTypeHelper.php +++ b/src/Type/ConstantTypeHelper.php @@ -39,6 +39,8 @@ public static function getTypeFromValue($value): Type $arrayBuilder->setOffsetValueType(self::getTypeFromValue($k), self::getTypeFromValue($v)); } return $arrayBuilder->getArray(); + } elseif (is_object($value)) { + return new ObjectType(get_class($value)); } return new MixedType(); diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 962886fce5..a689d4d187 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -542,6 +542,15 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2760.php'); + if (PHP_VERSION_ID >= 80100 || self::$useStaticReflectionProvider) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/new-in-initializers.php'); + + if (PHP_VERSION_ID >= 80100) { + define('TEST_OBJECT_CONSTANT', new \stdClass()); + yield from $this->gatherAssertTypes(__DIR__ . '/data/new-in-initializers-runtime.php'); + } + } + yield from $this->gatherAssertTypes(__DIR__ . '/data/filesystem-functions.php'); } diff --git a/tests/PHPStan/Analyser/data/new-in-initializers-runtime.php b/tests/PHPStan/Analyser/data/new-in-initializers-runtime.php new file mode 100644 index 0000000000..4fc08b50be --- /dev/null +++ b/tests/PHPStan/Analyser/data/new-in-initializers-runtime.php @@ -0,0 +1,7 @@ += 8.1 + +namespace NewInInitializers; + +use function PHPStan\Testing\assertType; + +assertType('stdClass', TEST_OBJECT_CONSTANT); diff --git a/tests/PHPStan/Analyser/data/new-in-initializers.php b/tests/PHPStan/Analyser/data/new-in-initializers.php new file mode 100644 index 0000000000..091e0ee628 --- /dev/null +++ b/tests/PHPStan/Analyser/data/new-in-initializers.php @@ -0,0 +1,46 @@ += 8.1 + +namespace NewInInitializers; + +use function PHPStan\Testing\assertType; + +class Foo +{ + + /** + * @template T of object + * @param T $test + * @return T + */ + public function doFoo( + object $test = new \stdClass() + ): object + { + return $test; + } + + #[\Test(new \stdClass())] + public function doBar() + { + assertType(\stdClass::class, $this->doFoo()); + assertType('$this(NewInInitializers\Foo)', $this->doFoo($this)); + assertType(Bar::class, $this->doFoo(new Bar())); + } + +} + +class Bar extends Foo +{ + + public function doBar() + { + + } + + public function doBaz() + { + static $o = new \stdClass(); + assertType('mixed', $o); + } + +} diff --git a/tests/PHPStan/Reflection/BetterReflection/SourceLocator/OptimizedSingleFileSourceLocatorTest.php b/tests/PHPStan/Reflection/BetterReflection/SourceLocator/OptimizedSingleFileSourceLocatorTest.php index c28ab59464..0985d31595 100644 --- a/tests/PHPStan/Reflection/BetterReflection/SourceLocator/OptimizedSingleFileSourceLocatorTest.php +++ b/tests/PHPStan/Reflection/BetterReflection/SourceLocator/OptimizedSingleFileSourceLocatorTest.php @@ -114,6 +114,10 @@ public function dataConst(): array 'const_with_dir_const', str_replace('\\', '/', __DIR__ . '/data'), ], + [ + 'OPTIMIZED_SFSL_OBJECT_CONSTANT', + new \stdClass(), + ], ]; } @@ -130,7 +134,7 @@ public function testConst(string $constantName, $value): void $constantReflector = new ConstantReflector($locator, $classReflector); $constant = $constantReflector->reflect($constantName); $this->assertSame($constantName, $constant->getName()); - $this->assertSame($value, $constant->getValue()); + $this->assertEquals($value, $constant->getValue()); } public function dataConstUnknown(): array diff --git a/tests/PHPStan/Reflection/BetterReflection/SourceLocator/data/const.php b/tests/PHPStan/Reflection/BetterReflection/SourceLocator/data/const.php index eba1d99941..ce333cbfb7 100644 --- a/tests/PHPStan/Reflection/BetterReflection/SourceLocator/data/const.php +++ b/tests/PHPStan/Reflection/BetterReflection/SourceLocator/data/const.php @@ -10,3 +10,5 @@ define('TEST_VARIABLE', $foo); define('const_with_dir_const', __DIR__); + +define('OPTIMIZED_SFSL_OBJECT_CONSTANT', new \stdClass()); diff --git a/tests/PHPStan/Rules/Methods/IncompatibleDefaultParameterTypeRuleTest.php b/tests/PHPStan/Rules/Methods/IncompatibleDefaultParameterTypeRuleTest.php index cf9640685c..6c4dfc45a6 100644 --- a/tests/PHPStan/Rules/Methods/IncompatibleDefaultParameterTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/IncompatibleDefaultParameterTypeRuleTest.php @@ -45,4 +45,18 @@ public function testBug2573(): void $this->analyse([__DIR__ . '/data/bug-2573.php'], []); } + public function testNewInInitializers(): void + { + if (PHP_VERSION_ID < 80100 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->analyse([__DIR__ . '/data/new-in-initializers.php'], [ + [ + 'Default value of the parameter #1 $i (stdClass) of method MethodNewInInitializers\Foo::doFoo() is incompatible with type int.', + 11, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/new-in-initializers.php b/tests/PHPStan/Rules/Methods/data/new-in-initializers.php new file mode 100644 index 0000000000..5369835557 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/new-in-initializers.php @@ -0,0 +1,16 @@ += 8.1 + +namespace MethodNewInInitializers; + +class Foo +{ + + /** + * @param int $i + */ + public function doFoo($i = new \stdClass(), object $o = new \stdClass()) + { + + } + +} From 0319ace3a34a1c723ef9cd45f39578833d4ecff1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 11 Nov 2021 11:12:35 +0100 Subject: [PATCH 0561/1284] Update Nette dependencies --- composer.json | 8 ++--- composer.lock | 93 +++++++++++++++++++++++++++------------------------ 2 files changed, 54 insertions(+), 47 deletions(-) diff --git a/composer.json b/composer.json index 66c8349458..1090a3f5e6 100644 --- a/composer.json +++ b/composer.json @@ -16,11 +16,11 @@ "jean85/pretty-package-versions": "^1.0.3", "jetbrains/phpstorm-stubs": "dev-master#fdcc30312221ce08f3a4413588e2df4b77f843fc", "nette/bootstrap": "^3.0", - "nette/di": "^3.0.5", + "nette/di": "^3.0.11", "nette/finder": "^2.5", - "nette/neon": "^3.0", - "nette/schema": "^1.0", - "nette/utils": "^3.1.3", + "nette/neon": "^3.3.1", + "nette/schema": "^1.2.2", + "nette/utils": "^3.2.5", "nikic/php-parser": "4.13.1", "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "4.3.80", diff --git a/composer.lock b/composer.lock index 4dc0bce021..9a05f5defc 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "6155dc11ecc4374c1dda15c039fdf1ae", + "content-hash": "893316fb27939a9490338d747bf26445", "packages": [ { "name": "clue/block-react", @@ -1461,26 +1461,26 @@ }, { "name": "nette/di", - "version": "v3.0.5", + "version": "v3.0.11", "source": { "type": "git", "url": "https://github.com/nette/di.git", - "reference": "766e8185196a97ded4f9128db6d79a3a124b7eb6" + "reference": "942e406f63b88b57cb4e095ae0fd95c103d12c5b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/di/zipball/766e8185196a97ded4f9128db6d79a3a124b7eb6", - "reference": "766e8185196a97ded4f9128db6d79a3a124b7eb6", + "url": "https://api.github.com/repos/nette/di/zipball/942e406f63b88b57cb4e095ae0fd95c103d12c5b", + "reference": "942e406f63b88b57cb4e095ae0fd95c103d12c5b", "shasum": "" }, "require": { "ext-tokenizer": "*", - "nette/neon": "^3.0", + "nette/neon": "^3.3", "nette/php-generator": "^3.3.3", "nette/robot-loader": "^3.2", - "nette/schema": "^1.0", - "nette/utils": "^3.1", - "php": ">=7.1" + "nette/schema": "^1.1", + "nette/utils": "^3.1.6", + "php": ">=7.1 <8.2" }, "conflict": { "nette/bootstrap": "<3.0" @@ -1517,7 +1517,7 @@ "homepage": "https://nette.org/contributors" } ], - "description": "💎 Nette Dependency Injection Container: Flexible, compiled and full-featured DIC with perfectly usable autowiring and support for all new PHP 7.1 features.", + "description": "💎 Nette Dependency Injection Container: Flexible, compiled and full-featured DIC with perfectly usable autowiring and support for all new PHP features.", "homepage": "https://nette.org", "keywords": [ "compiled", @@ -1530,9 +1530,9 @@ ], "support": { "issues": "https://github.com/nette/di/issues", - "source": "https://github.com/nette/di/tree/master" + "source": "https://github.com/nette/di/tree/v3.0.11" }, - "time": "2020-08-13T13:04:23+00:00" + "time": "2021-10-26T11:44:44+00:00" }, { "name": "nette/finder", @@ -1603,32 +1603,34 @@ }, { "name": "nette/neon", - "version": "v3.2.1", + "version": "v3.3.1", "source": { "type": "git", "url": "https://github.com/nette/neon.git", - "reference": "a5b3a60833d2ef55283a82d0c30b45d136b29e75" + "reference": "1f4e5f6a30bf45b6c2c932be7396ea70692ee607" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/neon/zipball/a5b3a60833d2ef55283a82d0c30b45d136b29e75", - "reference": "a5b3a60833d2ef55283a82d0c30b45d136b29e75", + "url": "https://api.github.com/repos/nette/neon/zipball/1f4e5f6a30bf45b6c2c932be7396ea70692ee607", + "reference": "1f4e5f6a30bf45b6c2c932be7396ea70692ee607", "shasum": "" }, "require": { - "ext-iconv": "*", "ext-json": "*", "php": ">=7.1" }, "require-dev": { "nette/tester": "^2.0", "phpstan/phpstan": "^0.12", - "tracy/tracy": "^2.3" + "tracy/tracy": "^2.8.8" }, + "bin": [ + "bin/neon-lint" + ], "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-master": "3.3-dev" } }, "autoload": { @@ -1663,9 +1665,9 @@ ], "support": { "issues": "https://github.com/nette/neon/issues", - "source": "https://github.com/nette/neon/tree/master" + "source": "https://github.com/nette/neon/tree/v3.3.1" }, - "time": "2020-07-31T12:28:05+00:00" + "time": "2021-11-09T01:00:15+00:00" }, { "name": "nette/php-generator", @@ -1804,30 +1806,32 @@ }, { "name": "nette/schema", - "version": "v1.0.2", + "version": "v1.2.2", "source": { "type": "git", "url": "https://github.com/nette/schema.git", - "reference": "febf71fb4052c824046f5a33f4f769a6e7fa0cb4" + "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/febf71fb4052c824046f5a33f4f769a6e7fa0cb4", - "reference": "febf71fb4052c824046f5a33f4f769a6e7fa0cb4", + "url": "https://api.github.com/repos/nette/schema/zipball/9a39cef03a5b34c7de64f551538cbba05c2be5df", + "reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df", "shasum": "" }, "require": { - "nette/utils": "^3.1", - "php": ">=7.1" + "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0", + "php": ">=7.1 <8.2" }, "require-dev": { - "nette/tester": "^2.2", + "nette/tester": "^2.3 || ^2.4", "phpstan/phpstan-nette": "^0.12", - "tracy/tracy": "^2.3" + "tracy/tracy": "^2.7" }, "type": "library", "extra": { - "branch-alias": [] + "branch-alias": { + "dev-master": "1.2-dev" + } }, "autoload": { "classmap": [ @@ -1837,8 +1841,8 @@ "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause", - "GPL-2.0", - "GPL-3.0" + "GPL-2.0-only", + "GPL-3.0-only" ], "authors": [ { @@ -1858,26 +1862,29 @@ ], "support": { "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.0.2" + "source": "https://github.com/nette/schema/tree/v1.2.2" }, - "time": "2020-01-06T22:52:48+00:00" + "time": "2021-10-15T11:40:02+00:00" }, { "name": "nette/utils", - "version": "v3.1.3", + "version": "v3.2.5", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "c09937fbb24987b2a41c6022ebe84f4f1b8eec0f" + "reference": "9cd80396ca58d7969ab44fc7afcf03624dfa526e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/c09937fbb24987b2a41c6022ebe84f4f1b8eec0f", - "reference": "c09937fbb24987b2a41c6022ebe84f4f1b8eec0f", + "url": "https://api.github.com/repos/nette/utils/zipball/9cd80396ca58d7969ab44fc7afcf03624dfa526e", + "reference": "9cd80396ca58d7969ab44fc7afcf03624dfa526e", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2 <8.2" + }, + "conflict": { + "nette/di": "<3.0.6" }, "require-dev": { "nette/tester": "~2.0", @@ -1896,7 +1903,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-master": "3.2-dev" } }, "autoload": { @@ -1920,7 +1927,7 @@ "homepage": "https://nette.org/contributors" } ], - "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", "homepage": "https://nette.org", "keywords": [ "array", @@ -1940,9 +1947,9 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v3.1.3" + "source": "https://github.com/nette/utils/tree/v3.2.5" }, - "time": "2020-08-07T10:34:21+00:00" + "time": "2021-09-20T10:50:11+00:00" }, { "name": "nikic/php-parser", From da34d3f1d642d71d5a5130d371bb243c65b0b195 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 11 Nov 2021 13:10:57 +0100 Subject: [PATCH 0562/1284] Type system: ConstantStringType created from ::class is always considered a classname --- src/Type/ClassStringType.php | 7 ++--- src/Type/Constant/ConstantStringType.php | 12 ++++--- src/Type/Generic/GenericClassStringType.php | 4 +-- ...sExistsFunctionTypeSpecifyingExtension.php | 26 ++++++---------- .../Rules/Methods/ReturnTypeRuleTest.php | 5 +++ tests/PHPStan/Rules/Methods/data/bug-5979.php | 31 +++++++++++++++++++ 6 files changed, 57 insertions(+), 28 deletions(-) create mode 100644 tests/PHPStan/Rules/Methods/data/bug-5979.php diff --git a/src/Type/ClassStringType.php b/src/Type/ClassStringType.php index 71b3344bd6..e0fea538d9 100644 --- a/src/Type/ClassStringType.php +++ b/src/Type/ClassStringType.php @@ -2,7 +2,6 @@ namespace PHPStan\Type; -use PHPStan\Reflection\ReflectionProviderStaticAccessor; use PHPStan\TrinaryLogic; use PHPStan\Type\Constant\ConstantStringType; @@ -28,8 +27,7 @@ public function accepts(Type $type, bool $strictTypes): TrinaryLogic } if ($type instanceof ConstantStringType) { - $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); - return TrinaryLogic::createFromBoolean($reflectionProvider->hasClass($type->getValue())); + return TrinaryLogic::createFromBoolean($type->isClassString()); } if ($type instanceof self) { @@ -46,8 +44,7 @@ public function accepts(Type $type, bool $strictTypes): TrinaryLogic public function isSuperTypeOf(Type $type): TrinaryLogic { if ($type instanceof ConstantStringType) { - $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); - return TrinaryLogic::createFromBoolean($reflectionProvider->hasClass($type->getValue())); + return TrinaryLogic::createFromBoolean($type->isClassString()); } if ($type instanceof self) { diff --git a/src/Type/Constant/ConstantStringType.php b/src/Type/Constant/ConstantStringType.php index b57e467e25..21be5ab5bd 100644 --- a/src/Type/Constant/ConstantStringType.php +++ b/src/Type/Constant/ConstantStringType.php @@ -57,7 +57,13 @@ public function getValue(): string public function isClassString(): bool { - return $this->isClassString; + if ($this->isClassString) { + return true; + } + + $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); + + return $reflectionProvider->hasClass($this->value); } public function describe(VerbosityLevel $level): string @@ -118,9 +124,7 @@ public function isSuperTypeOf(Type $type): TrinaryLogic return TrinaryLogic::createNo(); } if ($type instanceof ClassStringType) { - $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); - - return $reflectionProvider->hasClass($this->getValue()) ? TrinaryLogic::createMaybe() : TrinaryLogic::createNo(); + return $this->isClassString() ? TrinaryLogic::createMaybe() : TrinaryLogic::createNo(); } if ($type instanceof self) { diff --git a/src/Type/Generic/GenericClassStringType.php b/src/Type/Generic/GenericClassStringType.php index bcb5e67e56..9ae30ff415 100644 --- a/src/Type/Generic/GenericClassStringType.php +++ b/src/Type/Generic/GenericClassStringType.php @@ -2,7 +2,6 @@ namespace PHPStan\Type\Generic; -use PHPStan\Reflection\ReflectionProviderStaticAccessor; use PHPStan\TrinaryLogic; use PHPStan\Type\ClassStringType; use PHPStan\Type\CompoundType; @@ -53,8 +52,7 @@ public function accepts(Type $type, bool $strictTypes): TrinaryLogic } if ($type instanceof ConstantStringType) { - $reflectionProvider = ReflectionProviderStaticAccessor::getInstance(); - if (!$reflectionProvider->hasClass($type->getValue())) { + if (!$type->isClassString()) { return TrinaryLogic::createNo(); } diff --git a/src/Type/Php/ClassExistsFunctionTypeSpecifyingExtension.php b/src/Type/Php/ClassExistsFunctionTypeSpecifyingExtension.php index 771c5f924f..b36b83e401 100644 --- a/src/Type/Php/ClassExistsFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/ClassExistsFunctionTypeSpecifyingExtension.php @@ -16,8 +16,6 @@ use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\FunctionTypeSpecifyingExtension; -use PHPStan\Type\NeverType; -use PHPStan\Type\TypeCombinator; class ClassExistsFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension { @@ -41,20 +39,16 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n { $argType = $scope->getType($node->getArgs()[0]->value); $classStringType = new ClassStringType(); - if (TypeCombinator::intersect($argType, $classStringType) instanceof NeverType) { - if ($argType instanceof ConstantStringType) { - return $this->typeSpecifier->create( - new FuncCall(new FullyQualified('class_exists'), [ - new Arg(new String_(ltrim($argType->getValue(), '\\'))), - ]), - new ConstantBooleanType(true), - $context, - false, - $scope - ); - } - - return new SpecifiedTypes(); + if ($argType instanceof ConstantStringType) { + return $this->typeSpecifier->create( + new FuncCall(new FullyQualified('class_exists'), [ + new Arg(new String_(ltrim($argType->getValue(), '\\'))), + ]), + new ConstantBooleanType(true), + $context, + false, + $scope + ); } return $this->typeSpecifier->create( diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index 1b324ad17b..a873eb2e10 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -564,4 +564,9 @@ public function testBug5218(bool $checkExplicitMixed, array $errors): void $this->analyse([__DIR__ . '/data/bug-5218.php'], $errors); } + public function testBug5979(): void + { + $this->analyse([__DIR__ . '/data/bug-5979.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-5979.php b/tests/PHPStan/Rules/Methods/data/bug-5979.php new file mode 100644 index 0000000000..bbc8b8ee79 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-5979.php @@ -0,0 +1,31 @@ + + */ + public function dataProviderForTestValidCommands(): array + { + $data = [ + // left out some commands here for simplicity ... + // [...] + [ + 'migrations:execute', + SplQueue::class, + ], + ]; + + // this is only available with DBAL 2.x + if (class_exists(ImportCommand::class)) { + $data[] = [ + 'dbal:import', + ImportCommand::class, + ]; + } + + return $data; + } +} From 430f3adb28a4484136794dfdde204d72a64c33c2 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 11 Nov 2021 13:55:57 +0100 Subject: [PATCH 0563/1284] Support for first-class callables --- src/Analyser/MutatingScope.php | 78 +++++++++++++++++++ src/Analyser/NodeScopeResolver.php | 20 +++++ src/Analyser/TypeSpecifier.php | 4 + src/Node/FunctionCallableNode.php | 45 +++++++++++ src/Node/InstantiationCallableNode.php | 45 +++++++++++ src/Node/MethodCallableNode.php | 53 +++++++++++++ src/Node/StaticMethodCallableNode.php | 59 ++++++++++++++ .../Analyser/NodeScopeResolverTest.php | 4 + .../Analyser/data/first-class-callables.php | 28 +++++++ .../Rules/Classes/InstantiationRuleTest.php | 10 +++ .../first-class-instantiation-callable.php | 13 ++++ .../CallToFunctionParametersRuleTest.php | 10 +++ .../Functions/data/first-class-callables.php | 13 ++++ .../Rules/Methods/CallMethodsRuleTest.php | 14 ++++ .../Methods/CallStaticMethodsRuleTest.php | 12 +++ .../data/first-class-method-callable.php | 13 ++++ .../first-class-static-method-callable.php | 13 ++++ 17 files changed, 434 insertions(+) create mode 100644 src/Node/FunctionCallableNode.php create mode 100644 src/Node/InstantiationCallableNode.php create mode 100644 src/Node/MethodCallableNode.php create mode 100644 src/Node/StaticMethodCallableNode.php create mode 100644 tests/PHPStan/Analyser/data/first-class-callables.php create mode 100644 tests/PHPStan/Rules/Classes/data/first-class-instantiation-callable.php create mode 100644 tests/PHPStan/Rules/Functions/data/first-class-callables.php create mode 100644 tests/PHPStan/Rules/Methods/data/first-class-method-callable.php create mode 100644 tests/PHPStan/Rules/Methods/data/first-class-static-method-callable.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 3d1a384a44..c67928aa67 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -1474,6 +1474,65 @@ private function resolveType(Expr $node): Type return $constantString; } elseif ($node instanceof DNumber) { return new ConstantFloatType($node->value); + } elseif ($node instanceof Expr\CallLike && $node->isFirstClassCallable()) { + if ($node instanceof FuncCall) { + if ($node->name instanceof Name) { + if ($this->reflectionProvider->hasFunction($node->name, $this)) { + return $this->createFirstClassCallable( + $this->reflectionProvider->getFunction($node->name, $this)->getVariants() + ); + } + + return new ObjectType(\Closure::class); + } + + $callableType = $this->getType($node->name); + if (!$callableType->isCallable()->yes()) { + return new ObjectType(\Closure::class); + } + + return $this->createFirstClassCallable( + $callableType->getCallableParametersAcceptors($this) + ); + } + + if ($node instanceof MethodCall) { + if (!$node->name instanceof Node\Identifier) { + return new ObjectType(\Closure::class); + } + + $varType = $this->getType($node->var); + $method = $this->getMethodReflection($varType, $node->name->toString()); + if ($method === null) { + return new ObjectType(\Closure::class); + } + + return $this->createFirstClassCallable($method->getVariants()); + } + + if ($node instanceof Expr\StaticCall) { + if (!$node->class instanceof Name) { + return new ObjectType(\Closure::class); + } + + $classType = $this->resolveTypeByName($node->class); + if (!$node->name instanceof Node\Identifier) { + return new ObjectType(\Closure::class); + } + + $methodName = $node->name->toString(); + if (!$classType->hasMethod($methodName)->yes()) { + return new ObjectType(\Closure::class); + } + + return $this->createFirstClassCallable($classType->getMethod($methodName, $this)->getVariants()); + } + + if ($node instanceof New_) { + return new ErrorType(); + } + + throw new \PHPStan\ShouldNotHappenException(); } elseif ($node instanceof Expr\Closure || $node instanceof Expr\ArrowFunction) { $parameters = []; $isVariadic = false; @@ -2348,6 +2407,25 @@ private function getNullsafeShortCircuitingType(Expr $expr, Type $type): Type return $type; } + /** + * @param ParametersAcceptor[] $variants + * @return Type + */ + private function createFirstClassCallable(array $variants): Type + { + $closureTypes = []; + foreach ($variants as $variant) { + $parameters = $variant->getParameters(); + $closureTypes[] = new ClosureType( + $parameters, + $variant->getReturnType(), + $variant->isVariadic() + ); + } + + return TypeCombinator::union(...$closureTypes); + } + private function resolveConstantType(string $constantName, Type $constantType): Type { if ($constantType instanceof ConstantType && in_array($constantName, $this->dynamicConstantNames, true)) { diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 2b3125ed6c..2f22e74f78 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -67,19 +67,23 @@ use PHPStan\Node\DoWhileLoopConditionNode; use PHPStan\Node\ExecutionEndNode; use PHPStan\Node\FinallyExitPointsNode; +use PHPStan\Node\FunctionCallableNode; use PHPStan\Node\FunctionReturnStatementsNode; use PHPStan\Node\InArrowFunctionNode; use PHPStan\Node\InClassMethodNode; use PHPStan\Node\InClassNode; use PHPStan\Node\InClosureNode; use PHPStan\Node\InFunctionNode; +use PHPStan\Node\InstantiationCallableNode; use PHPStan\Node\LiteralArrayItem; use PHPStan\Node\LiteralArrayNode; use PHPStan\Node\MatchExpressionArm; use PHPStan\Node\MatchExpressionArmCondition; use PHPStan\Node\MatchExpressionNode; +use PHPStan\Node\MethodCallableNode; use PHPStan\Node\MethodReturnStatementsNode; use PHPStan\Node\ReturnStatement; +use PHPStan\Node\StaticMethodCallableNode; use PHPStan\Node\UnreachableStatementNode; use PHPStan\Parser\Parser; use PHPStan\Php\PhpVersion; @@ -1683,6 +1687,22 @@ private function findEarlyTerminatingExpr(Expr $expr, Scope $scope): ?Expr */ private function processExprNode(Expr $expr, MutatingScope $scope, callable $nodeCallback, ExpressionContext $context): ExpressionResult { + if ($expr instanceof Expr\CallLike && $expr->isFirstClassCallable()) { + if ($expr instanceof FuncCall) { + $newExpr = new FunctionCallableNode($expr->name, $expr->getAttributes()); + } elseif ($expr instanceof MethodCall) { + $newExpr = new MethodCallableNode($expr->var, $expr->name, $expr->getAttributes()); + } elseif ($expr instanceof StaticCall) { + $newExpr = new StaticMethodCallableNode($expr->class, $expr->name, $expr->getAttributes()); + } elseif ($expr instanceof New_ && !$expr->class instanceof Class_) { + $newExpr = new InstantiationCallableNode($expr->class, $expr->getAttributes()); + } else { + throw new \PHPStan\ShouldNotHappenException(); + } + + return $this->processExprNode($newExpr, $scope, $nodeCallback, $context); + } + $this->callNodeCallbackWithExpression($nodeCallback, $expr, $scope, $context); if ($expr instanceof Variable) { diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 5a6453374b..10e7791809 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -111,6 +111,10 @@ public function specifyTypesInCondition( TypeSpecifierContext $context ): SpecifiedTypes { + if ($expr instanceof Expr\CallLike && $expr->isFirstClassCallable()) { + return new SpecifiedTypes(); + } + if ($expr instanceof Instanceof_) { $exprNode = $expr->expr; if ($expr->class instanceof Name) { diff --git a/src/Node/FunctionCallableNode.php b/src/Node/FunctionCallableNode.php new file mode 100644 index 0000000000..909c576720 --- /dev/null +++ b/src/Node/FunctionCallableNode.php @@ -0,0 +1,45 @@ +name = $name; + } + + /** + * @return Expr|Name + */ + public function getName() + { + return $this->name; + } + + public function getType(): string + { + return 'PHPStan_Node_FunctionCallableNode'; + } + + /** + * @return string[] + */ + public function getSubNodeNames(): array + { + return []; + } + +} diff --git a/src/Node/InstantiationCallableNode.php b/src/Node/InstantiationCallableNode.php new file mode 100644 index 0000000000..bd1325e9b4 --- /dev/null +++ b/src/Node/InstantiationCallableNode.php @@ -0,0 +1,45 @@ +class = $class; + } + + /** + * @return Expr|Name + */ + public function getClass() + { + return $this->class; + } + + public function getType(): string + { + return 'PHPStan_Node_InstantiationCallableNode'; + } + + /** + * @return string[] + */ + public function getSubNodeNames(): array + { + return []; + } + +} diff --git a/src/Node/MethodCallableNode.php b/src/Node/MethodCallableNode.php new file mode 100644 index 0000000000..b2b1cb40ca --- /dev/null +++ b/src/Node/MethodCallableNode.php @@ -0,0 +1,53 @@ +var = $var; + $this->name = $name; + } + + public function getVar(): Expr + { + return $this->var; + } + + /** + * @return Expr|Identifier + */ + public function getName() + { + return $this->name; + } + + public function getType(): string + { + return 'PHPStan_Node_MethodCallableNode'; + } + + /** + * @return string[] + */ + public function getSubNodeNames(): array + { + return []; + } + +} diff --git a/src/Node/StaticMethodCallableNode.php b/src/Node/StaticMethodCallableNode.php new file mode 100644 index 0000000000..e233c0e311 --- /dev/null +++ b/src/Node/StaticMethodCallableNode.php @@ -0,0 +1,59 @@ +class = $class; + $this->name = $name; + } + + /** + * @return Expr|Name + */ + public function getClass() + { + return $this->class; + } + + /** + * @return Identifier|Expr + */ + public function getName() + { + return $this->name; + } + + public function getType(): string + { + return 'PHPStan_Node_StaticMethodCallableNode'; + } + + /** + * @return string[] + */ + public function getSubNodeNames(): array + { + return []; + } + +} diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index a689d4d187..ab0aa88a3c 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -551,6 +551,10 @@ public function dataFileAsserts(): iterable } } + if (PHP_VERSION_ID >= 80100 || self::$useStaticReflectionProvider) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/first-class-callables.php'); + } + yield from $this->gatherAssertTypes(__DIR__ . '/data/filesystem-functions.php'); } diff --git a/tests/PHPStan/Analyser/data/first-class-callables.php b/tests/PHPStan/Analyser/data/first-class-callables.php new file mode 100644 index 0000000000..138cf6ab66 --- /dev/null +++ b/tests/PHPStan/Analyser/data/first-class-callables.php @@ -0,0 +1,28 @@ += 8.1 + +namespace FirstClassCallables; + +use function PHPStan\Testing\assertType; + +class Foo +{ + + public function doFoo(string $foo): void + { + assertType('Closure(string): void', $this->doFoo(...)); + assertType('Closure(): void', self::doBar(...)); + assertType('Closure', self::$foo(...)); + assertType('Closure', $this->nonexistent(...)); + assertType('Closure', $this->$foo(...)); + assertType('Closure(string): int<0, max>', strlen(...)); + assertType('Closure(string): int<0, max>', 'strlen'(...)); + assertType('Closure', 'nonexistent'(...)); + assertType('*ERROR*', new self(...)); + } + + public static function doBar(): void + { + + } + +} diff --git a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php index 7e544db348..5d47375d3f 100644 --- a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php +++ b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php @@ -348,4 +348,14 @@ public function testBug4681(): void $this->analyse([__DIR__ . '/data/bug-4681.php'], []); } + public function testFirstClassCallable(): void + { + if (PHP_VERSION_ID < 80100 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + + // handled by a different rule + $this->analyse([__DIR__ . '/data/first-class-instantiation-callable.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Classes/data/first-class-instantiation-callable.php b/tests/PHPStan/Rules/Classes/data/first-class-instantiation-callable.php new file mode 100644 index 0000000000..7ec3378883 --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/first-class-instantiation-callable.php @@ -0,0 +1,13 @@ +analyse([__DIR__ . '/data/call-user-func-array.php'], $errors); } + public function testFirstClassCallables(): void + { + if (PHP_VERSION_ID < 80100 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + + // handled by a different rule + $this->analyse([__DIR__ . '/data/first-class-callables.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/first-class-callables.php b/tests/PHPStan/Rules/Functions/data/first-class-callables.php new file mode 100644 index 0000000000..da237d1130 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/first-class-callables.php @@ -0,0 +1,13 @@ += 8.1 + +namespace FirstClassFunctionCallable; + +class Foo +{ + + public function doFoo(): void + { + $f = json_encode(...); + } + +} diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 0d53417621..d8e3d2ca32 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -2230,4 +2230,18 @@ public function testBug5460(): void $this->analyse([__DIR__ . '/data/bug-5460.php'], []); } + public function testFirstClassCallable(): void + { + if (PHP_VERSION_ID < 80100 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + + // handled by a different rule + $this->analyse([__DIR__ . '/data/first-class-method-callable.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php index 9366509dcb..562351aa50 100644 --- a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php @@ -457,4 +457,16 @@ public function testBug4886(): void $this->analyse([__DIR__ . '/data/bug-4886.php'], []); } + public function testFirstClassCallables(): void + { + if (PHP_VERSION_ID < 80100 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + + $this->checkThisOnly = false; + + // handled by a different rule + $this->analyse([__DIR__ . '/data/first-class-static-method-callable.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/first-class-method-callable.php b/tests/PHPStan/Rules/Methods/data/first-class-method-callable.php new file mode 100644 index 0000000000..c969b924c2 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/first-class-method-callable.php @@ -0,0 +1,13 @@ += 8.1 + +namespace FirstClassMethodCallable; + +class Foo +{ + + public function doFoo(int $i): void + { + $this->doFoo(...); + } + +} diff --git a/tests/PHPStan/Rules/Methods/data/first-class-static-method-callable.php b/tests/PHPStan/Rules/Methods/data/first-class-static-method-callable.php new file mode 100644 index 0000000000..755e9be311 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/first-class-static-method-callable.php @@ -0,0 +1,13 @@ += 8.1 + +namespace FirstClassStaticMethodCallable; + +class Foo +{ + + public static function doFoo(int $i): void + { + self::doFoo(...); + } + +} From dc81bfd0a405691322576a3a74ef49883cb0864b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 11 Nov 2021 15:08:06 +0100 Subject: [PATCH 0564/1284] Fix --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 0dba251bfa..4a0cc45134 100644 --- a/Makefile +++ b/Makefile @@ -45,6 +45,7 @@ lint: --exclude tests/PHPStan/Rules/Properties/data/overriding-property.php \ --exclude tests/PHPStan/Rules/Constants/data/overriding-final-constant.php \ --exclude tests/PHPStan/Rules/Properties/data/intersection-types.php \ + --exclude tests/PHPStan/Rules/Classes/data/first-class-instantiation-callable.php \ src tests compiler/src cs: From 083c9b5cddaa3b78868030fd8ea5f480c6d486eb Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 11 Nov 2021 15:29:34 +0100 Subject: [PATCH 0565/1284] Fix --- tests/PHPStan/Analyser/data/first-class-callables.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/PHPStan/Analyser/data/first-class-callables.php b/tests/PHPStan/Analyser/data/first-class-callables.php index 138cf6ab66..9af25d7cf0 100644 --- a/tests/PHPStan/Analyser/data/first-class-callables.php +++ b/tests/PHPStan/Analyser/data/first-class-callables.php @@ -17,7 +17,6 @@ public function doFoo(string $foo): void assertType('Closure(string): int<0, max>', strlen(...)); assertType('Closure(string): int<0, max>', 'strlen'(...)); assertType('Closure', 'nonexistent'(...)); - assertType('*ERROR*', new self(...)); } public static function doBar(): void From 30ff22716a6304a5a7211b0b4bdd069af62910d4 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 11 Nov 2021 16:26:14 +0100 Subject: [PATCH 0566/1284] First-class callable rules --- Makefile | 1 + conf/config.level0.neon | 9 ++ src/Php/PhpVersion.php | 5 + .../Classes/InstantiationCallableRule.php | 28 ++++ src/Rules/Functions/FunctionCallableRule.php | 125 ++++++++++++++++++ .../Classes/InstantiationCallableRuleTest.php | 32 +++++ .../Classes/data/instantiation-callable.php | 14 ++ .../Functions/FunctionCallableRuleTest.php | 80 +++++++++++ .../data/function-callable-not-supported.php | 13 ++ .../Functions/data/function-callable.php | 55 ++++++++ 10 files changed, 362 insertions(+) create mode 100644 src/Rules/Classes/InstantiationCallableRule.php create mode 100644 src/Rules/Functions/FunctionCallableRule.php create mode 100644 tests/PHPStan/Rules/Classes/InstantiationCallableRuleTest.php create mode 100644 tests/PHPStan/Rules/Classes/data/instantiation-callable.php create mode 100644 tests/PHPStan/Rules/Functions/FunctionCallableRuleTest.php create mode 100644 tests/PHPStan/Rules/Functions/data/function-callable-not-supported.php create mode 100644 tests/PHPStan/Rules/Functions/data/function-callable.php diff --git a/Makefile b/Makefile index 4a0cc45134..c1f305b635 100644 --- a/Makefile +++ b/Makefile @@ -46,6 +46,7 @@ lint: --exclude tests/PHPStan/Rules/Constants/data/overriding-final-constant.php \ --exclude tests/PHPStan/Rules/Properties/data/intersection-types.php \ --exclude tests/PHPStan/Rules/Classes/data/first-class-instantiation-callable.php \ + --exclude tests/PHPStan/Rules/Classes/data/instantiation-callable.php \ src tests compiler/src cs: diff --git a/conf/config.level0.neon b/conf/config.level0.neon index d9e4a0f6bc..ee3cd08b75 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -26,6 +26,7 @@ rules: - PHPStan\Rules\Classes\ExistingClassesInInterfaceExtendsRule - PHPStan\Rules\Classes\ExistingClassInTraitUseRule - PHPStan\Rules\Classes\InstantiationRule + - PHPStan\Rules\Classes\InstantiationCallableRule - PHPStan\Rules\Classes\InvalidPromotedPropertiesRule - PHPStan\Rules\Classes\NewStaticRule - PHPStan\Rules\Classes\NonClassAttributeClassRule @@ -164,6 +165,14 @@ services: checkClassCaseSensitivity: %checkClassCaseSensitivity% checkThisOnly: %checkThisOnly% + - + class: PHPStan\Rules\Functions\FunctionCallableRule + arguments: + checkFunctionNameCase: %checkFunctionNameCase% + reportMaybes: %reportMaybes% + tags: + - phpstan.rules.rule + - class: PHPStan\Rules\Properties\OverridingPropertyRule arguments: diff --git a/src/Php/PhpVersion.php b/src/Php/PhpVersion.php index 4e6b02dab1..13d3f953e7 100644 --- a/src/Php/PhpVersion.php +++ b/src/Php/PhpVersion.php @@ -162,4 +162,9 @@ public function hasTentativeReturnTypes(): bool return $this->versionId >= 80100; } + public function supportsFirstClassCallables(): bool + { + return $this->versionId >= 80100; + } + } diff --git a/src/Rules/Classes/InstantiationCallableRule.php b/src/Rules/Classes/InstantiationCallableRule.php new file mode 100644 index 0000000000..7c0b49b130 --- /dev/null +++ b/src/Rules/Classes/InstantiationCallableRule.php @@ -0,0 +1,28 @@ + + */ +class InstantiationCallableRule implements \PHPStan\Rules\Rule +{ + + public function getNodeType(): string + { + return InstantiationCallableNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + return [ + RuleErrorBuilder::message('Cannot create callable from the new operator.')->nonIgnorable()->build(), + ]; + } + +} diff --git a/src/Rules/Functions/FunctionCallableRule.php b/src/Rules/Functions/FunctionCallableRule.php new file mode 100644 index 0000000000..02ba6900d9 --- /dev/null +++ b/src/Rules/Functions/FunctionCallableRule.php @@ -0,0 +1,125 @@ + + */ +class FunctionCallableRule implements \PHPStan\Rules\Rule +{ + + private ReflectionProvider $reflectionProvider; + + private RuleLevelHelper $ruleLevelHelper; + + private PhpVersion $phpVersion; + + private bool $checkFunctionNameCase; + + private bool $reportMaybes; + + public function __construct(ReflectionProvider $reflectionProvider, RuleLevelHelper $ruleLevelHelper, PhpVersion $phpVersion, bool $checkFunctionNameCase, bool $reportMaybes) + { + $this->reflectionProvider = $reflectionProvider; + $this->ruleLevelHelper = $ruleLevelHelper; + $this->phpVersion = $phpVersion; + $this->checkFunctionNameCase = $checkFunctionNameCase; + $this->reportMaybes = $reportMaybes; + } + + public function getNodeType(): string + { + return FunctionCallableNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$this->phpVersion->supportsFirstClassCallables()) { + return [ + RuleErrorBuilder::message('First-class callables are supported only on PHP 8.1 and later.') + ->nonIgnorable() + ->build(), + ]; + } + + $functionName = $node->getName(); + if ($functionName instanceof Node\Name) { + $functionNameName = $functionName->toString(); + if ($this->reflectionProvider->hasFunction($functionName, $scope)) { + if ($this->checkFunctionNameCase) { + $function = $this->reflectionProvider->getFunction($functionName, $scope); + + /** @var string $calledFunctionName */ + $calledFunctionName = $this->reflectionProvider->resolveFunctionName($functionName, $scope); + if ( + strtolower($function->getName()) === strtolower($calledFunctionName) + && $function->getName() !== $calledFunctionName + ) { + return [ + RuleErrorBuilder::message(sprintf( + 'Call to function %s() with incorrect case: %s', + $function->getName(), + $functionNameName + ))->build(), + ]; + } + } + + return []; + } + + if ($scope->isInFunctionExists($functionNameName)) { + return []; + } + + return [ + RuleErrorBuilder::message(sprintf('Function %s not found.', $functionNameName)) + ->build(), + ]; + } + + $typeResult = $this->ruleLevelHelper->findTypeToCheck( + $scope, + NullsafeOperatorHelper::getNullsafeShortcircuitedExprRespectingScope($scope, $functionName), + 'Creating callable from an unknown class %s.', + static function (Type $type): bool { + return $type->isCallable()->yes(); + } + ); + $type = $typeResult->getType(); + if ($type instanceof ErrorType) { + return $typeResult->getUnknownClassErrors(); + } + + $isCallable = $type->isCallable(); + if ($isCallable->no()) { + return [ + RuleErrorBuilder::message( + sprintf('Trying to create callable from %s but it\'s not a callable.', $type->describe(VerbosityLevel::value())) + )->build(), + ]; + } + if ($this->reportMaybes && $isCallable->maybe()) { + return [ + RuleErrorBuilder::message( + sprintf('Trying to create callable from %s but it might not be a callable.', $type->describe(VerbosityLevel::value())) + )->build(), + ]; + } + + return []; + } + +} diff --git a/tests/PHPStan/Rules/Classes/InstantiationCallableRuleTest.php b/tests/PHPStan/Rules/Classes/InstantiationCallableRuleTest.php new file mode 100644 index 0000000000..427c0b3a91 --- /dev/null +++ b/tests/PHPStan/Rules/Classes/InstantiationCallableRuleTest.php @@ -0,0 +1,32 @@ + + */ +class InstantiationCallableRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new InstantiationCallableRule(); + } + + public function testRule(): void + { + if (!self::$useStaticReflectionProvider) { + self::markTestSkipped('Test requires static reflection.'); + } + $this->analyse([__DIR__ . '/data/instantiation-callable.php'], [ + [ + 'Cannot create callable from the new operator.', + 11, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Classes/data/instantiation-callable.php b/tests/PHPStan/Rules/Classes/data/instantiation-callable.php new file mode 100644 index 0000000000..0a3a4d540f --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/instantiation-callable.php @@ -0,0 +1,14 @@ + + */ +class FunctionCallableRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + $reflectionProvider = $this->createReflectionProvider(); + + return new FunctionCallableRule( + $reflectionProvider, + new RuleLevelHelper($reflectionProvider, true, false, true, false), + new PhpVersion(PHP_VERSION_ID), + true, + true + ); + } + + public function testNotSupportedOnOlderVersions(): void + { + if (PHP_VERSION_ID >= 80100) { + self::markTestSkipped('Test runs on PHP < 8.1.'); + } + if (!self::$useStaticReflectionProvider) { + self::markTestSkipped('Test requires static reflection.'); + } + + $this->analyse([__DIR__ . '/data/function-callable-not-supported.php'], [ + [ + 'First-class callables are supported only on PHP 8.1 and later.', + 10, + ], + ]); + } + + public function testRule(): void + { + if (PHP_VERSION_ID < 80100) { + self::markTestSkipped('Test requires PHP 8.1.'); + } + + $this->analyse([__DIR__ . '/data/function-callable.php'], [ + [ + 'Function nonexistent not found.', + 13, + ], + [ + 'Trying to create callable from string but it might not be a callable.', + 19, + ], + [ + 'Trying to create callable from 1 but it\'s not a callable.', + 33, + ], + [ + 'Call to function strlen() with incorrect case: StrLen', + 38, + ], + [ + 'Trying to create callable from 1|(callable(): mixed) but it might not be a callable.', + 47, + ], + [ + 'Creating callable from an unknown class FunctionCallable\Nonexistent.', + 52, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Functions/data/function-callable-not-supported.php b/tests/PHPStan/Rules/Functions/data/function-callable-not-supported.php new file mode 100644 index 0000000000..534baa8174 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/function-callable-not-supported.php @@ -0,0 +1,13 @@ += 8.1 + +namespace FunctionCallableNotSupported; + +class Foo +{ + + public function doFoo(): void + { + $f = json_encode(...); + } + +} diff --git a/tests/PHPStan/Rules/Functions/data/function-callable.php b/tests/PHPStan/Rules/Functions/data/function-callable.php new file mode 100644 index 0000000000..8fe1d46880 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/function-callable.php @@ -0,0 +1,55 @@ += 8.1 + +namespace FunctionCallable; + +use function function_exists; + +class Foo +{ + + public function doFoo(string $s): void + { + strlen(...); + nonexistent(...); + + if (function_exists('blabla')) { + blabla(...); + } + + $s(...); + if (function_exists($s)) { + $s(...); + } + } + + public function doBar(): void + { + $f = function (): void { + + }; + $f(...); + + $i = 1; + $i(...); + } + + public function doBaz(): void + { + StrLen(...); + } + + public function doLorem(callable $cb): void + { + if (rand(0, 1)) { + $cb = 1; + } + + $f = $cb(...); + } + + public function doIpsum(Nonexistent $obj): void + { + $f = $obj(...); + } + +} From 6573959829e3d07e1fa2a93845c973d47a12fdfd Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 16 Nov 2021 11:34:46 +0100 Subject: [PATCH 0567/1284] Do not run PHP bug workaround on PHP 8+ where it's fixed --- src/Reflection/Php/PhpMethodReflection.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Reflection/Php/PhpMethodReflection.php b/src/Reflection/Php/PhpMethodReflection.php index b39acc065e..015cb87de1 100644 --- a/src/Reflection/Php/PhpMethodReflection.php +++ b/src/Reflection/Php/PhpMethodReflection.php @@ -193,6 +193,10 @@ public function getName(): string $name = $this->reflection->getName(); $lowercaseName = strtolower($name); if ($lowercaseName === $name) { + if (PHP_VERSION_ID >= 80000) { + return $name; + } + // fix for https://bugs.php.net/bug.php?id=74939 foreach ($this->getDeclaringClass()->getNativeReflection()->getTraitAliases() as $traitTarget) { $correctName = $this->getMethodNameWithCorrectCase($name, $traitTarget); From 08f3e2d02329fa39561356c630d8d561631419d9 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 16 Nov 2021 12:14:40 +0100 Subject: [PATCH 0568/1284] functionMap - removed functions that are reserved keywords and separate AST nodes --- resources/functionMap.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 57ab6c1bfc..6b36b462c1 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -57,7 +57,6 @@ return [ '_' => ['string', 'message'=>'string'], -'__halt_compiler' => ['void'], 'abs' => ['0|positive-int', 'number'=>'int'], 'abs\'1' => ['float', 'number'=>'float'], 'abs\'2' => ['float|0|positive-int', 'number'=>'string'], @@ -2293,7 +2292,6 @@ 'eio_unlink' => ['resource', 'path'=>'string', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'], 'eio_utime' => ['resource', 'path'=>'string', 'atime'=>'float', 'mtime'=>'float', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'], 'eio_write' => ['resource', 'fd'=>'mixed', 'str'=>'string', 'length='=>'int', 'offset='=>'int', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'], -'empty' => ['bool', 'var'=>'mixed'], 'EmptyIterator::current' => ['mixed'], 'EmptyIterator::key' => ['mixed'], 'EmptyIterator::next' => ['void'], @@ -2369,7 +2367,6 @@ 'Ev::suspend' => ['void'], 'Ev::time' => ['float'], 'Ev::verify' => ['void'], -'eval' => ['mixed', 'code_str'=>'string'], 'EvCheck::__construct' => ['void', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'], 'EvCheck::createStopped' => ['object', 'callback'=>'string', 'data='=>'string', 'priority='=>'string'], 'EvChild::__construct' => ['void', 'pid'=>'int', 'trace'=>'bool', 'callback'=>'callable', 'data='=>'mixed', 'priority='=>'int'], @@ -2635,7 +2632,6 @@ 'exif_read_data' => ['array|false', 'filename'=>'string|resource', 'sections_needed='=>'string', 'sub_arrays='=>'bool', 'read_thumbnail='=>'bool'], 'exif_tagname' => ['string|false', 'index'=>'int'], 'exif_thumbnail' => ['string|false', 'filename'=>'string', '&w_width='=>'int', '&w_height='=>'int', '&w_imagetype='=>'int'], -'exit' => ['', 'status'=>'string|int<0,254>'], 'exp' => ['float', 'number'=>'float'], 'expect_expectl' => ['int', 'expect'=>'resource', 'cases'=>'array', 'match='=>'array'], 'expect_popen' => ['resource|false', 'command'=>'string'], @@ -5657,7 +5653,6 @@ 'is_uploaded_file' => ['bool', 'path'=>'string'], 'is_writable' => ['bool', 'filename'=>'string'], 'is_writeable' => ['bool', 'filename'=>'string'], -'isset' => ['bool', 'var'=>'mixed', '...rest='=>'mixed'], 'Iterator::current' => ['mixed'], 'Iterator::key' => ['mixed'], 'Iterator::next' => ['void'], @@ -8842,7 +8837,6 @@ 'preg_replace_callback_array' => ['string|array|null', 'pattern'=>'array', 'subject'=>'string|array', 'limit='=>'int', '&w_count='=>'int'], 'preg_split' => ['array|false', 'pattern'=>'string', 'subject'=>'string', 'limit='=>'?int', 'flags='=>'int'], 'prev' => ['mixed', '&rw_array_arg'=>'array|object'], -'print' => ['int', 'arg'=>'string'], 'print_r' => ['string|true', 'var'=>'mixed', 'return='=>'bool'], 'printf' => ['int', 'format'=>'string', '...values='=>'string|int|float'], 'proc_close' => ['int', 'process'=>'resource'], @@ -12643,7 +12637,6 @@ 'unpack' => ['array|false', 'format'=>'string', 'data'=>'string', 'offset='=>'int'], 'unregister_tick_function' => ['void', 'function_name'=>'callable'], 'unserialize' => ['mixed', 'variable_representation'=>'string', 'allowed_classes='=>'array{allowed_classes?:string[]|bool}'], -'unset' => ['void', 'var='=>'mixed', '...args='=>'mixed'], 'untaint' => ['bool', '&rw_string'=>'string', '&...rw_strings='=>'string'], 'uopz_add_function' => ['bool', 'class'=>'string', 'function'=>'string', 'handler'=>'Closure', '$flags'=>'bool', '$all'=>'bool'], 'uopz_add_function\1' => ['bool', 'function'=>'string', 'handler'=>'Closure', '$flags'=>'bool'], From cb1050bf1ab327ae0a3b2b1ce9635eeb74577685 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 16 Nov 2021 12:57:56 +0100 Subject: [PATCH 0569/1284] Fix test --- tests/PHPStan/Rules/Functions/FunctionCallableRuleTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/PHPStan/Rules/Functions/FunctionCallableRuleTest.php b/tests/PHPStan/Rules/Functions/FunctionCallableRuleTest.php index 5b183e6755..3f928eb468 100644 --- a/tests/PHPStan/Rules/Functions/FunctionCallableRuleTest.php +++ b/tests/PHPStan/Rules/Functions/FunctionCallableRuleTest.php @@ -73,6 +73,7 @@ public function testRule(): void [ 'Creating callable from an unknown class FunctionCallable\Nonexistent.', 52, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', ], ]); } From 1d72885c3be12708c2fc8a2475716612c751e676 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 16 Nov 2021 12:59:01 +0100 Subject: [PATCH 0570/1284] Fix test --- tests/PHPStan/Rules/Classes/InstantiationRuleTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php index 5d47375d3f..9d9d1d598e 100644 --- a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php +++ b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php @@ -350,8 +350,8 @@ public function testBug4681(): void public function testFirstClassCallable(): void { - if (PHP_VERSION_ID < 80100 && !self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires PHP 8.1.'); + if (PHP_VERSION_ID < 80100 || !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.1 and static reflection.'); } // handled by a different rule From 7c8e81dcf88378039ac57da459b4288b7e620a59 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 16 Nov 2021 13:01:15 +0100 Subject: [PATCH 0571/1284] Promoted properties do not have a default value --- src/Analyser/NodeScopeResolver.php | 2 +- ...ncompatibleDefaultParameterTypeRuleTest.php | 18 ++++++++++++++++++ .../default-value-for-promoted-property.php | 0 ...tValueTypesAssignedToPropertiesRuleTest.php | 18 ------------------ .../Properties/ReadOnlyPropertyRuleTest.php | 4 ++++ .../Properties/data/read-only-property.php | 7 +++++++ 6 files changed, 30 insertions(+), 19 deletions(-) rename tests/PHPStan/Rules/{Properties => Methods}/data/default-value-for-promoted-property.php (100%) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 2f22e74f78..b55a59cf1c 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -521,7 +521,7 @@ private function processStmtNode( $param->var->name, $param->flags, $param->type, - $param->default, + null, $phpDoc, true, $param diff --git a/tests/PHPStan/Rules/Methods/IncompatibleDefaultParameterTypeRuleTest.php b/tests/PHPStan/Rules/Methods/IncompatibleDefaultParameterTypeRuleTest.php index 6c4dfc45a6..389e96e526 100644 --- a/tests/PHPStan/Rules/Methods/IncompatibleDefaultParameterTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/IncompatibleDefaultParameterTypeRuleTest.php @@ -59,4 +59,22 @@ public function testNewInInitializers(): void ]); } + public function testDefaultValueForPromotedProperty(): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires static reflection.'); + } + + $this->analyse([__DIR__ . '/data/default-value-for-promoted-property.php'], [ + [ + 'Default value of the parameter #1 $foo (string) of method DefaultValueForPromotedProperty\Foo::__construct() is incompatible with type int.', + 9, + ], + [ + 'Default value of the parameter #2 $foo (string) of method DefaultValueForPromotedProperty\Foo::__construct() is incompatible with type int.', + 10, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Properties/data/default-value-for-promoted-property.php b/tests/PHPStan/Rules/Methods/data/default-value-for-promoted-property.php similarity index 100% rename from tests/PHPStan/Rules/Properties/data/default-value-for-promoted-property.php rename to tests/PHPStan/Rules/Methods/data/default-value-for-promoted-property.php diff --git a/tests/PHPStan/Rules/Properties/DefaultValueTypesAssignedToPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/DefaultValueTypesAssignedToPropertiesRuleTest.php index 42064cd3e8..9588d3f87d 100644 --- a/tests/PHPStan/Rules/Properties/DefaultValueTypesAssignedToPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/DefaultValueTypesAssignedToPropertiesRuleTest.php @@ -47,22 +47,4 @@ public function testDefaultValueForNativePropertyType(): void ]); } - public function testDefaultValueForPromotedProperty(): void - { - if (!self::$useStaticReflectionProvider) { - $this->markTestSkipped('Test requires static reflection.'); - } - - $this->analyse([__DIR__ . '/data/default-value-for-promoted-property.php'], [ - [ - 'Property DefaultValueForPromotedProperty\Foo::$foo (int) does not accept default value of type string.', - 9, - ], - [ - 'Property DefaultValueForPromotedProperty\Foo::$foo (int) does not accept default value of type string.', - 10, - ], - ]); - } - } diff --git a/tests/PHPStan/Rules/Properties/ReadOnlyPropertyRuleTest.php b/tests/PHPStan/Rules/Properties/ReadOnlyPropertyRuleTest.php index 7bcd73b7c9..c88a70e9ee 100644 --- a/tests/PHPStan/Rules/Properties/ReadOnlyPropertyRuleTest.php +++ b/tests/PHPStan/Rules/Properties/ReadOnlyPropertyRuleTest.php @@ -46,6 +46,10 @@ public function dataRule(): array 'Readonly property cannot have a default value.', 10, ], + [ + 'Readonly properties are supported only on PHP 8.1 and later.', + 16, + ], ], ], [ diff --git a/tests/PHPStan/Rules/Properties/data/read-only-property.php b/tests/PHPStan/Rules/Properties/data/read-only-property.php index a360e2419b..979cf94e91 100644 --- a/tests/PHPStan/Rules/Properties/data/read-only-property.php +++ b/tests/PHPStan/Rules/Properties/data/read-only-property.php @@ -10,3 +10,10 @@ class Foo private readonly int $baz = 0; } + +final class ErrorResponse +{ + public function __construct(public readonly string $message = '') + { + } +} From 2652f2dc6c713ba2200df922a62fe651f2e4445e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 16 Nov 2021 13:12:03 +0100 Subject: [PATCH 0572/1284] CleaningParser - do not remove inline `@var` tags --- src/Parser/CleaningVisitor.php | 16 +++++++++----- src/Type/FileTypeMapper.php | 2 +- .../PHPStan/Parser/data/cleaning-1-after.php | 11 ++++++++++ .../PHPStan/Parser/data/cleaning-1-before.php | 22 +++++++++++++++++++ 4 files changed, 44 insertions(+), 7 deletions(-) diff --git a/src/Parser/CleaningVisitor.php b/src/Parser/CleaningVisitor.php index 8af4c10ad2..c8dcb4ff10 100644 --- a/src/Parser/CleaningVisitor.php +++ b/src/Parser/CleaningVisitor.php @@ -20,17 +20,17 @@ public function __construct() public function enterNode(Node $node): ?Node { if ($node instanceof Node\Stmt\Function_) { - $node->stmts = $this->keepVariadicsAndYields($node->stmts); + $node->stmts = $this->keepVariadicsAndYieldsAndInlineVars($node->stmts); return $node; } if ($node instanceof Node\Stmt\ClassMethod && $node->stmts !== null) { - $node->stmts = $this->keepVariadicsAndYields($node->stmts); + $node->stmts = $this->keepVariadicsAndYieldsAndInlineVars($node->stmts); return $node; } if ($node instanceof Node\Expr\Closure) { - $node->stmts = $this->keepVariadicsAndYields($node->stmts); + $node->stmts = $this->keepVariadicsAndYieldsAndInlineVars($node->stmts); return $node; } @@ -41,7 +41,7 @@ public function enterNode(Node $node): ?Node * @param Node\Stmt[] $stmts * @return Node\Stmt[] */ - private function keepVariadicsAndYields(array $stmts): array + private function keepVariadicsAndYieldsAndInlineVars(array $stmts): array { $results = $this->nodeFinder->find($stmts, static function (Node $node): bool { if ($node instanceof Node\Expr\YieldFrom || $node instanceof Node\Expr\Yield_) { @@ -50,6 +50,9 @@ private function keepVariadicsAndYields(array $stmts): array if ($node instanceof Node\Expr\FuncCall && $node->name instanceof Node\Name) { return in_array($node->name->toLowerString(), ParametersAcceptor::VARIADIC_FUNCTIONS, true); } + if ($node instanceof Node\Stmt && $node->getDocComment() !== null) { + return strpos($node->getDocComment()->getText(), '@var') !== false; + } return false; }); @@ -59,11 +62,12 @@ private function keepVariadicsAndYields(array $stmts): array $newStmts[] = new Node\Stmt\Expression($result); continue; } - if (!$result instanceof Node\Expr\FuncCall) { + if ($result instanceof Node\Expr\FuncCall && $result->name instanceof Node\Name) { + $newStmts[] = new Node\Stmt\Expression(new Node\Expr\FuncCall(new Node\Name\FullyQualified($result->name), [], $result->getAttributes())); continue; } - $newStmts[] = new Node\Stmt\Expression(new Node\Expr\FuncCall(new Node\Name\FullyQualified('func_get_args'))); + $newStmts[] = new Node\Stmt\Nop($result->getAttributes()); } return $newStmts; diff --git a/src/Type/FileTypeMapper.php b/src/Type/FileTypeMapper.php index ddbfd42485..bf0b251c0a 100644 --- a/src/Type/FileTypeMapper.php +++ b/src/Type/FileTypeMapper.php @@ -217,7 +217,7 @@ private function shouldPhpDocNodeBeCachedToDisk(PhpDocNode $phpDocNode): bool private function getResolvedPhpDocMap(string $fileName): array { if (!isset($this->memoryCache[$fileName])) { - $cacheKey = sprintf('%s-phpdocstring-v10-function-name-stack', $fileName); + $cacheKey = sprintf('%s-phpdocstring-v11-inline-vars', $fileName); $variableCacheKey = implode(',', array_map(static function (array $file): string { return sprintf('%s-%d', $file['filename'], $file['modifiedTime']); }, $this->getCachedDependentFilesWithTimestamps($fileName))); diff --git a/tests/PHPStan/Parser/data/cleaning-1-after.php b/tests/PHPStan/Parser/data/cleaning-1-after.php index 0f22d85600..3eb72d6ca5 100644 --- a/tests/PHPStan/Parser/data/cleaning-1-after.php +++ b/tests/PHPStan/Parser/data/cleaning-1-after.php @@ -31,3 +31,14 @@ public function both() \func_get_args(); } } +class InlineVars +{ + public function doFoo() + { + /** @var Test */ + /** @var Test2 */ + /** @var Test3 */ + yield; + \func_get_args(); + } +} diff --git a/tests/PHPStan/Parser/data/cleaning-1-before.php b/tests/PHPStan/Parser/data/cleaning-1-before.php index a412a0d217..a0cf69ae15 100644 --- a/tests/PHPStan/Parser/data/cleaning-1-before.php +++ b/tests/PHPStan/Parser/data/cleaning-1-before.php @@ -54,3 +54,25 @@ public function both() } } + +class InlineVars +{ + public function doFoo() + { + /** @var Test */ + $foo = doFoo(); + + /** @var Test2 */ + $foo = doFoo(); + + /** @var Test3 */ + $foo = doFoo(); + + if (rand(0, 1)) { + yield; + } + if (rand(0, 1)) { + func_get_args(); + } + } +} From 6366066f3b28a5bdef0c6a3d533c291a8490e1f8 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 16 Nov 2021 13:28:54 +0100 Subject: [PATCH 0573/1284] AttributesCheck - do not report named arguments in attributes --- src/Rules/AttributesCheck.php | 5 +++- src/Rules/FunctionCallParametersCheck.php | 2 +- .../Methods/MethodAttributesRuleTest.php | 17 ++++++++++++- tests/PHPStan/Rules/Methods/data/bug-5898.php | 25 +++++++++++++++++++ 4 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 tests/PHPStan/Rules/Methods/data/bug-5898.php diff --git a/src/Rules/AttributesCheck.php b/src/Rules/AttributesCheck.php index 205e3994d4..31da561a70 100644 --- a/src/Rules/AttributesCheck.php +++ b/src/Rules/AttributesCheck.php @@ -93,11 +93,14 @@ public function check( $attributeClassName = SprintfHelper::escapeFormatString($attributeClass->getDisplayName()); + $nodeAttributes = $attribute->getAttributes(); + $nodeAttributes['isAttribute'] = true; + $parameterErrors = $this->functionCallParametersCheck->check( ParametersAcceptorSelector::selectSingle($attributeConstructor->getVariants()), $scope, $attributeConstructor->getDeclaringClass()->isBuiltin(), - new New_($attribute->name, $attribute->args, $attribute->getAttributes()), + new New_($attribute->name, $attribute->args, $nodeAttributes), [ 'Attribute class ' . $attributeClassName . ' constructor invoked with %d parameter, %d required.', 'Attribute class ' . $attributeClassName . ' constructor invoked with %d parameters, %d required.', diff --git a/src/Rules/FunctionCallParametersCheck.php b/src/Rules/FunctionCallParametersCheck.php index f672293d66..aee6a284bb 100644 --- a/src/Rules/FunctionCallParametersCheck.php +++ b/src/Rules/FunctionCallParametersCheck.php @@ -169,7 +169,7 @@ public function check( ]; } - if ($hasNamedArguments && !$this->phpVersion->supportsNamedArguments()) { + if ($hasNamedArguments && !$this->phpVersion->supportsNamedArguments() && !$funcCall->getAttribute('isAttribute', false)) { $errors[] = RuleErrorBuilder::message('Named arguments are supported only on PHP 8.0 and later.')->line($funcCall->getLine())->nonIgnorable()->build(); } diff --git a/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php b/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php index badcc06643..3b77608fc9 100644 --- a/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Methods/MethodAttributesRuleTest.php @@ -18,6 +18,9 @@ class MethodAttributesRuleTest extends RuleTestCase { + /** @var int */ + private $phpVersion; + protected function getRule(): Rule { $reflectionProvider = $this->createReflectionProvider(); @@ -27,7 +30,7 @@ protected function getRule(): Rule new FunctionCallParametersCheck( new RuleLevelHelper($reflectionProvider, true, false, true, false), new NullsafeCheck(), - new PhpVersion(80000), + new PhpVersion($this->phpVersion), new UnresolvableTypeHelper(), true, true, @@ -45,6 +48,8 @@ public function testRule(): void $this->markTestSkipped('Test requires PHP 8.0.'); } + $this->phpVersion = 80000; + $this->analyse([__DIR__ . '/data/method-attributes.php'], [ [ 'Attribute class MethodAttributes\Foo does not have the method target.', @@ -53,4 +58,14 @@ public function testRule(): void ]); } + public function testBug5898(): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->phpVersion = 70400; + $this->analyse([__DIR__ . '/data/bug-5898.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-5898.php b/tests/PHPStan/Rules/Methods/data/bug-5898.php new file mode 100644 index 0000000000..cbf5e3cb47 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-5898.php @@ -0,0 +1,25 @@ + Date: Tue, 16 Nov 2021 13:41:02 +0100 Subject: [PATCH 0574/1284] Added FunctionTypeSpecifyingExtension for array_is_list --- conf/config.neon | 5 ++ ...yIsListFunctionTypeSpecifyingExtension.php | 65 +++++++++++++++++++ .../Analyser/NodeScopeResolverTest.php | 4 ++ .../data/array-is-list-type-specifying.php | 35 ++++++++++ ...mpossibleCheckTypeFunctionCallRuleTest.php | 26 ++++++++ .../data/check-type-function-call.php | 45 +++++++++++++ 6 files changed, 180 insertions(+) create mode 100644 src/Type/Php/ArrayIsListFunctionTypeSpecifyingExtension.php create mode 100644 tests/PHPStan/Analyser/data/array-is-list-type-specifying.php diff --git a/conf/config.neon b/conf/config.neon index a43e199216..a829e12ea5 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1396,6 +1396,11 @@ services: tags: - phpstan.typeSpecifier.functionTypeSpecifyingExtension + - + class: PHPStan\Type\Php\ArrayIsListFunctionTypeSpecifyingExtension + tags: + - phpstan.typeSpecifier.functionTypeSpecifyingExtension + - class: PHPStan\Type\Php\JsonThrowOnErrorDynamicReturnTypeExtension tags: diff --git a/src/Type/Php/ArrayIsListFunctionTypeSpecifyingExtension.php b/src/Type/Php/ArrayIsListFunctionTypeSpecifyingExtension.php new file mode 100644 index 0000000000..359641a7c0 --- /dev/null +++ b/src/Type/Php/ArrayIsListFunctionTypeSpecifyingExtension.php @@ -0,0 +1,65 @@ +getName()) === 'array_is_list' + && !$context->null(); + } + + public function specifyTypes( + FunctionReflection $functionReflection, + FuncCall $node, + Scope $scope, + TypeSpecifierContext $context + ): SpecifiedTypes + { + $arrayArg = $node->getArgs()[0]->value ?? null; + if ($arrayArg === null) { + return new SpecifiedTypes(); + } + + $valueType = $scope->getType($arrayArg); + if ($valueType instanceof ConstantArrayType) { + return $this->typeSpecifier->create($arrayArg, $valueType->getValuesArray(), $context, false, $scope); + } + + return $this->typeSpecifier->create( + $arrayArg, + TypeCombinator::intersect(new ArrayType(new IntegerType(), $valueType->getIterableValueType()), ...TypeUtils::getAccessoryTypes($valueType)), + $context, + false, + $scope + ); + } + + public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void + { + $this->typeSpecifier = $typeSpecifier; + } + +} diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index ab0aa88a3c..51c360228a 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -555,6 +555,10 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/first-class-callables.php'); } + if (PHP_VERSION_ID >= 80100) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/array-is-list-type-specifying.php'); + } + yield from $this->gatherAssertTypes(__DIR__ . '/data/filesystem-functions.php'); } diff --git a/tests/PHPStan/Analyser/data/array-is-list-type-specifying.php b/tests/PHPStan/Analyser/data/array-is-list-type-specifying.php new file mode 100644 index 0000000000..d3779c9f66 --- /dev/null +++ b/tests/PHPStan/Analyser/data/array-is-list-type-specifying.php @@ -0,0 +1,35 @@ +', $foo); + } else { + assertType('array', $foo); + } +} + +$bar = [1, 2, 3]; + +if (array_is_list($bar)) { + assertType('array{1, 2, 3}', $bar); +} else { + assertType('*NEVER*', $bar); +} + +/** @var array $foo */ + +if (array_is_list($foo)) { + assertType('array', $foo); +} else { + assertType('array', $foo); +} + +$baz = []; + +if (array_is_list($baz)) { + assertType('array{}', $baz); +} diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php index c15decc0a7..c9798c31e3 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php @@ -238,6 +238,19 @@ public function testImpossibleCheckTypeFunctionCall(): void 'Call to function property_exists() with CheckTypeFunctionCall\Bug2221 and \'foo\' will always evaluate to true.', 786, ], + [ + 'Call to function array_is_list() with array will always evaluate to false.', + 857, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ], + [ + "Call to function array_is_list() with array{foo: 'bar', bar: 'baz'} will always evaluate to false.", + 884, + ], + [ + 'Call to function array_is_list() with array{0: \'foo\', foo: \'bar\', bar: \'baz\'} will always evaluate to false.', + 888, + ], ] ); } @@ -338,6 +351,19 @@ public function testImpossibleCheckTypeFunctionCallWithoutAlwaysTrue(): void 'Call to function is_numeric() with \'blabla\' will always evaluate to false.', 693, ], + [ + 'Call to function array_is_list() with array will always evaluate to false.', + 857, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ], + [ + "Call to function array_is_list() with array{foo: 'bar', bar: 'baz'} will always evaluate to false.", + 884, + ], + [ + 'Call to function array_is_list() with array{0: \'foo\', foo: \'bar\', bar: \'baz\'} will always evaluate to false.', + 888, + ], ] ); } diff --git a/tests/PHPStan/Rules/Comparison/data/check-type-function-call.php b/tests/PHPStan/Rules/Comparison/data/check-type-function-call.php index cf6bcad137..aa056dc9b6 100644 --- a/tests/PHPStan/Rules/Comparison/data/check-type-function-call.php +++ b/tests/PHPStan/Rules/Comparison/data/check-type-function-call.php @@ -845,3 +845,48 @@ public function doBar($std, $stdClassesOrNull): void } } + +class ArrayIsList +{ + + /** + * @param array $stringKeyedArray + */ + public function doFoo(array $stringKeyedArray) + { + if (array_is_list($stringKeyedArray)) { + + } + } + + /** + * @param array $mixedArray + */ + public function doBar(array $mixedArray) + { + if (array_is_list($mixedArray)) { + // Fine + } + } + + /** + * @param array $arrayKeyedInts + */ + public function doBaz(array $arrayKeyedInts) + { + if (array_is_list($arrayKeyedInts)) { + // Fine + } + } + + public function doBax() + { + if (array_is_list(['foo' => 'bar', 'bar' => 'baz'])) { + + } + + if (array_is_list(['foo', 'foo' => 'bar', 'bar' => 'baz'])) { + // Fine + } + } +} From 084cb40924f3f0ed126dfcddf038b252e4779c0b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 16 Nov 2021 13:48:49 +0100 Subject: [PATCH 0575/1284] Fix --- src/Rules/FunctionCallParametersCheck.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Rules/FunctionCallParametersCheck.php b/src/Rules/FunctionCallParametersCheck.php index aee6a284bb..45cb087293 100644 --- a/src/Rules/FunctionCallParametersCheck.php +++ b/src/Rules/FunctionCallParametersCheck.php @@ -169,7 +169,7 @@ public function check( ]; } - if ($hasNamedArguments && !$this->phpVersion->supportsNamedArguments() && !$funcCall->getAttribute('isAttribute', false)) { + if ($hasNamedArguments && !$this->phpVersion->supportsNamedArguments() && !(bool) $funcCall->getAttribute('isAttribute', false)) { $errors[] = RuleErrorBuilder::message('Named arguments are supported only on PHP 8.0 and later.')->line($funcCall->getLine())->nonIgnorable()->build(); } From 602ce24799ef04a64d132d4747a17aea40ad72ba Mon Sep 17 00:00:00 2001 From: Craig Francis Date: Tue, 16 Nov 2021 13:12:24 +0000 Subject: [PATCH 0576/1284] Support 'literal-string' with Encapsed Strings --- src/Analyser/MutatingScope.php | 22 +++++++++++++++---- .../PHPStan/Analyser/data/literal-string.php | 2 ++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index c67928aa67..5235fa89a1 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -1459,13 +1459,27 @@ private function resolveType(Expr $node): Type continue; } + $isNonEmpty = false; + $isLiteralString = true; foreach ($parts as $partType) { if ($partType->isNonEmptyString()->yes()) { - return new IntersectionType([ - new StringType(), - new AccessoryNonEmptyStringType(), - ]); + $isNonEmpty = true; } + if (!$partType->isLiteralString()->yes()) { + $isLiteralString = false; + } + } + + $accessoryTypes = []; + if ($isNonEmpty === true) { + $accessoryTypes[] = new AccessoryNonEmptyStringType(); + } + if ($isLiteralString === true) { + $accessoryTypes[] = new AccessoryLiteralStringType(); + } + if (count($accessoryTypes) > 0) { + $accessoryTypes[] = new StringType(); + return new IntersectionType($accessoryTypes); } return new StringType(); diff --git a/tests/PHPStan/Analyser/data/literal-string.php b/tests/PHPStan/Analyser/data/literal-string.php index aae44ef3f6..28fce66175 100644 --- a/tests/PHPStan/Analyser/data/literal-string.php +++ b/tests/PHPStan/Analyser/data/literal-string.php @@ -15,6 +15,8 @@ public function doFoo($literalString, string $string) assertType('literal-string', '' . $literalString); assertType('literal-string&non-empty-string', $literalString . 'foo'); assertType('literal-string&non-empty-string', 'foo' . $literalString); + assertType('literal-string&non-empty-string', "foo ${literalString}"); + assertType('literal-string&non-empty-string', "${literalString} foo"); assertType('string', $string . ''); assertType('string', '' . $string); assertType('string', $literalString . $string); From 7571c2e65c8392967166acfd979298427a92bb17 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 16 Nov 2021 21:17:45 +0100 Subject: [PATCH 0577/1284] Fix CS --- src/Analyser/MutatingScope.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 5235fa89a1..1059b6c7b8 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -1465,9 +1465,10 @@ private function resolveType(Expr $node): Type if ($partType->isNonEmptyString()->yes()) { $isNonEmpty = true; } - if (!$partType->isLiteralString()->yes()) { - $isLiteralString = false; + if ($partType->isLiteralString()->yes()) { + continue; } + $isLiteralString = false; } $accessoryTypes = []; From f99dda7afda09973660216d08a939870c52cf14b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 16 Nov 2021 21:19:32 +0100 Subject: [PATCH 0578/1284] Fix tests --- ...mpossibleCheckTypeFunctionCallRuleTest.php | 51 +++++++++---------- .../Rules/Comparison/data/array-is-list.php | 48 +++++++++++++++++ .../data/check-type-function-call.php | 45 ---------------- 3 files changed, 73 insertions(+), 71 deletions(-) create mode 100644 tests/PHPStan/Rules/Comparison/data/array-is-list.php diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php index c9798c31e3..a4fa004b32 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php @@ -238,19 +238,6 @@ public function testImpossibleCheckTypeFunctionCall(): void 'Call to function property_exists() with CheckTypeFunctionCall\Bug2221 and \'foo\' will always evaluate to true.', 786, ], - [ - 'Call to function array_is_list() with array will always evaluate to false.', - 857, - 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', - ], - [ - "Call to function array_is_list() with array{foo: 'bar', bar: 'baz'} will always evaluate to false.", - 884, - ], - [ - 'Call to function array_is_list() with array{0: \'foo\', foo: \'bar\', bar: \'baz\'} will always evaluate to false.', - 888, - ], ] ); } @@ -351,19 +338,6 @@ public function testImpossibleCheckTypeFunctionCallWithoutAlwaysTrue(): void 'Call to function is_numeric() with \'blabla\' will always evaluate to false.', 693, ], - [ - 'Call to function array_is_list() with array will always evaluate to false.', - 857, - 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', - ], - [ - "Call to function array_is_list() with array{foo: 'bar', bar: 'baz'} will always evaluate to false.", - 884, - ], - [ - 'Call to function array_is_list() with array{0: \'foo\', foo: \'bar\', bar: \'baz\'} will always evaluate to false.', - 888, - ], ] ); } @@ -439,4 +413,29 @@ public function testBug4999(): void $this->analyse([__DIR__ . '/data/bug-4999.php'], []); } + public function testArrayIsList(): void + { + if (PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + + $this->checkAlwaysTrueCheckTypeFunctionCall = true; + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/array-is-list.php'], [ + [ + 'Call to function array_is_list() with array will always evaluate to false.', + 13, + 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + ], + [ + 'Call to function array_is_list() with array{foo: \'bar\', bar: \'baz\'} will always evaluate to false.', + 40, + ], + [ + 'Call to function array_is_list() with array{0: \'foo\', foo: \'bar\', bar: \'baz\'} will always evaluate to false.', + 44, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Comparison/data/array-is-list.php b/tests/PHPStan/Rules/Comparison/data/array-is-list.php new file mode 100644 index 0000000000..f812dc9a06 --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/array-is-list.php @@ -0,0 +1,48 @@ + $stringKeyedArray + */ + public function doFoo(array $stringKeyedArray) + { + if (array_is_list($stringKeyedArray)) { + + } + } + + /** + * @param array $mixedArray + */ + public function doBar(array $mixedArray) + { + if (array_is_list($mixedArray)) { + // Fine + } + } + + /** + * @param array $arrayKeyedInts + */ + public function doBaz(array $arrayKeyedInts) + { + if (array_is_list($arrayKeyedInts)) { + // Fine + } + } + + public function doBax() + { + if (array_is_list(['foo' => 'bar', 'bar' => 'baz'])) { + + } + + if (array_is_list(['foo', 'foo' => 'bar', 'bar' => 'baz'])) { + // Fine + } + } +} diff --git a/tests/PHPStan/Rules/Comparison/data/check-type-function-call.php b/tests/PHPStan/Rules/Comparison/data/check-type-function-call.php index aa056dc9b6..cf6bcad137 100644 --- a/tests/PHPStan/Rules/Comparison/data/check-type-function-call.php +++ b/tests/PHPStan/Rules/Comparison/data/check-type-function-call.php @@ -845,48 +845,3 @@ public function doBar($std, $stdClassesOrNull): void } } - -class ArrayIsList -{ - - /** - * @param array $stringKeyedArray - */ - public function doFoo(array $stringKeyedArray) - { - if (array_is_list($stringKeyedArray)) { - - } - } - - /** - * @param array $mixedArray - */ - public function doBar(array $mixedArray) - { - if (array_is_list($mixedArray)) { - // Fine - } - } - - /** - * @param array $arrayKeyedInts - */ - public function doBaz(array $arrayKeyedInts) - { - if (array_is_list($arrayKeyedInts)) { - // Fine - } - } - - public function doBax() - { - if (array_is_list(['foo' => 'bar', 'bar' => 'baz'])) { - - } - - if (array_is_list(['foo', 'foo' => 'bar', 'bar' => 'baz'])) { - // Fine - } - } -} From 388561385d6e6cf8ed00d58504518aa7e6ce8353 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 16 Nov 2021 21:35:23 +0100 Subject: [PATCH 0579/1284] FunctionCallableRule - reword --- src/Rules/Functions/FunctionCallableRule.php | 4 ++-- tests/PHPStan/Rules/Functions/FunctionCallableRuleTest.php | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Rules/Functions/FunctionCallableRule.php b/src/Rules/Functions/FunctionCallableRule.php index 02ba6900d9..e8e37104cc 100644 --- a/src/Rules/Functions/FunctionCallableRule.php +++ b/src/Rules/Functions/FunctionCallableRule.php @@ -107,14 +107,14 @@ static function (Type $type): bool { if ($isCallable->no()) { return [ RuleErrorBuilder::message( - sprintf('Trying to create callable from %s but it\'s not a callable.', $type->describe(VerbosityLevel::value())) + sprintf('Creating callable from %s but it\'s not a callable.', $type->describe(VerbosityLevel::value())) )->build(), ]; } if ($this->reportMaybes && $isCallable->maybe()) { return [ RuleErrorBuilder::message( - sprintf('Trying to create callable from %s but it might not be a callable.', $type->describe(VerbosityLevel::value())) + sprintf('Creating callable from %s but it might not be a callable.', $type->describe(VerbosityLevel::value())) )->build(), ]; } diff --git a/tests/PHPStan/Rules/Functions/FunctionCallableRuleTest.php b/tests/PHPStan/Rules/Functions/FunctionCallableRuleTest.php index 3f928eb468..715c22530a 100644 --- a/tests/PHPStan/Rules/Functions/FunctionCallableRuleTest.php +++ b/tests/PHPStan/Rules/Functions/FunctionCallableRuleTest.php @@ -55,11 +55,11 @@ public function testRule(): void 13, ], [ - 'Trying to create callable from string but it might not be a callable.', + 'Creating callable from string but it might not be a callable.', 19, ], [ - 'Trying to create callable from 1 but it\'s not a callable.', + 'Creating callable from 1 but it\'s not a callable.', 33, ], [ @@ -67,7 +67,7 @@ public function testRule(): void 38, ], [ - 'Trying to create callable from 1|(callable(): mixed) but it might not be a callable.', + 'Creating callable from 1|(callable(): mixed) but it might not be a callable.', 47, ], [ From 050f48f6fea74019e48fc9a9f3bef61c89fdee5d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 16 Nov 2021 21:39:21 +0100 Subject: [PATCH 0580/1284] First-class callable of a private method should mark the method as used --- src/Analyser/NodeScopeResolver.php | 4 +-- src/Node/ClassStatementsGatherer.php | 4 +++ src/Node/MethodCallableNode.php | 15 ++++++--- src/Node/StaticMethodCallableNode.php | 15 ++++++--- .../DeadCode/UnusedPrivateMethodRuleTest.php | 9 +++++ .../data/callable-unused-private-method.php | 33 +++++++++++++++++++ 6 files changed, 70 insertions(+), 10 deletions(-) create mode 100644 tests/PHPStan/Rules/DeadCode/data/callable-unused-private-method.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index b55a59cf1c..0d1fcd6cf9 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -1691,9 +1691,9 @@ private function processExprNode(Expr $expr, MutatingScope $scope, callable $nod if ($expr instanceof FuncCall) { $newExpr = new FunctionCallableNode($expr->name, $expr->getAttributes()); } elseif ($expr instanceof MethodCall) { - $newExpr = new MethodCallableNode($expr->var, $expr->name, $expr->getAttributes()); + $newExpr = new MethodCallableNode($expr->var, $expr->name, $expr); } elseif ($expr instanceof StaticCall) { - $newExpr = new StaticMethodCallableNode($expr->class, $expr->name, $expr->getAttributes()); + $newExpr = new StaticMethodCallableNode($expr->class, $expr->name, $expr); } elseif ($expr instanceof New_ && !$expr->class instanceof Class_) { $newExpr = new InstantiationCallableNode($expr->class, $expr->getAttributes()); } else { diff --git a/src/Node/ClassStatementsGatherer.php b/src/Node/ClassStatementsGatherer.php index 58b9763140..79b886380b 100644 --- a/src/Node/ClassStatementsGatherer.php +++ b/src/Node/ClassStatementsGatherer.php @@ -140,6 +140,10 @@ private function gatherNodes(\PhpParser\Node $node, Scope $scope): void $this->methodCalls[] = new \PHPStan\Node\Method\MethodCall($node, $scope); return; } + if ($node instanceof MethodCallableNode || $node instanceof StaticMethodCallableNode) { + $this->methodCalls[] = new \PHPStan\Node\Method\MethodCall($node->getOriginalNode(), $scope); + return; + } if ($node instanceof Array_ && count($node->items) === 2) { $this->methodCalls[] = new \PHPStan\Node\Method\MethodCall($node, $scope); return; diff --git a/src/Node/MethodCallableNode.php b/src/Node/MethodCallableNode.php index b2b1cb40ca..a82764672e 100644 --- a/src/Node/MethodCallableNode.php +++ b/src/Node/MethodCallableNode.php @@ -11,17 +11,19 @@ class MethodCallableNode extends Expr implements VirtualNode private Expr $var; /** @var Identifier|Expr */ - public $name; + private $name; + + private Expr\MethodCall $originalNode; /** * @param Expr|Identifier $name - * @param mixed[] $attributes */ - public function __construct(Expr $var, $name, array $attributes = []) + public function __construct(Expr $var, $name, Expr\MethodCall $originalNode) { - parent::__construct($attributes); + parent::__construct($originalNode->getAttributes()); $this->var = $var; $this->name = $name; + $this->originalNode = $originalNode; } public function getVar(): Expr @@ -37,6 +39,11 @@ public function getName() return $this->name; } + public function getOriginalNode(): Expr\MethodCall + { + return $this->originalNode; + } + public function getType(): string { return 'PHPStan_Node_MethodCallableNode'; diff --git a/src/Node/StaticMethodCallableNode.php b/src/Node/StaticMethodCallableNode.php index e233c0e311..f4b77c82ec 100644 --- a/src/Node/StaticMethodCallableNode.php +++ b/src/Node/StaticMethodCallableNode.php @@ -13,18 +13,20 @@ class StaticMethodCallableNode extends Expr implements VirtualNode private $class; /** @var Identifier|Expr */ - public $name; + private $name; + + private Expr\StaticCall $originalNode; /** * @param Name|Expr $class * @param Identifier|Expr $name - * @param mixed[] $attributes */ - public function __construct($class, $name, array $attributes = []) + public function __construct($class, $name, Expr\StaticCall $originalNode) { - parent::__construct($attributes); + parent::__construct($originalNode->getAttributes()); $this->class = $class; $this->name = $name; + $this->originalNode = $originalNode; } /** @@ -43,6 +45,11 @@ public function getName() return $this->name; } + public function getOriginalNode(): Expr\StaticCall + { + return $this->originalNode; + } + public function getType(): string { return 'PHPStan_Node_StaticMethodCallableNode'; diff --git a/tests/PHPStan/Rules/DeadCode/UnusedPrivateMethodRuleTest.php b/tests/PHPStan/Rules/DeadCode/UnusedPrivateMethodRuleTest.php index b06a4c013d..863e66a59a 100644 --- a/tests/PHPStan/Rules/DeadCode/UnusedPrivateMethodRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/UnusedPrivateMethodRuleTest.php @@ -56,4 +56,13 @@ public function testNullsafe(): void $this->analyse([__DIR__ . '/data/nullsafe-unused-private-method.php'], []); } + public function testFirstClassCallable(): void + { + if (PHP_VERSION_ID < 80100 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + + $this->analyse([__DIR__ . '/data/callable-unused-private-method.php'], []); + } + } diff --git a/tests/PHPStan/Rules/DeadCode/data/callable-unused-private-method.php b/tests/PHPStan/Rules/DeadCode/data/callable-unused-private-method.php new file mode 100644 index 0000000000..28b2b4f397 --- /dev/null +++ b/tests/PHPStan/Rules/DeadCode/data/callable-unused-private-method.php @@ -0,0 +1,33 @@ += 8.1 + +namespace CallableUnusedPrivateMethod; + +class Foo +{ + + public function doFoo(): void + { + $f = $this->doBar(...); + } + + private function doBar(): void + { + + } + +} + +class Bar +{ + + public function doFoo(): void + { + $f = self::doBar(...); + } + + private static function doBar(): void + { + + } + +} From 53eb2e33aa69fd9f08763d6cf8fcd3ceebda905b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 16 Nov 2021 22:01:29 +0100 Subject: [PATCH 0581/1284] Generics on first-class callables --- src/Analyser/MutatingScope.php | 4 +- src/Type/ClosureType.php | 22 ++++++++--- ...FromCallableDynamicReturnTypeExtension.php | 4 +- .../Analyser/data/first-class-callables.php | 38 +++++++++++++++++++ .../Rules/Functions/CallCallablesRuleTest.php | 20 ++++++++++ .../data/call-first-class-callables.php | 30 +++++++++++++++ 6 files changed, 111 insertions(+), 7 deletions(-) create mode 100644 tests/PHPStan/Rules/Functions/data/call-first-class-callables.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 1059b6c7b8..a15d08bac9 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -2434,7 +2434,9 @@ private function createFirstClassCallable(array $variants): Type $closureTypes[] = new ClosureType( $parameters, $variant->getReturnType(), - $variant->isVariadic() + $variant->isVariadic(), + $variant->getTemplateTypeMap(), + $variant->getResolvedTemplateTypeMap() ); } diff --git a/src/Type/ClosureType.php b/src/Type/ClosureType.php index 6fdf33fd2a..c1994a1dab 100644 --- a/src/Type/ClosureType.php +++ b/src/Type/ClosureType.php @@ -40,6 +40,10 @@ class ClosureType implements TypeWithClassName, ParametersAcceptor private bool $variadic; + private TemplateTypeMap $templateTypeMap; + + private TemplateTypeMap $resolvedTemplateTypeMap; + /** * @api * @param array $parameters @@ -49,13 +53,17 @@ class ClosureType implements TypeWithClassName, ParametersAcceptor public function __construct( array $parameters, Type $returnType, - bool $variadic + bool $variadic, + ?TemplateTypeMap $templateTypeMap = null, + ?TemplateTypeMap $resolvedTemplateTypeMap = null ) { $this->objectType = new ObjectType(\Closure::class); $this->parameters = $parameters; $this->returnType = $returnType; $this->variadic = $variadic; + $this->templateTypeMap = $templateTypeMap ?? TemplateTypeMap::createEmpty(); + $this->resolvedTemplateTypeMap = $resolvedTemplateTypeMap ?? TemplateTypeMap::createEmpty(); } public function getClassName(): string @@ -308,12 +316,12 @@ public function toArray(): Type public function getTemplateTypeMap(): TemplateTypeMap { - return TemplateTypeMap::createEmpty(); + return $this->templateTypeMap; } public function getResolvedTemplateTypeMap(): TemplateTypeMap { - return TemplateTypeMap::createEmpty(); + return $this->resolvedTemplateTypeMap; } /** @@ -392,7 +400,9 @@ public function traverse(callable $cb): Type ); }, $this->getParameters()), $cb($this->getReturnType()), - $this->isVariadic() + $this->isVariadic(), + $this->templateTypeMap, + $this->resolvedTemplateTypeMap ); } @@ -425,7 +435,9 @@ public static function __set_state(array $properties): Type return new self( $properties['parameters'], $properties['returnType'], - $properties['variadic'] + $properties['variadic'], + $properties['templateTypeMap'], + $properties['resolvedTemplateTypeMap'] ); } diff --git a/src/Type/Php/ClosureFromCallableDynamicReturnTypeExtension.php b/src/Type/Php/ClosureFromCallableDynamicReturnTypeExtension.php index 925b272ddd..30f3e949da 100644 --- a/src/Type/Php/ClosureFromCallableDynamicReturnTypeExtension.php +++ b/src/Type/Php/ClosureFromCallableDynamicReturnTypeExtension.php @@ -45,7 +45,9 @@ public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, $closureTypes[] = new ClosureType( $parameters, $variant->getReturnType(), - $variant->isVariadic() + $variant->isVariadic(), + $variant->getTemplateTypeMap(), + $variant->getResolvedTemplateTypeMap() ); } diff --git a/tests/PHPStan/Analyser/data/first-class-callables.php b/tests/PHPStan/Analyser/data/first-class-callables.php index 9af25d7cf0..5473388adf 100644 --- a/tests/PHPStan/Analyser/data/first-class-callables.php +++ b/tests/PHPStan/Analyser/data/first-class-callables.php @@ -25,3 +25,41 @@ public static function doBar(): void } } + +class GenericFoo +{ + + /** + * @template T + * @param T $a + * @return T + */ + public function doFoo($a) + { + return $a; + } + + public function doBar() + { + $f = $this->doFoo(...); + assertType('int', $f(1)); + assertType('string', $f('foo')); + + $g = \Closure::fromCallable([$this, 'doFoo']); + assertType('int', $g(1)); + assertType('string', $g('foo')); + } + + public function doBaz() + { + $ref = new \ReflectionClass(\stdClass::class); + assertType('class-string', $ref->getName()); + + $f = $ref->getName(...); + assertType('class-string', $f()); + + $g = \Closure::fromCallable([$ref, 'getName']); + assertType('class-string', $g()); + } + +} diff --git a/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php b/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php index 30e51389a4..dd808c92d1 100644 --- a/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallCallablesRuleTest.php @@ -218,4 +218,24 @@ public function testBug1849(): void $this->analyse([__DIR__ . '/data/bug-1849.php'], []); } + public function testFirstClassCallables(): void + { + if (PHP_VERSION_ID < 80100 && !self::$useStaticReflectionProvider) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + + $this->analyse([__DIR__ . '/data/call-first-class-callables.php'], [ + [ + 'Unable to resolve the template type T in call to closure', + 14, + 'See: https://phpstan.org/blog/solving-phpstan-error-unable-to-resolve-template-type', + ], + [ + 'Unable to resolve the template type T in call to closure', + 17, + 'See: https://phpstan.org/blog/solving-phpstan-error-unable-to-resolve-template-type', + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/call-first-class-callables.php b/tests/PHPStan/Rules/Functions/data/call-first-class-callables.php new file mode 100644 index 0000000000..4e78b9bb77 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/call-first-class-callables.php @@ -0,0 +1,30 @@ +doBar(...); + $f($mixed); + + $g = \Closure::fromCallable([$this, 'doBar']); + $g($mixed); + } + + /** + * @template T of object + * @param T $object + * @return T + */ + public function doBar($object) + { + return $object; + } + +} From 2db3c268e26d44125aa728c917ba51ec400b46b3 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 17 Nov 2021 13:10:38 +0100 Subject: [PATCH 0582/1284] Tips for always used properties and constants extensions --- .../DeadCode/UnusedPrivateConstantRule.php | 1 + .../DeadCode/UnusedPrivatePropertyRule.php | 6 +++-- .../UnusedPrivateConstantRuleTest.php | 2 ++ .../UnusedPrivatePropertyRuleTest.php | 22 +++++++++++++++++++ 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/Rules/DeadCode/UnusedPrivateConstantRule.php b/src/Rules/DeadCode/UnusedPrivateConstantRule.php index 6b3006f87f..ad3b249beb 100644 --- a/src/Rules/DeadCode/UnusedPrivateConstantRule.php +++ b/src/Rules/DeadCode/UnusedPrivateConstantRule.php @@ -85,6 +85,7 @@ public function processNode(Node $node, Scope $scope): array 'classStartLine' => $node->getClass()->getStartLine(), 'constantName' => $constantName, ]) + ->tip(sprintf('See: %s', 'https://phpstan.org/developing-extensions/always-used-class-constants')) ->build(); } diff --git a/src/Rules/DeadCode/UnusedPrivatePropertyRule.php b/src/Rules/DeadCode/UnusedPrivatePropertyRule.php index 6ba7338b28..54f4f3085c 100644 --- a/src/Rules/DeadCode/UnusedPrivatePropertyRule.php +++ b/src/Rules/DeadCode/UnusedPrivatePropertyRule.php @@ -185,6 +185,7 @@ public function processNode(Node $node, Scope $scope): array } else { $propertyName = sprintf('Property %s::$%s', $scope->getClassReflection()->getDisplayName(), $name); } + $tip = sprintf('See: %s', 'https://phpstan.org/developing-extensions/always-read-written-properties'); if (!$data['read']) { if (!$data['written']) { $errors[] = RuleErrorBuilder::message(sprintf('%s is unused.', $propertyName)) @@ -196,12 +197,13 @@ public function processNode(Node $node, Scope $scope): array 'classStartLine' => $node->getClass()->getStartLine(), 'propertyName' => $name, ]) + ->tip($tip) ->build(); } else { - $errors[] = RuleErrorBuilder::message(sprintf('%s is never read, only written.', $propertyName))->line($propertyNode->getStartLine())->build(); + $errors[] = RuleErrorBuilder::message(sprintf('%s is never read, only written.', $propertyName))->line($propertyNode->getStartLine())->tip($tip)->build(); } } elseif (!$data['written'] && (!array_key_exists($name, $uninitializedProperties) || !$this->checkUninitializedProperties)) { - $errors[] = RuleErrorBuilder::message(sprintf('%s is never written, only read.', $propertyName))->line($propertyNode->getStartLine())->build(); + $errors[] = RuleErrorBuilder::message(sprintf('%s is never written, only read.', $propertyName))->line($propertyNode->getStartLine())->tip($tip)->build(); } } diff --git a/tests/PHPStan/Rules/DeadCode/UnusedPrivateConstantRuleTest.php b/tests/PHPStan/Rules/DeadCode/UnusedPrivateConstantRuleTest.php index 305c37cf13..16b90f4be2 100644 --- a/tests/PHPStan/Rules/DeadCode/UnusedPrivateConstantRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/UnusedPrivateConstantRuleTest.php @@ -38,10 +38,12 @@ public function testRule(): void [ 'Constant UnusedPrivateConstant\Foo::BAR_CONST is unused.', 10, + 'See: https://phpstan.org/developing-extensions/always-used-class-constants', ], [ 'Constant UnusedPrivateConstant\TestExtension::UNUSED is unused.', 23, + 'See: https://phpstan.org/developing-extensions/always-used-class-constants', ], ]); } diff --git a/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php b/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php index 5b8b717227..d7a3eb437e 100644 --- a/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/UnusedPrivatePropertyRuleTest.php @@ -67,60 +67,75 @@ public function testRule(): void $this->alwaysWrittenTags = []; $this->alwaysReadTags = []; + $tip = 'See: https://phpstan.org/developing-extensions/always-read-written-properties'; + $this->analyse([__DIR__ . '/data/unused-private-property.php'], [ [ 'Property UnusedPrivateProperty\Foo::$bar is never read, only written.', 10, + $tip, ], [ 'Property UnusedPrivateProperty\Foo::$baz is unused.', 12, + $tip, ], [ 'Property UnusedPrivateProperty\Foo::$lorem is never written, only read.', 14, + $tip, ], [ 'Property UnusedPrivateProperty\Bar::$baz is never written, only read.', 57, + $tip, ], [ 'Static property UnusedPrivateProperty\Baz::$bar is never read, only written.', 86, + $tip, ], [ 'Static property UnusedPrivateProperty\Baz::$baz is unused.', 88, + $tip, ], [ 'Static property UnusedPrivateProperty\Baz::$lorem is never written, only read.', 90, + $tip, ], [ 'Property UnusedPrivateProperty\Lorem::$baz is never read, only written.', 117, + $tip, ], [ 'Property class@anonymous/tests/PHPStan/Rules/DeadCode/data/unused-private-property.php:152::$bar is unused.', 153, + $tip, ], [ 'Property UnusedPrivateProperty\DolorWithAnonymous::$foo is unused.', 148, + $tip, ], ]); $this->analyse([__DIR__ . '/data/TestExtension.php'], [ [ 'Property UnusedPrivateProperty\TestExtension::$unused is unused.', 8, + $tip, ], [ 'Property UnusedPrivateProperty\TestExtension::$read is never written, only read.', 10, + $tip, ], [ 'Property UnusedPrivateProperty\TestExtension::$written is never read, only written.', 12, + $tip, ], ]); } @@ -129,14 +144,17 @@ public function testAlwaysUsedTags(): void { $this->alwaysWrittenTags = ['@ORM\Column']; $this->alwaysReadTags = ['@get']; + $tip = 'See: https://phpstan.org/developing-extensions/always-read-written-properties'; $this->analyse([__DIR__ . '/data/private-property-with-tags.php'], [ [ 'Property PrivatePropertyWithTags\Foo::$title is never read, only written.', 13, + $tip, ], [ 'Property PrivatePropertyWithTags\Foo::$text is never written, only read.', 18, + $tip, ], ]); } @@ -155,10 +173,12 @@ public function testBug3636(): void } $this->alwaysWrittenTags = []; $this->alwaysReadTags = []; + $tip = 'See: https://phpstan.org/developing-extensions/always-read-written-properties'; $this->analyse([__DIR__ . '/data/bug-3636.php'], [ [ 'Property Bug3636\Bar::$date is never written, only read.', 22, + $tip, ], ]); } @@ -171,10 +191,12 @@ public function testPromotedProperties(): void $this->alwaysWrittenTags = []; $this->alwaysReadTags = ['@get']; + $tip = 'See: https://phpstan.org/developing-extensions/always-read-written-properties'; $this->analyse([__DIR__ . '/data/unused-private-promoted-property.php'], [ [ 'Property UnusedPrivatePromotedProperty\Foo::$lorem is never read, only written.', 12, + $tip, ], ]); } From cf7976980bf34063ae7f27aee79a6cc27647bfc1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 17 Nov 2021 15:09:38 +0100 Subject: [PATCH 0583/1284] Extracted part of CallMethodsRule to be reusable --- conf/config.level0.neon | 9 +- src/Rules/Methods/CallMethodsRule.php | 124 ++------------- src/Rules/Methods/MethodCallCheck.php | 150 ++++++++++++++++++ .../Rules/Methods/CallMethodsRuleTest.php | 11 +- 4 files changed, 171 insertions(+), 123 deletions(-) create mode 100644 src/Rules/Methods/MethodCallCheck.php diff --git a/conf/config.level0.neon b/conf/config.level0.neon index ee3cd08b75..508eca2296 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -90,9 +90,6 @@ services: class: PHPStan\Rules\Methods\CallMethodsRule tags: - phpstan.rules.rule - arguments: - checkFunctionNameCase: %checkFunctionNameCase% - reportMagicMethods: %reportMagicMethods% - class: PHPStan\Rules\Methods\CallStaticMethodsRule @@ -109,6 +106,12 @@ services: tags: - phpstan.rules.rule + - + class: PHPStan\Rules\Methods\MethodCallCheck + arguments: + checkFunctionNameCase: %checkFunctionNameCase% + reportMagicMethods: %reportMagicMethods% + - class: PHPStan\Rules\Methods\OverridingMethodRule arguments: diff --git a/src/Rules/Methods/CallMethodsRule.php b/src/Rules/Methods/CallMethodsRule.php index 399dbed758..dddd464dec 100644 --- a/src/Rules/Methods/CallMethodsRule.php +++ b/src/Rules/Methods/CallMethodsRule.php @@ -4,17 +4,10 @@ use PhpParser\Node; use PhpParser\Node\Expr\MethodCall; -use PHPStan\Analyser\NullsafeOperatorHelper; use PHPStan\Analyser\Scope; use PHPStan\Internal\SprintfHelper; use PHPStan\Reflection\ParametersAcceptorSelector; -use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\FunctionCallParametersCheck; -use PHPStan\Rules\RuleErrorBuilder; -use PHPStan\Rules\RuleLevelHelper; -use PHPStan\Type\ErrorType; -use PHPStan\Type\Type; -use PHPStan\Type\VerbosityLevel; /** * @implements \PHPStan\Rules\Rule<\PhpParser\Node\Expr\MethodCall> @@ -22,29 +15,17 @@ class CallMethodsRule implements \PHPStan\Rules\Rule { - private \PHPStan\Reflection\ReflectionProvider $reflectionProvider; + private MethodCallCheck $methodCallCheck; - private \PHPStan\Rules\FunctionCallParametersCheck $check; - - private \PHPStan\Rules\RuleLevelHelper $ruleLevelHelper; - - private bool $checkFunctionNameCase; - - private bool $reportMagicMethods; + private \PHPStan\Rules\FunctionCallParametersCheck $parametersCheck; public function __construct( - ReflectionProvider $reflectionProvider, - FunctionCallParametersCheck $check, - RuleLevelHelper $ruleLevelHelper, - bool $checkFunctionNameCase, - bool $reportMagicMethods + MethodCallCheck $methodCallCheck, + FunctionCallParametersCheck $parametersCheck ) { - $this->reflectionProvider = $reflectionProvider; - $this->check = $check; - $this->ruleLevelHelper = $ruleLevelHelper; - $this->checkFunctionNameCase = $checkFunctionNameCase; - $this->reportMagicMethods = $reportMagicMethods; + $this->methodCallCheck = $methodCallCheck; + $this->parametersCheck = $parametersCheck; } public function getNodeType(): string @@ -58,88 +39,17 @@ public function processNode(Node $node, Scope $scope): array return []; } - $name = $node->name->name; - - $typeResult = $this->ruleLevelHelper->findTypeToCheck( - $scope, - NullsafeOperatorHelper::getNullsafeShortcircuitedExprRespectingScope($scope, $node->var), - sprintf('Call to method %s() on an unknown class %%s.', SprintfHelper::escapeFormatString($name)), - static function (Type $type) use ($name): bool { - return $type->canCallMethods()->yes() && $type->hasMethod($name)->yes(); - } - ); - - $type = $typeResult->getType(); - if ($type instanceof ErrorType) { - return $typeResult->getUnknownClassErrors(); - } - if (!$type->canCallMethods()->yes()) { - return [ - RuleErrorBuilder::message(sprintf( - 'Cannot call method %s() on %s.', - $name, - $type->describe(VerbosityLevel::typeOnly()) - ))->build(), - ]; - } - - if (!$type->hasMethod($name)->yes()) { - $directClassNames = $typeResult->getReferencedClasses(); - if (!$this->reportMagicMethods) { - foreach ($directClassNames as $className) { - if (!$this->reflectionProvider->hasClass($className)) { - continue; - } - - $classReflection = $this->reflectionProvider->getClass($className); - if ($classReflection->hasNativeMethod('__call')) { - return []; - } - } - } - - if (count($directClassNames) === 1) { - $referencedClass = $directClassNames[0]; - $methodClassReflection = $this->reflectionProvider->getClass($referencedClass); - $parentClassReflection = $methodClassReflection->getParentClass(); - while ($parentClassReflection !== null) { - if ($parentClassReflection->hasMethod($name)) { - return [ - RuleErrorBuilder::message(sprintf( - 'Call to private method %s() of parent class %s.', - $parentClassReflection->getMethod($name, $scope)->getName(), - $parentClassReflection->getDisplayName() - ))->build(), - ]; - } - - $parentClassReflection = $parentClassReflection->getParentClass(); - } - } + $methodName = $node->name->name; - return [ - RuleErrorBuilder::message(sprintf( - 'Call to an undefined method %s::%s().', - $type->describe(VerbosityLevel::typeOnly()), - $name - ))->build(), - ]; + [$errors, $methodReflection] = $this->methodCallCheck->check($scope, $methodName, $node->var); + if ($methodReflection === null) { + return $errors; } - $methodReflection = $type->getMethod($name, $scope); $declaringClass = $methodReflection->getDeclaringClass(); $messagesMethodName = SprintfHelper::escapeFormatString($declaringClass->getDisplayName() . '::' . $methodReflection->getName() . '()'); - $errors = []; - if (!$scope->canCallMethod($methodReflection)) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'Call to %s method %s() of class %s.', - $methodReflection->isPrivate() ? 'private' : 'protected', - $methodReflection->getName(), - $declaringClass->getDisplayName() - ))->build(); - } - $errors = array_merge($errors, $this->check->check( + return array_merge($errors, $this->parametersCheck->check( ParametersAcceptorSelector::selectFromArgs( $scope, $node->getArgs(), @@ -164,18 +74,6 @@ static function (Type $type) use ($name): bool { 'Return type of call to method ' . $messagesMethodName . ' contains unresolvable type.', ] )); - - if ( - $this->checkFunctionNameCase - && strtolower($methodReflection->getName()) === strtolower($name) - && $methodReflection->getName() !== $name - ) { - $errors[] = RuleErrorBuilder::message( - sprintf('Call to method %s with incorrect case: %s', $messagesMethodName, $name) - )->build(); - } - - return $errors; } } diff --git a/src/Rules/Methods/MethodCallCheck.php b/src/Rules/Methods/MethodCallCheck.php new file mode 100644 index 0000000000..3bf33fc9de --- /dev/null +++ b/src/Rules/Methods/MethodCallCheck.php @@ -0,0 +1,150 @@ +reflectionProvider = $reflectionProvider; + $this->ruleLevelHelper = $ruleLevelHelper; + $this->checkFunctionNameCase = $checkFunctionNameCase; + $this->reportMagicMethods = $reportMagicMethods; + } + /** + * @return array{RuleError[], MethodReflection|null} + */ + public function check( + Scope $scope, + string $methodName, + Expr $var + ): array + { + $typeResult = $this->ruleLevelHelper->findTypeToCheck( + $scope, + NullsafeOperatorHelper::getNullsafeShortcircuitedExprRespectingScope($scope, $var), + sprintf('Call to method %s() on an unknown class %%s.', SprintfHelper::escapeFormatString($methodName)), + static function (Type $type) use ($methodName): bool { + return $type->canCallMethods()->yes() && $type->hasMethod($methodName)->yes(); + } + ); + + $type = $typeResult->getType(); + if ($type instanceof ErrorType) { + return [$typeResult->getUnknownClassErrors(), null]; + } + if (!$type->canCallMethods()->yes()) { + return [ + [ + RuleErrorBuilder::message(sprintf( + 'Cannot call method %s() on %s.', + $methodName, + $type->describe(VerbosityLevel::typeOnly()) + ))->build(), + ], + null, + ]; + } + + if (!$type->hasMethod($methodName)->yes()) { + $directClassNames = $typeResult->getReferencedClasses(); + if (!$this->reportMagicMethods) { + foreach ($directClassNames as $className) { + if (!$this->reflectionProvider->hasClass($className)) { + continue; + } + + $classReflection = $this->reflectionProvider->getClass($className); + if ($classReflection->hasNativeMethod('__call')) { + return [[], null]; + } + } + } + + if (count($directClassNames) === 1) { + $referencedClass = $directClassNames[0]; + $methodClassReflection = $this->reflectionProvider->getClass($referencedClass); + $parentClassReflection = $methodClassReflection->getParentClass(); + while ($parentClassReflection !== null) { + if ($parentClassReflection->hasMethod($methodName)) { + $methodReflection = $parentClassReflection->getMethod($methodName, $scope); + return [ + [ + RuleErrorBuilder::message(sprintf( + 'Call to private method %s() of parent class %s.', + $methodReflection->getName(), + $parentClassReflection->getDisplayName() + ))->build(), + ], + $methodReflection, + ]; + } + + $parentClassReflection = $parentClassReflection->getParentClass(); + } + } + + return [ + [ + RuleErrorBuilder::message(sprintf( + 'Call to an undefined method %s::%s().', + $type->describe(VerbosityLevel::typeOnly()), + $methodName + ))->build(), + ], + null, + ]; + } + + $methodReflection = $type->getMethod($methodName, $scope); + $declaringClass = $methodReflection->getDeclaringClass(); + $messagesMethodName = SprintfHelper::escapeFormatString($declaringClass->getDisplayName() . '::' . $methodReflection->getName() . '()'); + $errors = []; + if (!$scope->canCallMethod($methodReflection)) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Call to %s method %s() of class %s.', + $methodReflection->isPrivate() ? 'private' : 'protected', + $methodReflection->getName(), + $declaringClass->getDisplayName() + ))->build(); + } + + if ( + $this->checkFunctionNameCase + && strtolower($methodReflection->getName()) === strtolower($methodName) + && $methodReflection->getName() !== $methodName + ) { + $errors[] = RuleErrorBuilder::message( + sprintf('Call to method %s with incorrect case: %s', $messagesMethodName, $methodName) + )->build(); + } + + return [$errors, $methodReflection]; + } + +} diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index d8e3d2ca32..4e57f68b1e 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -33,14 +33,11 @@ class CallMethodsRuleTest extends \PHPStan\Testing\RuleTestCase protected function getRule(): Rule { - $broker = $this->createReflectionProvider(); - $ruleLevelHelper = new RuleLevelHelper($broker, $this->checkNullables, $this->checkThisOnly, $this->checkUnionTypes, $this->checkExplicitMixed); + $reflectionProvider = $this->createReflectionProvider(); + $ruleLevelHelper = new RuleLevelHelper($reflectionProvider, $this->checkNullables, $this->checkThisOnly, $this->checkUnionTypes, $this->checkExplicitMixed); return new CallMethodsRule( - $broker, - new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new PhpVersion($this->phpVersion), new UnresolvableTypeHelper(), true, true, true, true), - $ruleLevelHelper, - true, - true + new MethodCallCheck($reflectionProvider, $ruleLevelHelper, true, true), + new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new PhpVersion($this->phpVersion), new UnresolvableTypeHelper(), true, true, true, true) ); } From c106a9dc2b47f11d55ba23e18f598e380799e378 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 17 Nov 2021 15:24:24 +0100 Subject: [PATCH 0584/1284] Check methods in first-class callables --- conf/config.level0.neon | 7 +- src/Rules/Methods/MethodCallableRule.php | 68 ++++++++++++++ .../Rules/Methods/MethodCallableRuleTest.php | 90 +++++++++++++++++++ .../data/method-callable-not-supported.php | 13 +++ .../Rules/Methods/data/method-callable.php | 88 ++++++++++++++++++ 5 files changed, 261 insertions(+), 5 deletions(-) create mode 100644 src/Rules/Methods/MethodCallableRule.php create mode 100644 tests/PHPStan/Rules/Methods/MethodCallableRuleTest.php create mode 100644 tests/PHPStan/Rules/Methods/data/method-callable-not-supported.php create mode 100644 tests/PHPStan/Rules/Methods/data/method-callable.php diff --git a/conf/config.level0.neon b/conf/config.level0.neon index 508eca2296..44187cefd8 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -48,7 +48,9 @@ rules: - PHPStan\Rules\Functions\ReturnNullsafeByRefRule - PHPStan\Rules\Keywords\ContinueBreakInLoopRule - PHPStan\Rules\Methods\AbstractMethodInNonAbstractClassRule + - PHPStan\Rules\Methods\CallMethodsRule - PHPStan\Rules\Methods\ExistingClassesInTypehintsRule + - PHPStan\Rules\Methods\MethodCallableRule - PHPStan\Rules\Methods\MissingMethodImplementationRule - PHPStan\Rules\Methods\MethodAttributesRule - PHPStan\Rules\Operators\InvalidAssignVarRule @@ -86,11 +88,6 @@ services: arguments: checkFunctionNameCase: %checkFunctionNameCase% - - - class: PHPStan\Rules\Methods\CallMethodsRule - tags: - - phpstan.rules.rule - - class: PHPStan\Rules\Methods\CallStaticMethodsRule tags: diff --git a/src/Rules/Methods/MethodCallableRule.php b/src/Rules/Methods/MethodCallableRule.php new file mode 100644 index 0000000000..6ca18ae43c --- /dev/null +++ b/src/Rules/Methods/MethodCallableRule.php @@ -0,0 +1,68 @@ + + */ +class MethodCallableRule implements Rule +{ + + private MethodCallCheck $methodCallCheck; + + private PhpVersion $phpVersion; + + public function __construct(MethodCallCheck $methodCallCheck, PhpVersion $phpVersion) + { + $this->methodCallCheck = $methodCallCheck; + $this->phpVersion = $phpVersion; + } + + public function getNodeType(): string + { + return MethodCallableNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$this->phpVersion->supportsFirstClassCallables()) { + return [ + RuleErrorBuilder::message('First-class callables are supported only on PHP 8.1 and later.') + ->nonIgnorable() + ->build(), + ]; + } + + $methodName = $node->getName(); + if (!$methodName instanceof Node\Identifier) { + return []; + } + + $methodNameName = $methodName->toString(); + + [$errors, $methodReflection] = $this->methodCallCheck->check($scope, $methodNameName, $node->getVar()); + if ($methodReflection === null) { + return $errors; + } + + $declaringClass = $methodReflection->getDeclaringClass(); + if ($declaringClass->hasNativeMethod($methodNameName)) { + return $errors; + } + + $messagesMethodName = SprintfHelper::escapeFormatString($declaringClass->getDisplayName() . '::' . $methodReflection->getName() . '()'); + + $errors[] = RuleErrorBuilder::message(sprintf('Creating callable from a non-native method %s.', $messagesMethodName))->build(); + + return $errors; + } + +} diff --git a/tests/PHPStan/Rules/Methods/MethodCallableRuleTest.php b/tests/PHPStan/Rules/Methods/MethodCallableRuleTest.php new file mode 100644 index 0000000000..5c3a0f4e06 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/MethodCallableRuleTest.php @@ -0,0 +1,90 @@ + + */ +class MethodCallableRuleTest extends RuleTestCase +{ + + /** @var int */ + private $phpVersion = PHP_VERSION_ID; + + protected function getRule(): Rule + { + $reflectionProvider = $this->createReflectionProvider(); + $ruleLevelHelper = new RuleLevelHelper($reflectionProvider, true, false, true, false); + + return new MethodCallableRule( + new MethodCallCheck($reflectionProvider, $ruleLevelHelper, true, true), + new PhpVersion($this->phpVersion) + ); + } + + public function testNotSupportedOnOlderVersions(): void + { + if (PHP_VERSION_ID >= 80100) { + self::markTestSkipped('Test runs on PHP < 8.1.'); + } + if (!self::$useStaticReflectionProvider) { + self::markTestSkipped('Test requires static reflection.'); + } + + $this->analyse([__DIR__ . '/data/method-callable-not-supported.php'], [ + [ + 'First-class callables are supported only on PHP 8.1 and later.', + 10, + ], + ]); + } + + public function testRule(): void + { + if (PHP_VERSION_ID < 80100) { + self::markTestSkipped('Test requires PHP 8.1.'); + } + + $this->analyse([__DIR__ . '/data/method-callable.php'], [ + [ + 'Call to method MethodCallable\Foo::doFoo() with incorrect case: dofoo', + 11, + ], + [ + 'Call to an undefined method MethodCallable\Foo::doNonexistent().', + 12, + ], + [ + 'Cannot call method doFoo() on int.', + 13, + ], + [ + 'Call to private method doBar() of class MethodCallable\Bar.', + 18, + ], + [ + 'Call to method doFoo() on an unknown class MethodCallable\Nonexistent.', + 23, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Call to private method doFoo() of class MethodCallable\ParentClass.', + 53, + ], + [ + 'Creating callable from a non-native method MethodCallable\Lorem::doBar().', + 66, + ], + [ + 'Creating callable from a non-native method MethodCallable\Ipsum::doBar().', + 85, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Methods/data/method-callable-not-supported.php b/tests/PHPStan/Rules/Methods/data/method-callable-not-supported.php new file mode 100644 index 0000000000..3ad95fe40a --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/method-callable-not-supported.php @@ -0,0 +1,13 @@ += 8.1 + +namespace MethodCallableNotSupported; + +class Foo +{ + + public function doFoo(): void + { + $this->doFoo(...); + } + +} diff --git a/tests/PHPStan/Rules/Methods/data/method-callable.php b/tests/PHPStan/Rules/Methods/data/method-callable.php new file mode 100644 index 0000000000..b86bca74e0 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/method-callable.php @@ -0,0 +1,88 @@ += 8.1 + +namespace MethodCallable; + +class Foo +{ + + public function doFoo(int $i): void + { + $this->doFoo(...); + $this->dofoo(...); + $this->doNonexistent(...); + $i->doFoo(...); + } + + public function doBar(Bar $bar): void + { + $bar->doBar(...); + } + + public function doBaz(Nonexistent $n): void + { + $n->doFoo(...); + } + +} + +class Bar +{ + + private function doBar() + { + + } + +} + +class ParentClass +{ + + private function doFoo() + { + + } + +} + +class ChildClass extends ParentClass +{ + + public function doBar() + { + $this->doFoo(...); + } + +} + +/** + * @method void doBar() + */ +class Lorem +{ + + public function doFoo() + { + $this->doBar(...); + } + + public function __call($name, $arguments) + { + + } + + +} + +/** + * @method void doBar() + */ +class Ipsum +{ + + public function doFoo() + { + $this->doBar(...); + } + +} From 50cb90ddc6e6f6bdd248b7ea4bb5002193e808d6 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 17 Nov 2021 16:09:58 +0100 Subject: [PATCH 0585/1284] Autowire rules from the rules section by their class name --- src/DependencyInjection/RulesExtension.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DependencyInjection/RulesExtension.php b/src/DependencyInjection/RulesExtension.php index b72f286754..79222ac2ab 100644 --- a/src/DependencyInjection/RulesExtension.php +++ b/src/DependencyInjection/RulesExtension.php @@ -22,7 +22,7 @@ public function loadConfiguration(): void foreach ($config as $key => $rule) { $builder->addDefinition($this->prefix((string) $key)) ->setFactory($rule) - ->setAutowired(false) + ->setAutowired($rule) ->addTag(RegistryFactory::RULE_TAG); } } From adb5261bd8f64f66d20b5530ca2988a8f5e895f4 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 17 Nov 2021 22:22:03 +0100 Subject: [PATCH 0586/1284] Correct config file for MethodCallCheck --- conf/config.level0.neon | 6 ------ conf/config.neon | 6 ++++++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/conf/config.level0.neon b/conf/config.level0.neon index 44187cefd8..e0e9970716 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -103,12 +103,6 @@ services: tags: - phpstan.rules.rule - - - class: PHPStan\Rules\Methods\MethodCallCheck - arguments: - checkFunctionNameCase: %checkFunctionNameCase% - reportMagicMethods: %reportMagicMethods% - - class: PHPStan\Rules\Methods\OverridingMethodRule arguments: diff --git a/conf/config.neon b/conf/config.neon index a829e12ea5..daaa6ff565 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -802,6 +802,12 @@ services: checkAdvancedIsset: %checkAdvancedIsset% treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% + - + class: PHPStan\Rules\Methods\MethodCallCheck + arguments: + checkFunctionNameCase: %checkFunctionNameCase% + reportMagicMethods: %reportMagicMethods% + - # checked as part of OverridingMethodRule class: PHPStan\Rules\Methods\MethodSignatureRule From db456dd6fd79405bbbdac00fddb8ca0acb90ed49 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 18 Nov 2021 10:03:11 +0100 Subject: [PATCH 0587/1284] Open 1.2-dev --- composer.json | 2 +- composer.lock | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 1090a3f5e6..ab071ce220 100644 --- a/composer.json +++ b/composer.json @@ -60,7 +60,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.2-dev" }, "patcher": { "search": "patches" diff --git a/composer.lock b/composer.lock index 9a05f5defc..d32af7fda8 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "893316fb27939a9490338d747bf26445", + "content-hash": "669c335796ae43e31c7b06f23e01c519", "packages": [ { "name": "clue/block-react", @@ -475,6 +475,7 @@ "issues": "https://github.com/hoaproject/Compiler/issues", "source": "https://central.hoa-project.net/Resource/Library/Compiler" }, + "abandoned": true, "time": "2017-08-08T07:44:07+00:00" }, { @@ -546,6 +547,7 @@ "issues": "https://github.com/hoaproject/Consistency/issues", "source": "https://central.hoa-project.net/Resource/Library/Consistency" }, + "abandoned": true, "time": "2017-05-02T12:18:12+00:00" }, { @@ -610,6 +612,7 @@ "issues": "https://github.com/hoaproject/Event/issues", "source": "https://central.hoa-project.net/Resource/Library/Event" }, + "abandoned": true, "time": "2017-01-13T15:30:50+00:00" }, { @@ -672,6 +675,7 @@ "issues": "https://github.com/hoaproject/Exception/issues", "source": "https://central.hoa-project.net/Resource/Library/Exception" }, + "abandoned": true, "time": "2017-01-16T07:53:27+00:00" }, { @@ -742,6 +746,7 @@ "issues": "https://github.com/hoaproject/File/issues", "source": "https://central.hoa-project.net/Resource/Library/File" }, + "abandoned": true, "time": "2017-07-11T07:42:15+00:00" }, { @@ -804,6 +809,7 @@ "issues": "https://github.com/hoaproject/Iterator/issues", "source": "https://central.hoa-project.net/Resource/Library/Iterator" }, + "abandoned": true, "time": "2017-01-10T10:34:47+00:00" }, { @@ -877,6 +883,7 @@ "issues": "https://github.com/hoaproject/Math/issues", "source": "https://central.hoa-project.net/Resource/Library/Math" }, + "abandoned": true, "time": "2017-05-16T08:02:17+00:00" }, { @@ -945,6 +952,7 @@ "issues": "https://github.com/hoaproject/Protocol/issues", "source": "https://central.hoa-project.net/Resource/Library/Protocol" }, + "abandoned": true, "time": "2017-01-14T12:26:10+00:00" }, { @@ -1009,6 +1017,7 @@ "issues": "https://github.com/hoaproject/Regex/issues", "source": "https://central.hoa-project.net/Resource/Library/Regex" }, + "abandoned": true, "time": "2017-01-13T16:10:24+00:00" }, { @@ -1081,6 +1090,7 @@ "issues": "https://github.com/hoaproject/Stream/issues", "source": "https://central.hoa-project.net/Resource/Library/Stream" }, + "abandoned": true, "time": "2017-02-21T16:01:06+00:00" }, { @@ -1149,6 +1159,7 @@ "issues": "https://github.com/hoaproject/Ustring/issues", "source": "https://central.hoa-project.net/Resource/Library/Ustring" }, + "abandoned": true, "time": "2017-01-16T07:08:25+00:00" }, { @@ -1212,6 +1223,7 @@ "issues": "https://github.com/hoaproject/Visitor/issues", "source": "https://central.hoa-project.net/Resource/Library/Visitor" }, + "abandoned": true, "time": "2017-01-16T07:02:03+00:00" }, { @@ -1272,6 +1284,7 @@ "issues": "https://github.com/hoaproject/Zformat/issues", "source": "https://central.hoa-project.net/Resource/Library/Zformat" }, + "abandoned": true, "time": "2017-01-10T10:39:54+00:00" }, { From 7ed316d5983eb5a2601a5c3eb7781f8c06a50503 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 18 Nov 2021 10:10:34 +0100 Subject: [PATCH 0588/1284] Cover first-class callable nodes with BC promise --- src/Node/FunctionCallableNode.php | 1 + src/Node/InstantiationCallableNode.php | 1 + src/Node/MethodCallableNode.php | 1 + src/Node/StaticMethodCallableNode.php | 1 + 4 files changed, 4 insertions(+) diff --git a/src/Node/FunctionCallableNode.php b/src/Node/FunctionCallableNode.php index 909c576720..288ed4abb5 100644 --- a/src/Node/FunctionCallableNode.php +++ b/src/Node/FunctionCallableNode.php @@ -5,6 +5,7 @@ use PhpParser\Node\Expr; use PhpParser\Node\Name; +/** @api */ class FunctionCallableNode extends Expr implements VirtualNode { diff --git a/src/Node/InstantiationCallableNode.php b/src/Node/InstantiationCallableNode.php index bd1325e9b4..f44a7ae3e8 100644 --- a/src/Node/InstantiationCallableNode.php +++ b/src/Node/InstantiationCallableNode.php @@ -5,6 +5,7 @@ use PhpParser\Node\Expr; use PhpParser\Node\Name; +/** @api */ class InstantiationCallableNode extends Expr implements VirtualNode { diff --git a/src/Node/MethodCallableNode.php b/src/Node/MethodCallableNode.php index a82764672e..c343e2857e 100644 --- a/src/Node/MethodCallableNode.php +++ b/src/Node/MethodCallableNode.php @@ -5,6 +5,7 @@ use PhpParser\Node\Expr; use PhpParser\Node\Identifier; +/** @api */ class MethodCallableNode extends Expr implements VirtualNode { diff --git a/src/Node/StaticMethodCallableNode.php b/src/Node/StaticMethodCallableNode.php index f4b77c82ec..e269d3c5ff 100644 --- a/src/Node/StaticMethodCallableNode.php +++ b/src/Node/StaticMethodCallableNode.php @@ -6,6 +6,7 @@ use PhpParser\Node\Identifier; use PhpParser\Node\Name; +/** @api */ class StaticMethodCallableNode extends Expr implements VirtualNode { From 5b374062ef738672e72ceadf0d8c1d8c18fac766 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 18 Nov 2021 10:28:12 +0100 Subject: [PATCH 0589/1284] Remove todos --- src/Reflection/GenericParametersAcceptorResolver.php | 2 -- .../StrictComparisonOfDifferentTypesRuleTest.php | 8 -------- tests/PHPStan/Rules/Comparison/data/strict-comparison.php | 2 +- 3 files changed, 1 insertion(+), 11 deletions(-) diff --git a/src/Reflection/GenericParametersAcceptorResolver.php b/src/Reflection/GenericParametersAcceptorResolver.php index 34c56baf84..01e03cf62c 100644 --- a/src/Reflection/GenericParametersAcceptorResolver.php +++ b/src/Reflection/GenericParametersAcceptorResolver.php @@ -27,8 +27,6 @@ public static function resolve(array $argTypes, ParametersAcceptor $parametersAc } $paramType = $param->getType(); - - // todo zde "doaplikovat" typy, ktere se dosud nevyskytly - typicky callable(T) - parametr callable $typeMap = $typeMap->union($paramType->inferTemplateTypes($argType)); } diff --git a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php index b92346d98f..558cf9799e 100644 --- a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php @@ -154,10 +154,6 @@ public function testStrictComparison(): void 'Strict comparison using === between 0 and 0 will always evaluate to true.', 426, ], - [ - 'Strict comparison using === between int and null will always evaluate to false.', // todo remove with isDeterministic - 438, - ], [ 'Strict comparison using === between int|int<2, max>|string and 1.0 will always evaluate to false.', 464, @@ -332,10 +328,6 @@ public function testStrictComparisonWithoutAlwaysTrue(): void 'Strict comparison using !== between null and null will always evaluate to false.', 408, ], - [ - 'Strict comparison using === between int and null will always evaluate to false.', // todo remove with isDeterministic - 438, - ], [ 'Strict comparison using === between int|int<2, max>|string and 1.0 will always evaluate to false.', 464, diff --git a/tests/PHPStan/Rules/Comparison/data/strict-comparison.php b/tests/PHPStan/Rules/Comparison/data/strict-comparison.php index 6eba9be0d5..4d2f5c61a4 100644 --- a/tests/PHPStan/Rules/Comparison/data/strict-comparison.php +++ b/tests/PHPStan/Rules/Comparison/data/strict-comparison.php @@ -440,7 +440,7 @@ public function doFoo() } } } - + /** @phpstan-impure */ public function nullableInt(): ?int { From 480c516ce2795d7af27142ac7c370e0d1f6bbb38 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 18 Nov 2021 10:31:42 +0100 Subject: [PATCH 0590/1284] Extracted part of CallStaticMethodsRule to be reusable --- conf/config.level0.neon | 9 +- conf/config.neon | 6 + src/Rules/Methods/CallStaticMethodsRule.php | 252 +-------------- src/Rules/Methods/MethodCallCheck.php | 1 + src/Rules/Methods/StaticMethodCallCheck.php | 292 ++++++++++++++++++ .../Methods/CallStaticMethodsRuleTest.php | 12 +- 6 files changed, 318 insertions(+), 254 deletions(-) create mode 100644 src/Rules/Methods/StaticMethodCallCheck.php diff --git a/conf/config.level0.neon b/conf/config.level0.neon index e0e9970716..34847e99e3 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -49,6 +49,7 @@ rules: - PHPStan\Rules\Keywords\ContinueBreakInLoopRule - PHPStan\Rules\Methods\AbstractMethodInNonAbstractClassRule - PHPStan\Rules\Methods\CallMethodsRule + - PHPStan\Rules\Methods\CallStaticMethodsRule - PHPStan\Rules\Methods\ExistingClassesInTypehintsRule - PHPStan\Rules\Methods\MethodCallableRule - PHPStan\Rules\Methods\MissingMethodImplementationRule @@ -88,14 +89,6 @@ services: arguments: checkFunctionNameCase: %checkFunctionNameCase% - - - class: PHPStan\Rules\Methods\CallStaticMethodsRule - tags: - - phpstan.rules.rule - arguments: - checkFunctionNameCase: %checkFunctionNameCase% - reportMagicMethods: %reportMagicMethods% - - class: PHPStan\Rules\Constants\OverridingConstantRule arguments: diff --git a/conf/config.neon b/conf/config.neon index daaa6ff565..d4d5342b62 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -808,6 +808,12 @@ services: checkFunctionNameCase: %checkFunctionNameCase% reportMagicMethods: %reportMagicMethods% + - + class: PHPStan\Rules\Methods\StaticMethodCallCheck + arguments: + checkFunctionNameCase: %checkFunctionNameCase% + reportMagicMethods: %reportMagicMethods% + - # checked as part of OverridingMethodRule class: PHPStan\Rules\Methods\MethodSignatureRule diff --git a/src/Rules/Methods/CallStaticMethodsRule.php b/src/Rules/Methods/CallStaticMethodsRule.php index 70f942ac6c..9ddcf6e91e 100644 --- a/src/Rules/Methods/CallStaticMethodsRule.php +++ b/src/Rules/Methods/CallStaticMethodsRule.php @@ -4,30 +4,10 @@ use PhpParser\Node; use PhpParser\Node\Expr\StaticCall; -use PhpParser\Node\Name; -use PHPStan\Analyser\NullsafeOperatorHelper; use PHPStan\Analyser\Scope; use PHPStan\Internal\SprintfHelper; -use PHPStan\Reflection\MethodReflection; -use PHPStan\Reflection\Native\NativeMethodReflection; use PHPStan\Reflection\ParametersAcceptorSelector; -use PHPStan\Reflection\Php\PhpMethodReflection; -use PHPStan\Reflection\ReflectionProvider; -use PHPStan\Rules\ClassCaseSensitivityCheck; -use PHPStan\Rules\ClassNameNodePair; use PHPStan\Rules\FunctionCallParametersCheck; -use PHPStan\Rules\RuleErrorBuilder; -use PHPStan\Rules\RuleLevelHelper; -use PHPStan\Type\ErrorType; -use PHPStan\Type\Generic\GenericClassStringType; -use PHPStan\Type\ObjectWithoutClassType; -use PHPStan\Type\StringType; -use PHPStan\Type\ThisType; -use PHPStan\Type\Type; -use PHPStan\Type\TypeCombinator; -use PHPStan\Type\TypeUtils; -use PHPStan\Type\TypeWithClassName; -use PHPStan\Type\VerbosityLevel; /** * @implements \PHPStan\Rules\Rule<\PhpParser\Node\Expr\StaticCall> @@ -35,33 +15,17 @@ class CallStaticMethodsRule implements \PHPStan\Rules\Rule { - private \PHPStan\Reflection\ReflectionProvider $reflectionProvider; + private StaticMethodCallCheck $methodCallCheck; - private \PHPStan\Rules\FunctionCallParametersCheck $check; - - private \PHPStan\Rules\RuleLevelHelper $ruleLevelHelper; - - private \PHPStan\Rules\ClassCaseSensitivityCheck $classCaseSensitivityCheck; - - private bool $checkFunctionNameCase; - - private bool $reportMagicMethods; + private FunctionCallParametersCheck $parametersCheck; public function __construct( - ReflectionProvider $reflectionProvider, - FunctionCallParametersCheck $check, - RuleLevelHelper $ruleLevelHelper, - ClassCaseSensitivityCheck $classCaseSensitivityCheck, - bool $checkFunctionNameCase, - bool $reportMagicMethods + StaticMethodCallCheck $methodCallCheck, + FunctionCallParametersCheck $parametersCheck ) { - $this->reflectionProvider = $reflectionProvider; - $this->check = $check; - $this->ruleLevelHelper = $ruleLevelHelper; - $this->classCaseSensitivityCheck = $classCaseSensitivityCheck; - $this->checkFunctionNameCase = $checkFunctionNameCase; - $this->reportMagicMethods = $reportMagicMethods; + $this->methodCallCheck = $methodCallCheck; + $this->parametersCheck = $parametersCheck; } public function getNodeType(): string @@ -76,200 +40,23 @@ public function processNode(Node $node, Scope $scope): array } $methodName = $node->name->name; - $class = $node->class; - $errors = []; - $isAbstract = false; - if ($class instanceof Name) { - $className = (string) $class; - $lowercasedClassName = strtolower($className); - if (in_array($lowercasedClassName, ['self', 'static'], true)) { - if (!$scope->isInClass()) { - return [ - RuleErrorBuilder::message(sprintf( - 'Calling %s::%s() outside of class scope.', - $className, - $methodName - ))->build(), - ]; - } - $classType = $scope->resolveTypeByName($class); - } elseif ($lowercasedClassName === 'parent') { - if (!$scope->isInClass()) { - return [ - RuleErrorBuilder::message(sprintf( - 'Calling %s::%s() outside of class scope.', - $className, - $methodName - ))->build(), - ]; - } - $currentClassReflection = $scope->getClassReflection(); - if ($currentClassReflection->getParentClass() === null) { - return [ - RuleErrorBuilder::message(sprintf( - '%s::%s() calls parent::%s() but %s does not extend any class.', - $scope->getClassReflection()->getDisplayName(), - $scope->getFunctionName(), - $methodName, - $scope->getClassReflection()->getDisplayName() - ))->build(), - ]; - } - - if ($scope->getFunctionName() === null) { - throw new \PHPStan\ShouldNotHappenException(); - } - - $classType = $scope->resolveTypeByName($class); - } else { - if (!$this->reflectionProvider->hasClass($className)) { - if ($scope->isInClassExists($className)) { - return []; - } - - return [ - RuleErrorBuilder::message(sprintf( - 'Call to static method %s() on an unknown class %s.', - $methodName, - $className - ))->discoveringSymbolsTip()->build(), - ]; - } else { - $errors = $this->classCaseSensitivityCheck->checkClassNames([new ClassNameNodePair($className, $class)]); - } - - $classType = $scope->resolveTypeByName($class); - } - - $classReflection = $classType->getClassReflection(); - if ($classReflection !== null && $classReflection->hasNativeMethod($methodName) && $lowercasedClassName !== 'static') { - $nativeMethodReflection = $classReflection->getNativeMethod($methodName); - if ($nativeMethodReflection instanceof PhpMethodReflection || $nativeMethodReflection instanceof NativeMethodReflection) { - $isAbstract = $nativeMethodReflection->isAbstract(); - } - } - } else { - $classTypeResult = $this->ruleLevelHelper->findTypeToCheck( - $scope, - NullsafeOperatorHelper::getNullsafeShortcircuitedExprRespectingScope($scope, $class), - sprintf('Call to static method %s() on an unknown class %%s.', SprintfHelper::escapeFormatString($methodName)), - static function (Type $type) use ($methodName): bool { - return $type->canCallMethods()->yes() && $type->hasMethod($methodName)->yes(); - } - ); - $classType = $classTypeResult->getType(); - if ($classType instanceof ErrorType) { - return $classTypeResult->getUnknownClassErrors(); - } + [$errors, $method] = $this->methodCallCheck->check($scope, $methodName, $node->class); + if ($method === null) { + return $errors; } - if ($classType instanceof GenericClassStringType) { - $classType = $classType->getGenericType(); - if (!(new ObjectWithoutClassType())->isSuperTypeOf($classType)->yes()) { - return []; - } - } elseif ((new StringType())->isSuperTypeOf($classType)->yes()) { - return []; - } - - $typeForDescribe = $classType; - if ($classType instanceof ThisType) { - $typeForDescribe = $classType->getStaticObjectType(); - } - $classType = TypeCombinator::remove($classType, new StringType()); - - if (!$classType->canCallMethods()->yes()) { - return array_merge($errors, [ - RuleErrorBuilder::message(sprintf( - 'Cannot call static method %s() on %s.', - $methodName, - $typeForDescribe->describe(VerbosityLevel::typeOnly()) - ))->build(), - ]); - } - - if (!$classType->hasMethod($methodName)->yes()) { - if (!$this->reportMagicMethods) { - $directClassNames = TypeUtils::getDirectClassNames($classType); - foreach ($directClassNames as $className) { - if (!$this->reflectionProvider->hasClass($className)) { - continue; - } - - $classReflection = $this->reflectionProvider->getClass($className); - if ($classReflection->hasNativeMethod('__callStatic')) { - return []; - } - } - } - - return array_merge($errors, [ - RuleErrorBuilder::message(sprintf( - 'Call to an undefined static method %s::%s().', - $typeForDescribe->describe(VerbosityLevel::typeOnly()), - $methodName - ))->build(), - ]); - } - - $method = $classType->getMethod($methodName, $scope); - if (!$method->isStatic()) { - $function = $scope->getFunction(); - if ( - !$function instanceof MethodReflection - || $function->isStatic() - || !$scope->isInClass() - || ( - $classType instanceof TypeWithClassName - && $scope->getClassReflection()->getName() !== $classType->getClassName() - && !$scope->getClassReflection()->isSubclassOf($classType->getClassName()) - ) - ) { - return array_merge($errors, [ - RuleErrorBuilder::message(sprintf( - 'Static call to instance method %s::%s().', - $method->getDeclaringClass()->getDisplayName(), - $method->getName() - ))->build(), - ]); - } - } - - if (!$scope->canCallMethod($method)) { - $errors = array_merge($errors, [ - RuleErrorBuilder::message(sprintf( - 'Call to %s %s %s() of class %s.', - $method->isPrivate() ? 'private' : 'protected', - $method->isStatic() ? 'static method' : 'method', - $method->getName(), - $method->getDeclaringClass()->getDisplayName() - ))->build(), - ]); - } - - if ($isAbstract) { - return [ - RuleErrorBuilder::message(sprintf( - 'Cannot call abstract%s method %s::%s().', - $method->isStatic() ? ' static' : '', - $method->getDeclaringClass()->getDisplayName(), - $method->getName() - ))->build(), - ]; - } - - $lowercasedMethodName = SprintfHelper::escapeFormatString(sprintf( + $displayMethodName = SprintfHelper::escapeFormatString(sprintf( '%s %s', - $method->isStatic() ? 'static method' : 'method', + $method->isStatic() ? 'Static method' : 'Method', $method->getDeclaringClass()->getDisplayName() . '::' . $method->getName() . '()' )); - $displayMethodName = SprintfHelper::escapeFormatString(sprintf( + $lowercasedMethodName = SprintfHelper::escapeFormatString(sprintf( '%s %s', - $method->isStatic() ? 'Static method' : 'Method', + $method->isStatic() ? 'static method' : 'method', $method->getDeclaringClass()->getDisplayName() . '::' . $method->getName() . '()' )); - $errors = array_merge($errors, $this->check->check( + $errors = array_merge($errors, $this->parametersCheck->check( ParametersAcceptorSelector::selectFromArgs( $scope, $node->getArgs(), @@ -295,17 +82,6 @@ static function (Type $type) use ($methodName): bool { ] )); - if ( - $this->checkFunctionNameCase - && $method->getName() !== $methodName - ) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'Call to %s with incorrect case: %s', - $lowercasedMethodName, - $methodName - ))->build(); - } - return $errors; } diff --git a/src/Rules/Methods/MethodCallCheck.php b/src/Rules/Methods/MethodCallCheck.php index 3bf33fc9de..bc5ca888fc 100644 --- a/src/Rules/Methods/MethodCallCheck.php +++ b/src/Rules/Methods/MethodCallCheck.php @@ -36,6 +36,7 @@ public function __construct( $this->checkFunctionNameCase = $checkFunctionNameCase; $this->reportMagicMethods = $reportMagicMethods; } + /** * @return array{RuleError[], MethodReflection|null} */ diff --git a/src/Rules/Methods/StaticMethodCallCheck.php b/src/Rules/Methods/StaticMethodCallCheck.php new file mode 100644 index 0000000000..91cde7f403 --- /dev/null +++ b/src/Rules/Methods/StaticMethodCallCheck.php @@ -0,0 +1,292 @@ +reflectionProvider = $reflectionProvider; + $this->ruleLevelHelper = $ruleLevelHelper; + $this->classCaseSensitivityCheck = $classCaseSensitivityCheck; + $this->checkFunctionNameCase = $checkFunctionNameCase; + $this->reportMagicMethods = $reportMagicMethods; + } + /** + * @param Name|Expr $class + * @return array{RuleError[], MethodReflection|null} + */ + public function check( + Scope $scope, + string $methodName, + $class + ): array + { + $errors = []; + $isAbstract = false; + if ($class instanceof Name) { + $className = (string) $class; + $lowercasedClassName = strtolower($className); + if (in_array($lowercasedClassName, ['self', 'static'], true)) { + if (!$scope->isInClass()) { + return [ + [ + RuleErrorBuilder::message(sprintf( + 'Calling %s::%s() outside of class scope.', + $className, + $methodName + ))->build(), + ], + null, + ]; + } + $classType = $scope->resolveTypeByName($class); + } elseif ($lowercasedClassName === 'parent') { + if (!$scope->isInClass()) { + return [ + [ + RuleErrorBuilder::message(sprintf( + 'Calling %s::%s() outside of class scope.', + $className, + $methodName + ))->build(), + ], + null, + ]; + } + $currentClassReflection = $scope->getClassReflection(); + if ($currentClassReflection->getParentClass() === null) { + return [ + [ + RuleErrorBuilder::message(sprintf( + '%s::%s() calls parent::%s() but %s does not extend any class.', + $scope->getClassReflection()->getDisplayName(), + $scope->getFunctionName(), + $methodName, + $scope->getClassReflection()->getDisplayName() + ))->build(), + ], + null, + ]; + } + + if ($scope->getFunctionName() === null) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $classType = $scope->resolveTypeByName($class); + } else { + if (!$this->reflectionProvider->hasClass($className)) { + if ($scope->isInClassExists($className)) { + return [[], null]; + } + + return [ + [ + RuleErrorBuilder::message(sprintf( + 'Call to static method %s() on an unknown class %s.', + $methodName, + $className + ))->discoveringSymbolsTip()->build(), + ], + null, + ]; + } else { + $errors = $this->classCaseSensitivityCheck->checkClassNames([new ClassNameNodePair($className, $class)]); + } + + $classType = $scope->resolveTypeByName($class); + } + + $classReflection = $classType->getClassReflection(); + if ($classReflection !== null && $classReflection->hasNativeMethod($methodName) && $lowercasedClassName !== 'static') { + $nativeMethodReflection = $classReflection->getNativeMethod($methodName); + if ($nativeMethodReflection instanceof PhpMethodReflection || $nativeMethodReflection instanceof NativeMethodReflection) { + $isAbstract = $nativeMethodReflection->isAbstract(); + } + } + } else { + $classTypeResult = $this->ruleLevelHelper->findTypeToCheck( + $scope, + NullsafeOperatorHelper::getNullsafeShortcircuitedExprRespectingScope($scope, $class), + sprintf('Call to static method %s() on an unknown class %%s.', SprintfHelper::escapeFormatString($methodName)), + static function (Type $type) use ($methodName): bool { + return $type->canCallMethods()->yes() && $type->hasMethod($methodName)->yes(); + } + ); + $classType = $classTypeResult->getType(); + if ($classType instanceof ErrorType) { + return [$classTypeResult->getUnknownClassErrors(), null]; + } + } + + if ($classType instanceof GenericClassStringType) { + $classType = $classType->getGenericType(); + if (!(new ObjectWithoutClassType())->isSuperTypeOf($classType)->yes()) { + return [[], null]; + } + } elseif ((new StringType())->isSuperTypeOf($classType)->yes()) { + return [[], null]; + } + + $typeForDescribe = $classType; + if ($classType instanceof ThisType) { + $typeForDescribe = $classType->getStaticObjectType(); + } + $classType = TypeCombinator::remove($classType, new StringType()); + + if (!$classType->canCallMethods()->yes()) { + return [ + array_merge($errors, [ + RuleErrorBuilder::message(sprintf( + 'Cannot call static method %s() on %s.', + $methodName, + $typeForDescribe->describe(VerbosityLevel::typeOnly()) + ))->build(), + ]), + null, + ]; + } + + if (!$classType->hasMethod($methodName)->yes()) { + if (!$this->reportMagicMethods) { + $directClassNames = TypeUtils::getDirectClassNames($classType); + foreach ($directClassNames as $className) { + if (!$this->reflectionProvider->hasClass($className)) { + continue; + } + + $classReflection = $this->reflectionProvider->getClass($className); + if ($classReflection->hasNativeMethod('__callStatic')) { + return [[], null]; + } + } + } + + return [ + array_merge($errors, [ + RuleErrorBuilder::message(sprintf( + 'Call to an undefined static method %s::%s().', + $typeForDescribe->describe(VerbosityLevel::typeOnly()), + $methodName + ))->build(), + ]), + null, + ]; + } + + $method = $classType->getMethod($methodName, $scope); + if (!$method->isStatic()) { + $function = $scope->getFunction(); + if ( + !$function instanceof MethodReflection + || $function->isStatic() + || !$scope->isInClass() + || ( + $classType instanceof TypeWithClassName + && $scope->getClassReflection()->getName() !== $classType->getClassName() + && !$scope->getClassReflection()->isSubclassOf($classType->getClassName()) + ) + ) { + return [ + array_merge($errors, [ + RuleErrorBuilder::message(sprintf( + 'Static call to instance method %s::%s().', + $method->getDeclaringClass()->getDisplayName(), + $method->getName() + ))->build(), + ]), + $method, + ]; + } + } + + if (!$scope->canCallMethod($method)) { + $errors = array_merge($errors, [ + RuleErrorBuilder::message(sprintf( + 'Call to %s %s %s() of class %s.', + $method->isPrivate() ? 'private' : 'protected', + $method->isStatic() ? 'static method' : 'method', + $method->getName(), + $method->getDeclaringClass()->getDisplayName() + ))->build(), + ]); + } + + if ($isAbstract) { + return [ + [ + RuleErrorBuilder::message(sprintf( + 'Cannot call abstract%s method %s::%s().', + $method->isStatic() ? ' static' : '', + $method->getDeclaringClass()->getDisplayName(), + $method->getName() + ))->build(), + ], + $method, + ]; + } + + $lowercasedMethodName = SprintfHelper::escapeFormatString(sprintf( + '%s %s', + $method->isStatic() ? 'static method' : 'method', + $method->getDeclaringClass()->getDisplayName() . '::' . $method->getName() . '()' + )); + + if ( + $this->checkFunctionNameCase + && $method->getName() !== $methodName + ) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Call to %s with incorrect case: %s', + $lowercasedMethodName, + $methodName + ))->build(); + } + + return [$errors, $method]; + } + +} diff --git a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php index 562351aa50..5bf70d9b32 100644 --- a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php @@ -20,15 +20,11 @@ class CallStaticMethodsRuleTest extends \PHPStan\Testing\RuleTestCase protected function getRule(): \PHPStan\Rules\Rule { - $broker = $this->createReflectionProvider(); - $ruleLevelHelper = new RuleLevelHelper($broker, true, $this->checkThisOnly, true, false); + $reflectionProvider = $this->createReflectionProvider(); + $ruleLevelHelper = new RuleLevelHelper($reflectionProvider, true, $this->checkThisOnly, true, false); return new CallStaticMethodsRule( - $broker, - new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), true, true, true, true), - $ruleLevelHelper, - new ClassCaseSensitivityCheck($broker, true), - true, - true + new StaticMethodCallCheck($reflectionProvider, $ruleLevelHelper, new ClassCaseSensitivityCheck($reflectionProvider, true), true, true), + new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new PhpVersion(80000), new UnresolvableTypeHelper(), true, true, true, true) ); } From b42ceb618accbe20651165b0a049da299013e273 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 18 Nov 2021 10:55:17 +0100 Subject: [PATCH 0591/1284] Check static methods in first-class callables --- conf/config.level0.neon | 1 + .../Methods/StaticMethodCallableRule.php | 68 ++++++++++++ .../Methods/StaticMethodCallableRuleTest.php | 100 ++++++++++++++++++ .../static-method-callable-not-supported.php | 13 +++ .../Methods/data/static-method-callable.php | 69 ++++++++++++ 5 files changed, 251 insertions(+) create mode 100644 src/Rules/Methods/StaticMethodCallableRule.php create mode 100644 tests/PHPStan/Rules/Methods/StaticMethodCallableRuleTest.php create mode 100644 tests/PHPStan/Rules/Methods/data/static-method-callable-not-supported.php create mode 100644 tests/PHPStan/Rules/Methods/data/static-method-callable.php diff --git a/conf/config.level0.neon b/conf/config.level0.neon index 34847e99e3..9411e88475 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -54,6 +54,7 @@ rules: - PHPStan\Rules\Methods\MethodCallableRule - PHPStan\Rules\Methods\MissingMethodImplementationRule - PHPStan\Rules\Methods\MethodAttributesRule + - PHPStan\Rules\Methods\StaticMethodCallableRule - PHPStan\Rules\Operators\InvalidAssignVarRule - PHPStan\Rules\Properties\AccessPropertiesInAssignRule - PHPStan\Rules\Properties\AccessStaticPropertiesInAssignRule diff --git a/src/Rules/Methods/StaticMethodCallableRule.php b/src/Rules/Methods/StaticMethodCallableRule.php new file mode 100644 index 0000000000..7325a0ba02 --- /dev/null +++ b/src/Rules/Methods/StaticMethodCallableRule.php @@ -0,0 +1,68 @@ + + */ +class StaticMethodCallableRule implements Rule +{ + + private StaticMethodCallCheck $methodCallCheck; + + private PhpVersion $phpVersion; + + public function __construct(StaticMethodCallCheck $methodCallCheck, PhpVersion $phpVersion) + { + $this->methodCallCheck = $methodCallCheck; + $this->phpVersion = $phpVersion; + } + + public function getNodeType(): string + { + return StaticMethodCallableNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$this->phpVersion->supportsFirstClassCallables()) { + return [ + RuleErrorBuilder::message('First-class callables are supported only on PHP 8.1 and later.') + ->nonIgnorable() + ->build(), + ]; + } + + $methodName = $node->getName(); + if (!$methodName instanceof Node\Identifier) { + return []; + } + + $methodNameName = $methodName->toString(); + + [$errors, $methodReflection] = $this->methodCallCheck->check($scope, $methodNameName, $node->getClass()); + if ($methodReflection === null) { + return $errors; + } + + $declaringClass = $methodReflection->getDeclaringClass(); + if ($declaringClass->hasNativeMethod($methodNameName)) { + return $errors; + } + + $messagesMethodName = SprintfHelper::escapeFormatString($declaringClass->getDisplayName() . '::' . $methodReflection->getName() . '()'); + + $errors[] = RuleErrorBuilder::message(sprintf('Creating callable from a non-native static method %s.', $messagesMethodName))->build(); + + return $errors; + } + +} diff --git a/tests/PHPStan/Rules/Methods/StaticMethodCallableRuleTest.php b/tests/PHPStan/Rules/Methods/StaticMethodCallableRuleTest.php new file mode 100644 index 0000000000..234988ab56 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/StaticMethodCallableRuleTest.php @@ -0,0 +1,100 @@ + + */ +class StaticMethodCallableRuleTest extends RuleTestCase +{ + + /** @var int */ + private $phpVersion = PHP_VERSION_ID; + + protected function getRule(): Rule + { + $reflectionProvider = $this->createReflectionProvider(); + $ruleLevelHelper = new RuleLevelHelper($reflectionProvider, true, false, true, false); + + return new StaticMethodCallableRule( + new StaticMethodCallCheck($reflectionProvider, $ruleLevelHelper, new ClassCaseSensitivityCheck($reflectionProvider, true), true, true), + new PhpVersion($this->phpVersion) + ); + } + + public function testNotSupportedOnOlderVersions(): void + { + if (PHP_VERSION_ID >= 80100) { + self::markTestSkipped('Test runs on PHP < 8.1.'); + } + if (!self::$useStaticReflectionProvider) { + self::markTestSkipped('Test requires static reflection.'); + } + + $this->analyse([__DIR__ . '/data/static-method-callable-not-supported.php'], [ + [ + 'First-class callables are supported only on PHP 8.1 and later.', + 10, + ], + ]); + } + + public function testRule(): void + { + if (PHP_VERSION_ID < 80100) { + self::markTestSkipped('Test requires PHP 8.1.'); + } + + $this->analyse([__DIR__ . '/data/static-method-callable.php'], [ + [ + 'Call to static method StaticMethodCallable\Foo::doFoo() with incorrect case: dofoo', + 11, + ], + [ + 'Call to static method doFoo() on an unknown class StaticMethodCallable\Nonexistent.', + 12, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Call to an undefined static method StaticMethodCallable\Foo::nonexistent().', + 13, + ], + [ + 'Static call to instance method StaticMethodCallable\Foo::doBar().', + 14, + ], + [ + 'Call to private static method doBar() of class StaticMethodCallable\Bar.', + 15, + ], + [ + 'Cannot call abstract static method StaticMethodCallable\Bar::doBaz().', + 16, + ], + [ + 'Call to static method doFoo() on an unknown class StaticMethodCallable\Nonexistent.', + 21, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'Cannot call static method doFoo() on int.', + 22, + ], + [ + 'Creating callable from a non-native static method StaticMethodCallable\Lorem::doBar().', + 47, + ], + [ + 'Creating callable from a non-native static method StaticMethodCallable\Ipsum::doBar().', + 66, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Methods/data/static-method-callable-not-supported.php b/tests/PHPStan/Rules/Methods/data/static-method-callable-not-supported.php new file mode 100644 index 0000000000..0aaec79db5 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/static-method-callable-not-supported.php @@ -0,0 +1,13 @@ += 8.1 + +namespace StaticMethodCallableNotSupported; + +class Foo +{ + + public static function doFoo(): void + { + self::doFoo(...); + } + +} diff --git a/tests/PHPStan/Rules/Methods/data/static-method-callable.php b/tests/PHPStan/Rules/Methods/data/static-method-callable.php new file mode 100644 index 0000000000..d8256c1c4b --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/static-method-callable.php @@ -0,0 +1,69 @@ += 8.1 + +namespace StaticMethodCallable; + +class Foo +{ + + public static function doFoo() + { + self::doFoo(...); + self::dofoo(...); + Nonexistent::doFoo(...); + self::nonexistent(...); + self::doBar(...); + Bar::doBar(...); + Bar::doBaz(...); + } + + public function doBar(Nonexistent $n, int $i) + { + $n::doFoo(...); + $i::doFoo(...); + } + +} + +abstract class Bar +{ + + private static function doBar() + { + + } + + abstract public static function doBaz(); + +} + +/** + * @method static void doBar() + */ +class Lorem +{ + + public function doFoo() + { + self::doBar(...); + } + + public function __call($name, $arguments) + { + + } + + +} + +/** + * @method static void doBar() + */ +class Ipsum +{ + + public function doFoo() + { + self::doBar(...); + } + +} From 5a0d0314ea20b905e952670820554c541db8029c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 18 Nov 2021 11:26:54 +0100 Subject: [PATCH 0592/1284] Check creation of first-class callable without using it --- ...unctionStatementWithoutSideEffectsRule.php | 10 ++-- ...oMethodStatementWithoutSideEffectsRule.php | 10 ++-- ...cMethodStatementWithoutSideEffectsRule.php | 10 ++-- ...ionStatementWithoutSideEffectsRuleTest.php | 26 ++++++++++ ...-callable-function-without-side-effect.php | 50 +++++++++++++++++++ ...hodStatementWithoutSideEffectsRuleTest.php | 22 ++++++++ ...hodStatementWithoutSideEffectsRuleTest.php | 22 ++++++++ ...ss-callable-method-without-side-effect.php | 42 ++++++++++++++++ ...able-static-method-without-side-effect.php | 42 ++++++++++++++++ 9 files changed, 222 insertions(+), 12 deletions(-) create mode 100644 tests/PHPStan/Rules/Functions/data/first-class-callable-function-without-side-effect.php create mode 100644 tests/PHPStan/Rules/Methods/data/first-class-callable-method-without-side-effect.php create mode 100644 tests/PHPStan/Rules/Methods/data/first-class-callable-static-method-without-side-effect.php diff --git a/src/Rules/Functions/CallToFunctionStatementWithoutSideEffectsRule.php b/src/Rules/Functions/CallToFunctionStatementWithoutSideEffectsRule.php index 5c4bc21420..64a8bb8733 100644 --- a/src/Rules/Functions/CallToFunctionStatementWithoutSideEffectsRule.php +++ b/src/Rules/Functions/CallToFunctionStatementWithoutSideEffectsRule.php @@ -44,10 +44,12 @@ public function processNode(Node $node, Scope $scope): array } $function = $this->reflectionProvider->getFunction($funcCall->name, $scope); - if ($function->hasSideEffects()->no()) { - $throwsType = $function->getThrowType(); - if ($throwsType !== null && !$throwsType instanceof VoidType) { - return []; + if ($function->hasSideEffects()->no() || $node->expr->isFirstClassCallable()) { + if (!$node->expr->isFirstClassCallable()) { + $throwsType = $function->getThrowType(); + if ($throwsType !== null && !$throwsType instanceof VoidType) { + return []; + } } $functionResult = $scope->getType($funcCall); diff --git a/src/Rules/Methods/CallToMethodStatementWithoutSideEffectsRule.php b/src/Rules/Methods/CallToMethodStatementWithoutSideEffectsRule.php index 474910e2c6..8c5e02171e 100644 --- a/src/Rules/Methods/CallToMethodStatementWithoutSideEffectsRule.php +++ b/src/Rules/Methods/CallToMethodStatementWithoutSideEffectsRule.php @@ -66,10 +66,12 @@ static function (Type $type) use ($methodName): bool { } $method = $calledOnType->getMethod($methodName, $scope); - if ($method->hasSideEffects()->no()) { - $throwsType = $method->getThrowType(); - if ($throwsType !== null && !$throwsType instanceof VoidType) { - return []; + if ($method->hasSideEffects()->no() || ($node->expr instanceof Node\Expr\CallLike && $node->expr->isFirstClassCallable())) { + if (!$node->expr instanceof Node\Expr\CallLike || !$node->expr->isFirstClassCallable()) { + $throwsType = $method->getThrowType(); + if ($throwsType !== null && !$throwsType instanceof VoidType) { + return []; + } } $methodResult = $scope->getType($methodCall); diff --git a/src/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRule.php b/src/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRule.php index f6e68c6996..13e026fd59 100644 --- a/src/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRule.php +++ b/src/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRule.php @@ -91,10 +91,12 @@ static function (Type $type) use ($methodName): bool { return []; } - if ($method->hasSideEffects()->no()) { - $throwsType = $method->getThrowType(); - if ($throwsType !== null && !$throwsType instanceof VoidType) { - return []; + if ($method->hasSideEffects()->no() || $node->expr->isFirstClassCallable()) { + if (!$node->expr->isFirstClassCallable()) { + $throwsType = $method->getThrowType(); + if ($throwsType !== null && !$throwsType instanceof VoidType) { + return []; + } } $methodResult = $scope->getType($staticCall); diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionStatementWithoutSideEffectsRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionStatementWithoutSideEffectsRuleTest.php index f47f1f4461..019830556d 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionStatementWithoutSideEffectsRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionStatementWithoutSideEffectsRuleTest.php @@ -55,4 +55,30 @@ public function testBug4455(): void $this->analyse([__DIR__ . '/data/bug-4455.php'], []); } + public function testFirstClassCallables(): void + { + if (PHP_VERSION_ID < 80100) { + self::markTestSkipped('Test requires PHP 8.1.'); + } + + $this->analyse([__DIR__ . '/data/first-class-callable-function-without-side-effect.php'], [ + [ + 'Call to function mkdir() on a separate line has no effect.', + 12, + ], + [ + 'Call to function strlen() on a separate line has no effect.', + 24, + ], + [ + 'Call to function FirstClassCallableFunctionWithoutSideEffect\foo() on a separate line has no effect.', + 36, + ], + [ + 'Call to function FirstClassCallableFunctionWithoutSideEffect\bar() on a separate line has no effect.', + 49, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/first-class-callable-function-without-side-effect.php b/tests/PHPStan/Rules/Functions/data/first-class-callable-function-without-side-effect.php new file mode 100644 index 0000000000..52282c5099 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/first-class-callable-function-without-side-effect.php @@ -0,0 +1,50 @@ += 8.1 + +namespace FirstClassCallableFunctionWithoutSideEffect; + +class Foo +{ + + public static function doFoo(): void + { + $f = mkdir(...); + + mkdir(...); + } + +} + +class Bar +{ + + public static function doFoo(): void + { + $f = strlen(...); + + strlen(...); + } + +} + +function foo(): never +{ + throw new \Exception(); +} + +function (): void { + $f = foo(...); + foo(...); +}; + +/** + * @throws \Exception + */ +function bar() +{ + throw new \Exception(); +} + +function (): void { + $f = bar(...); + bar(...); +}; diff --git a/tests/PHPStan/Rules/Methods/CallToMethodStatementWithoutSideEffectsRuleTest.php b/tests/PHPStan/Rules/Methods/CallToMethodStatementWithoutSideEffectsRuleTest.php index a2a63eb692..f834450c39 100644 --- a/tests/PHPStan/Rules/Methods/CallToMethodStatementWithoutSideEffectsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallToMethodStatementWithoutSideEffectsRuleTest.php @@ -85,4 +85,26 @@ public function testBug4455(): void $this->analyse([__DIR__ . '/data/bug-4455.php'], []); } + public function testFirstClassCallables(): void + { + if (PHP_VERSION_ID < 80100) { + self::markTestSkipped('Test requires PHP 8.1.'); + } + + $this->analyse([__DIR__ . '/data/first-class-callable-method-without-side-effect.php'], [ + [ + 'Call to method FirstClassCallableMethodWithoutSideEffect\Foo::doFoo() on a separate line has no effect.', + 12, + ], + [ + 'Call to method FirstClassCallableMethodWithoutSideEffect\Bar::doFoo() on a separate line has no effect.', + 36, + ], + [ + 'Call to method FirstClassCallableMethodWithoutSideEffect\Bar::doBar() on a separate line has no effect.', + 39, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRuleTest.php b/tests/PHPStan/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRuleTest.php index 08832088ee..7ae71ccdce 100644 --- a/tests/PHPStan/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRuleTest.php @@ -66,4 +66,26 @@ public function testBug4455(): void $this->analyse([__DIR__ . '/data/bug-4455-static.php'], []); } + public function testFirstClassCallables(): void + { + if (PHP_VERSION_ID < 80100) { + self::markTestSkipped('Test requires PHP 8.1.'); + } + + $this->analyse([__DIR__ . '/data/first-class-callable-static-method-without-side-effect.php'], [ + [ + 'Call to static method FirstClassCallableStaticMethodWithoutSideEffect\Foo::doFoo() on a separate line has no effect.', + 12, + ], + [ + 'Call to static method FirstClassCallableStaticMethodWithoutSideEffect\Bar::doFoo() on a separate line has no effect.', + 36, + ], + [ + 'Call to static method FirstClassCallableStaticMethodWithoutSideEffect\Bar::doBar() on a separate line has no effect.', + 39, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/first-class-callable-method-without-side-effect.php b/tests/PHPStan/Rules/Methods/data/first-class-callable-method-without-side-effect.php new file mode 100644 index 0000000000..78489a0c19 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/first-class-callable-method-without-side-effect.php @@ -0,0 +1,42 @@ += 8.1 + +namespace FirstClassCallableMethodWithoutSideEffect; + +class Foo +{ + + public function doFoo(): void + { + $f = $this->doFoo(...); + + $this->doFoo(...); + } + +} + +class Bar +{ + + function doFoo(): never + { + throw new \Exception(); + } + + /** + * @throws \Exception + */ + function doBar() + { + throw new \Exception(); + } + + function doBaz(): void + { + $f = $this->doFoo(...); + $this->doFoo(...); + + $g = $this->doBar(...); + $this->doBar(...); + } + +} diff --git a/tests/PHPStan/Rules/Methods/data/first-class-callable-static-method-without-side-effect.php b/tests/PHPStan/Rules/Methods/data/first-class-callable-static-method-without-side-effect.php new file mode 100644 index 0000000000..b387cd8be0 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/first-class-callable-static-method-without-side-effect.php @@ -0,0 +1,42 @@ += 8.1 + +namespace FirstClassCallableStaticMethodWithoutSideEffect; + +class Foo +{ + + public static function doFoo(): void + { + $f = self::doFoo(...); + + self::doFoo(...); + } + +} + +class Bar +{ + + static function doFoo(): never + { + throw new \Exception(); + } + + /** + * @throws \Exception + */ + static function doBar() + { + throw new \Exception(); + } + + function doBaz(): void + { + $f = self::doFoo(...); + self::doFoo(...); + + $g = self::doBar(...); + self::doBar(...); + } + +} From 61c4fea2a6f65cf40da01136220dfd59bdc5d768 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 18 Nov 2021 14:31:32 +0100 Subject: [PATCH 0593/1284] Test - check that first-class callables are not throw points --- .../Analyser/data/first-class-callables.php | 51 +++++++++++++++++++ .../CatchWithUnthrownExceptionRuleTest.php | 14 +++++ .../data/dead-catch-first-class-callables.php | 34 +++++++++++++ 3 files changed, 99 insertions(+) create mode 100644 tests/PHPStan/Rules/Exceptions/data/dead-catch-first-class-callables.php diff --git a/tests/PHPStan/Analyser/data/first-class-callables.php b/tests/PHPStan/Analyser/data/first-class-callables.php index 5473388adf..96f1454d8b 100644 --- a/tests/PHPStan/Analyser/data/first-class-callables.php +++ b/tests/PHPStan/Analyser/data/first-class-callables.php @@ -2,7 +2,9 @@ namespace FirstClassCallables; +use PHPStan\TrinaryLogic; use function PHPStan\Testing\assertType; +use function PHPStan\Testing\assertVariableCertainty; class Foo { @@ -63,3 +65,52 @@ public function doBaz() } } + +class NeverCallable +{ + + public function doFoo() + { + $n = function (): never { + throw new \Exception(); + }; + + if (rand(0, 1)) { + $n(); + } else { + $foo = 1; + } + + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } + + public function doBar() + { + $n = function (): never { + throw new \Exception(); + }; + + if (rand(0, 1)) { + $n(...); + } else { + $foo = 1; + } + + assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); + } + + /** + * @param callable(): never $n + */ + public function doBaz(callable $n): void + { + if (rand(0, 1)) { + $n(); + } else { + $foo = 1; + } + + assertVariableCertainty(TrinaryLogic::createYes(), $foo); + } + +} diff --git a/tests/PHPStan/Rules/Exceptions/CatchWithUnthrownExceptionRuleTest.php b/tests/PHPStan/Rules/Exceptions/CatchWithUnthrownExceptionRuleTest.php index 15c4930e07..b94afb8b1a 100644 --- a/tests/PHPStan/Rules/Exceptions/CatchWithUnthrownExceptionRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/CatchWithUnthrownExceptionRuleTest.php @@ -181,4 +181,18 @@ public function testDeadCatch(): void ]); } + public function testFirstClassCallables(): void + { + if (PHP_VERSION_ID < 80100) { + self::markTestSkipped('Test requires PHP 8.1.'); + } + + $this->analyse([__DIR__ . '/data/dead-catch-first-class-callables.php'], [ + [ + 'Dead catch - InvalidArgumentException is never thrown in the try block.', + 29, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Exceptions/data/dead-catch-first-class-callables.php b/tests/PHPStan/Rules/Exceptions/data/dead-catch-first-class-callables.php new file mode 100644 index 0000000000..a212159e99 --- /dev/null +++ b/tests/PHPStan/Rules/Exceptions/data/dead-catch-first-class-callables.php @@ -0,0 +1,34 @@ += 8.1 + +namespace DeadCatchFirstClassCallables; + +class Foo +{ + + public function doFoo(): void + { + try { + $this->doBar(); + } catch (\InvalidArgumentException $e) { + + } + } + + /** + * @throws \InvalidArgumentException + */ + public function doBar(): void + { + throw new \InvalidArgumentException(); + } + + public function doBaz(): void + { + try { + $this->doBar(...); + } catch (\InvalidArgumentException $e) { + + } + } + +} From c862bb97482bced730988217685a8ae019436ad6 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 18 Nov 2021 14:40:00 +0100 Subject: [PATCH 0594/1284] First-class callables - processExpr --- src/Analyser/NodeScopeResolver.php | 44 ++++++++++++++ .../Variables/DefinedVariableRuleTest.php | 38 ++++++++++++ .../Variables/data/first-class-callables.php | 60 +++++++++++++++++++ 3 files changed, 142 insertions(+) create mode 100644 tests/PHPStan/Rules/Variables/data/first-class-callables.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 0d1fcd6cf9..f9db619006 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -2656,6 +2656,50 @@ static function () use ($finalScope, $expr): MutatingScope { $result = $this->processExprNode($expr->expr, $scope, $nodeCallback, ExpressionContext::createDeep()); $throwPoints = $result->getThrowPoints(); $throwPoints[] = ThrowPoint::createExplicit($scope, $scope->getType($expr->expr), $expr, false); + } elseif ($expr instanceof FunctionCallableNode) { + $throwPoints = []; + $hasYield = false; + if ($expr->getName() instanceof Expr) { + $result = $this->processExprNode($expr->getName(), $scope, $nodeCallback, ExpressionContext::createDeep()); + $scope = $result->getScope(); + $hasYield = $result->hasYield(); + $throwPoints = $result->getThrowPoints(); + } + } elseif ($expr instanceof MethodCallableNode) { + $result = $this->processExprNode($expr->getVar(), $scope, $nodeCallback, ExpressionContext::createDeep()); + $scope = $result->getScope(); + $hasYield = $result->hasYield(); + $throwPoints = $result->getThrowPoints(); + if ($expr->getName() instanceof Expr) { + $nameResult = $this->processExprNode($expr->getVar(), $scope, $nodeCallback, ExpressionContext::createDeep()); + $scope = $nameResult->getScope(); + $hasYield = $hasYield || $nameResult->hasYield(); + $throwPoints = array_merge($throwPoints, $nameResult->getThrowPoints()); + } + } elseif ($expr instanceof StaticMethodCallableNode) { + $throwPoints = []; + $hasYield = false; + if ($expr->getClass() instanceof Expr) { + $classResult = $this->processExprNode($expr->getClass(), $scope, $nodeCallback, ExpressionContext::createDeep()); + $scope = $classResult->getScope(); + $hasYield = $classResult->hasYield(); + $throwPoints = $classResult->getThrowPoints(); + } + if ($expr->getName() instanceof Expr) { + $nameResult = $this->processExprNode($expr->getName(), $scope, $nodeCallback, ExpressionContext::createDeep()); + $scope = $nameResult->getScope(); + $hasYield = $hasYield || $nameResult->hasYield(); + $throwPoints = array_merge($throwPoints, $nameResult->getThrowPoints()); + } + } elseif ($expr instanceof InstantiationCallableNode) { + $throwPoints = []; + $hasYield = false; + if ($expr->getClass() instanceof Expr) { + $classResult = $this->processExprNode($expr->getClass(), $scope, $nodeCallback, ExpressionContext::createDeep()); + $scope = $classResult->getScope(); + $hasYield = $classResult->hasYield(); + $throwPoints = $classResult->getThrowPoints(); + } } else { $hasYield = false; $throwPoints = []; diff --git a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php index 7e82582d7d..3604b1b6e7 100644 --- a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php +++ b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php @@ -818,4 +818,42 @@ public function testBug3283(): void $this->analyse([__DIR__ . '/data/bug-3283.php'], []); } + public function testFirstClassCallables(): void + { + if (PHP_VERSION_ID < 80100) { + self::markTestSkipped('Test requires PHP 8.1.'); + } + + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = false; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + $this->analyse([__DIR__ . '/data/first-class-callables.php'], [ + [ + 'Undefined variable: $foo', + 10, + ], + [ + 'Undefined variable: $foo', + 11, + ], + [ + 'Undefined variable: $foo', + 29, + ], + [ + 'Undefined variable: $foo', + 30, + ], + [ + 'Undefined variable: $foo', + 48, + ], + [ + 'Undefined variable: $foo', + 49, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Variables/data/first-class-callables.php b/tests/PHPStan/Rules/Variables/data/first-class-callables.php new file mode 100644 index 0000000000..c4695a551b --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/first-class-callables.php @@ -0,0 +1,60 @@ += 8.1 + +namespace FirstClassCallablesDefinedVariables; + +class Foo +{ + + public function doFoo(): void + { + $foo->doFoo(); + $foo->doFoo(...); + } + + public function doBar(object $o): void + { + $o->doFoo(...); + ($p = $o)->doFoo(...); + $p->doFoo(); + $p->doFoo(...); + } + +} + +class Bar +{ + + public function doFoo(): void + { + $foo::doFoo(); + $foo::doFoo(...); + } + + public function doBar(object $o): void + { + $o::doFoo(...); + ($p = $o)::doFoo(...); + $p::doFoo(); + $p::doFoo(...); + } + +} + +class Baz +{ + + public function doFoo(): void + { + $foo(); + $foo(...); + } + + public function doBar(object $o): void + { + $o(...); + ($p = $o)(...); + $p(); + $p(...); + } + +} From cb0ba9506c38d252b4b6b3bfe510e6f64bf04ba4 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Thu, 18 Nov 2021 18:55:42 +0000 Subject: [PATCH 0595/1284] `echo` isn't callable either. --- resources/functionMap.php | 1 - 1 file changed, 1 deletion(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 6b36b462c1..3920e3052a 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -2232,7 +2232,6 @@ 'each' => ['array', '&rw_arr'=>'array'], 'easter_date' => ['int', 'year='=>'int'], 'easter_days' => ['int', 'year='=>'int', 'method='=>'int'], -'echo' => ['void', 'arg1'=>'string', '...args='=>'string'], 'eio_busy' => ['resource', 'delay'=>'int', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'], 'eio_cancel' => ['void', 'req'=>'resource'], 'eio_chmod' => ['resource', 'path'=>'string', 'mode'=>'int', 'pri='=>'int', 'callback='=>'callable', 'data='=>'mixed'], From 916706143e2279ca14473a8fd5886647cce64363 Mon Sep 17 00:00:00 2001 From: Marcin Michalski Date: Wed, 27 Oct 2021 22:42:01 +0100 Subject: [PATCH 0596/1284] Resolve object type by class const fetch in switch statement --- src/Analyser/TypeSpecifier.php | 25 ++++++++++++++++--- .../Analyser/NodeScopeResolverTest.php | 2 ++ tests/PHPStan/Analyser/data/bug-4896.php | 21 ++++++++++++++++ tests/PHPStan/Analyser/data/bug-5843.php | 19 ++++++++++++++ 4 files changed, 64 insertions(+), 3 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/bug-4896.php create mode 100644 tests/PHPStan/Analyser/data/bug-5843.php diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 10e7791809..d2e9a591d1 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -9,6 +9,7 @@ use PhpParser\Node\Expr\BinaryOp\BooleanOr; use PhpParser\Node\Expr\BinaryOp\LogicalAnd; use PhpParser\Node\Expr\BinaryOp\LogicalOr; +use PhpParser\Node\Expr\ClassConstFetch; use PhpParser\Node\Expr\ConstFetch; use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Expr\Instanceof_; @@ -350,8 +351,26 @@ public function specifyTypesInCondition( } $leftType = $scope->getType($expr->left); - $leftBooleanType = $leftType->toBoolean(); $rightType = $scope->getType($expr->right); + if ( + $expr->left instanceof ClassConstFetch && + $expr->left->class instanceof Expr && + $expr->left->name instanceof Node\Identifier && + $expr->right instanceof ClassConstFetch && + $rightType instanceof ConstantStringType && + strtolower($expr->left->name->toString()) === 'class' + ) { + return $this->specifyTypesInCondition( + $scope, + new Instanceof_( + $expr->left->class, + new Name($rightType->getValue()) + ), + $context + ); + } + + $leftBooleanType = $leftType->toBoolean(); if ($leftBooleanType instanceof ConstantBooleanType && $rightType instanceof BooleanType) { return $this->specifyTypesInCondition( $scope, @@ -927,13 +946,13 @@ private function findTypeExpressionsFromBinaryOperation(Scope $scope, Node\Expr\ if ( $leftType instanceof \PHPStan\Type\ConstantScalarType && !$binaryOperation->right instanceof ConstFetch - && !$binaryOperation->right instanceof Expr\ClassConstFetch + && !$binaryOperation->right instanceof ClassConstFetch ) { return [$binaryOperation->right, $leftType]; } elseif ( $rightType instanceof \PHPStan\Type\ConstantScalarType && !$binaryOperation->left instanceof ConstFetch - && !$binaryOperation->left instanceof Expr\ClassConstFetch + && !$binaryOperation->left instanceof ClassConstFetch ) { return [$binaryOperation->left, $rightType]; } diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 51c360228a..9c55fd8a3b 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -520,6 +520,8 @@ public function dataFileAsserts(): iterable if (PHP_VERSION_ID >= 80000) { yield from $this->gatherAssertTypes(__DIR__ . '/data/variadic-parameter-php8.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4896.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5843.php'); } yield from $this->gatherAssertTypes(__DIR__ . '/data/eval-implicit-throw.php'); diff --git a/tests/PHPStan/Analyser/data/bug-4896.php b/tests/PHPStan/Analyser/data/bug-4896.php new file mode 100644 index 0000000000..d3d727e930 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-4896.php @@ -0,0 +1,21 @@ +getTimestamp()); + break; + case \DateInterval::class: + assertType(\DateInterval::class, $command); + echo " Hello Date Interval " . $command->format('d'); + break; +} diff --git a/tests/PHPStan/Analyser/data/bug-5843.php b/tests/PHPStan/Analyser/data/bug-5843.php new file mode 100644 index 0000000000..e57a15eeef --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-5843.php @@ -0,0 +1,19 @@ +modify('+1 day'); + break; + case \Throwable::class: + assertType(\Throwable::class, $object); + $object->getPrevious(); + break; + } +} From c1579f15c61db47950854c7bd17abfe177bca1d1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 19 Nov 2021 10:23:20 +0100 Subject: [PATCH 0597/1284] Allow also $object::class with Identical --- src/Analyser/TypeSpecifier.php | 24 ++++++++++--- tests/PHPStan/Analyser/data/bug-4896.php | 43 +++++++++++++++++------- tests/PHPStan/Analyser/data/bug-5843.php | 38 +++++++++++++++------ 3 files changed, 77 insertions(+), 28 deletions(-) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index d2e9a591d1..0f2eb27c3f 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -244,6 +244,25 @@ public function specifyTypesInCondition( } } + $rightType = $scope->getType($expr->right); + if ( + $expr->left instanceof ClassConstFetch && + $expr->left->class instanceof Expr && + $expr->left->name instanceof Node\Identifier && + $expr->right instanceof ClassConstFetch && + $rightType instanceof ConstantStringType && + strtolower($expr->left->name->toString()) === 'class' + ) { + return $this->specifyTypesInCondition( + $scope, + new Instanceof_( + $expr->left->class, + new Name($rightType->getValue()) + ), + $context + ); + } + if ($context->true()) { $type = TypeCombinator::intersect($scope->getType($expr->right), $scope->getType($expr->left)); $leftTypes = $this->create($expr->left, $type, $context, false, $scope); @@ -362,10 +381,7 @@ public function specifyTypesInCondition( ) { return $this->specifyTypesInCondition( $scope, - new Instanceof_( - $expr->left->class, - new Name($rightType->getValue()) - ), + new Expr\BinaryOp\Identical($expr->left, $expr->right), $context ); } diff --git a/tests/PHPStan/Analyser/data/bug-4896.php b/tests/PHPStan/Analyser/data/bug-4896.php index d3d727e930..c8345a364e 100644 --- a/tests/PHPStan/Analyser/data/bug-4896.php +++ b/tests/PHPStan/Analyser/data/bug-4896.php @@ -1,4 +1,4 @@ -= 8.0 declare(strict_types = 1); @@ -6,16 +6,33 @@ use function PHPStan\Testing\assertType; -/** @var \DateTime|\DateInterval $command */ -$command = new \DateTime(); - -switch ($command::class) { - case \DateTime::class: - assertType(\DateTime::class, $command); - var_dump($command->getTimestamp()); - break; - case \DateInterval::class: - assertType(\DateInterval::class, $command); - echo " Hello Date Interval " . $command->format('d'); - break; +class Foo +{ + + public function doFoo(\DateTime|\DateInterval $command): void + { + switch ($command::class) { + case \DateTime::class: + assertType(\DateTime::class, $command); + break; + case \DateInterval::class: + assertType(\DateInterval::class, $command); + break; + } + + } + +} + +class Bar +{ + + public function doFoo(\DateTime|\DateInterval $command): void + { + match ($command::class) { + \DateTime::class => assertType(\DateTime::class, $command), + \DateInterval::class => assertType(\DateInterval::class, $command), + }; + } + } diff --git a/tests/PHPStan/Analyser/data/bug-5843.php b/tests/PHPStan/Analyser/data/bug-5843.php index e57a15eeef..9683f5737a 100644 --- a/tests/PHPStan/Analyser/data/bug-5843.php +++ b/tests/PHPStan/Analyser/data/bug-5843.php @@ -1,19 +1,35 @@ -= 8.0 namespace Bug5843; use function PHPStan\Testing\assertType; -function foo(object $object): void +class Foo { - switch ($object::class) { - case \DateTime::class: - assertType(\DateTime::class, $object); - $object->modify('+1 day'); - break; - case \Throwable::class: - assertType(\Throwable::class, $object); - $object->getPrevious(); - break; + + function doFoo(object $object): void + { + switch ($object::class) { + case \DateTime::class: + assertType(\DateTime::class, $object); + break; + case \Throwable::class: + assertType(\Throwable::class, $object); + break; + } + } + +} + +class Bar +{ + + function doFoo(object $object): void + { + match ($object::class) { + \DateTime::class => assertType(\DateTime::class, $object), + \Throwable::class => assertType(\Throwable::class, $object), + }; } + } From deee81034544a09fe3759393bb627a17861bc9e5 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 19 Nov 2021 14:14:13 +0100 Subject: [PATCH 0598/1284] Automatic PHP 8 stubs update merge --- .../workflows/merge-update-php-8-stubs.yml | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/workflows/merge-update-php-8-stubs.yml diff --git a/.github/workflows/merge-update-php-8-stubs.yml b/.github/workflows/merge-update-php-8-stubs.yml new file mode 100644 index 0000000000..a87348d1d0 --- /dev/null +++ b/.github/workflows/merge-update-php-8-stubs.yml @@ -0,0 +1,20 @@ +# https://help.github.com/en/categories/automating-your-workflow-with-github-actions +name: Merge PHP 8 stubs update + +on: + check_suite: + types: + - completed + +jobs: + merge: + name: Merge PHP 8 stubs update + runs-on: ubuntu-latest + steps: + - name: Merge + if: ${{ github.event.workflow_run.conclusion == 'success' }} + uses: ridedott/merge-me-action@v2 + with: + GITHUB_LOGIN: phpstan-bot + GITHUB_TOKEN: ${{ secrets.PHPSTAN_BOT_TOKEN }} + MERGE_METHOD: REBASE From a987fd24e38a5a3d09890d60c4d140a2cc3e15bd Mon Sep 17 00:00:00 2001 From: phpstan-bot Date: Fri, 19 Nov 2021 13:07:43 +0000 Subject: [PATCH 0599/1284] Update PHP 8 stubs --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index ab071ce220..8afed26c9a 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "nikic/php-parser": "4.13.1", "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "4.3.80", - "phpstan/php-8-stubs": "^0.1.23", + "phpstan/php-8-stubs": "0.1.26", "phpstan/phpdoc-parser": "^1.2.0", "react/child-process": "^0.6.4", "react/event-loop": "^1.2", diff --git a/composer.lock b/composer.lock index d32af7fda8..e2da260b2d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "669c335796ae43e31c7b06f23e01c519", + "content-hash": "9f57d02b359afa76d0fba7a1113a3c28", "packages": [ { "name": "clue/block-react", @@ -2164,16 +2164,16 @@ }, { "name": "phpstan/php-8-stubs", - "version": "0.1.23", + "version": "0.1.26", "source": { "type": "git", "url": "https://github.com/phpstan/php-8-stubs.git", - "reference": "3882ffe35d87a7eb7a133916907a44739d020184" + "reference": "b106710f125984f79dab06f5f4c45275fda10af0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/3882ffe35d87a7eb7a133916907a44739d020184", - "reference": "3882ffe35d87a7eb7a133916907a44739d020184", + "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/b106710f125984f79dab06f5f4c45275fda10af0", + "reference": "b106710f125984f79dab06f5f4c45275fda10af0", "shasum": "" }, "type": "library", @@ -2190,9 +2190,9 @@ "description": "PHP stubs extracted from php-src", "support": { "issues": "https://github.com/phpstan/php-8-stubs/issues", - "source": "https://github.com/phpstan/php-8-stubs/tree/0.1.23" + "source": "https://github.com/phpstan/php-8-stubs/tree/0.1.26" }, - "time": "2021-08-31T00:08:55+00:00" + "time": "2021-11-19T13:07:03+00:00" }, { "name": "phpstan/phpdoc-parser", From 7a59c0a34b0b474cb4fff3fa54bad93e32eeecef Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 19 Nov 2021 14:18:05 +0100 Subject: [PATCH 0600/1284] Fix --- phpstan-baseline.neon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 1a4f865252..4f901e21a9 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -130,7 +130,7 @@ parameters: path: src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php - - message: "#^Only booleans are allowed in a negated boolean, int\\|false\\|null given\\.$#" + message: "#^Only booleans are allowed in a negated boolean, int\\|false given\\.$#" count: 1 path: src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php From 83910ba5ecb487c782f7d37a3bf5cd0d7e2fa25c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 19 Nov 2021 14:22:17 +0100 Subject: [PATCH 0601/1284] Fix --- phpstan-baseline.neon | 5 ----- .../SourceLocator/OptimizedDirectorySourceLocator.php | 3 ++- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 4f901e21a9..2c51a460da 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -129,11 +129,6 @@ parameters: count: 1 path: src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php - - - message: "#^Only booleans are allowed in a negated boolean, int\\|false given\\.$#" - count: 1 - path: src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php - - message: "#^Only booleans are allowed in &&, int\\|false given on the right side\\.$#" count: 1 diff --git a/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php b/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php index d13ee52738..93300eab82 100644 --- a/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php +++ b/src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php @@ -217,7 +217,8 @@ private function findSymbols(string $file): array return ['classes' => [], 'functions' => []]; } - if (!preg_match_all(sprintf('{\b(?:class|interface|trait|function%s)\s}i', $this->extraTypes), $contents, $matches)) { + $matchResults = (bool) preg_match_all(sprintf('{\b(?:class|interface|trait|function%s)\s}i', $this->extraTypes), $contents, $matches); + if (!$matchResults) { return ['classes' => [], 'functions' => []]; } From 9adfec27a97f1b7beecf1940add2c72b8f6661ac Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 19 Nov 2021 16:11:10 +0100 Subject: [PATCH 0602/1284] Fix --- .github/workflows/merge-update-php-8-stubs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/merge-update-php-8-stubs.yml b/.github/workflows/merge-update-php-8-stubs.yml index a87348d1d0..d5dc41bd61 100644 --- a/.github/workflows/merge-update-php-8-stubs.yml +++ b/.github/workflows/merge-update-php-8-stubs.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Merge - if: ${{ github.event.workflow_run.conclusion == 'success' }} + if: ${{ github.event.check_suite.conclusion == 'success' }} uses: ridedott/merge-me-action@v2 with: GITHUB_LOGIN: phpstan-bot From 746b9b557df599edb7c96f25d0a7a5b94a39a05a Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 19 Nov 2021 16:26:11 +0100 Subject: [PATCH 0603/1284] Added DynamicReturnTypeExtension for `trigger_error` --- conf/config.neon | 5 +++ ...TriggerErrorDynamicReturnTypeExtension.php | 37 +++++++++++++++++++ .../Analyser/NodeScopeResolverTest.php | 1 + tests/PHPStan/Analyser/data/bug-5992.php | 24 ++++++++++++ 4 files changed, 67 insertions(+) create mode 100644 src/Type/Php/TriggerErrorDynamicReturnTypeExtension.php create mode 100644 tests/PHPStan/Analyser/data/bug-5992.php diff --git a/conf/config.neon b/conf/config.neon index d4d5342b62..7f494ce35b 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1273,6 +1273,11 @@ services: tags: - phpstan.broker.dynamicFunctionReturnTypeExtension + - + class: PHPStan\Type\Php\TriggerErrorDynamicReturnTypeExtension + tags: + - phpstan.broker.dynamicFunctionReturnTypeExtension + - class: PHPStan\Type\Php\VersionCompareFunctionDynamicReturnTypeExtension tags: diff --git a/src/Type/Php/TriggerErrorDynamicReturnTypeExtension.php b/src/Type/Php/TriggerErrorDynamicReturnTypeExtension.php new file mode 100644 index 0000000000..3aca7ecb2c --- /dev/null +++ b/src/Type/Php/TriggerErrorDynamicReturnTypeExtension.php @@ -0,0 +1,37 @@ +getName() === 'trigger_error'; + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + if (count($functionCall->getArgs()) < 2) { + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } + + $errorType = $scope->getType($functionCall->getArgs()[1]->value); + if ($errorType instanceof ConstantScalarType) { + if ($errorType->getValue() === E_USER_ERROR) { + return new NeverType(true); + } + } + + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } + +} diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 9c55fd8a3b..e9ffac52e5 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -529,6 +529,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5501.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4743.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5017.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5992.php'); if (PHP_VERSION_ID >= 70400) { yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5458.php'); diff --git a/tests/PHPStan/Analyser/data/bug-5992.php b/tests/PHPStan/Analyser/data/bug-5992.php new file mode 100644 index 0000000000..6c1ab9c021 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-5992.php @@ -0,0 +1,24 @@ + Date: Fri, 19 Nov 2021 19:22:23 +0100 Subject: [PATCH 0604/1284] Remove unused workflow --- .../workflows/merge-update-php-8-stubs.yml | 20 ------------------- 1 file changed, 20 deletions(-) delete mode 100644 .github/workflows/merge-update-php-8-stubs.yml diff --git a/.github/workflows/merge-update-php-8-stubs.yml b/.github/workflows/merge-update-php-8-stubs.yml deleted file mode 100644 index d5dc41bd61..0000000000 --- a/.github/workflows/merge-update-php-8-stubs.yml +++ /dev/null @@ -1,20 +0,0 @@ -# https://help.github.com/en/categories/automating-your-workflow-with-github-actions -name: Merge PHP 8 stubs update - -on: - check_suite: - types: - - completed - -jobs: - merge: - name: Merge PHP 8 stubs update - runs-on: ubuntu-latest - steps: - - name: Merge - if: ${{ github.event.check_suite.conclusion == 'success' }} - uses: ridedott/merge-me-action@v2 - with: - GITHUB_LOGIN: phpstan-bot - GITHUB_TOKEN: ${{ secrets.PHPSTAN_BOT_TOKEN }} - MERGE_METHOD: REBASE From 4aa4ad45aa15420ee313b56e5d5ce6b645106170 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 19 Nov 2021 22:34:16 +0100 Subject: [PATCH 0605/1284] New Merge bot PR workflow --- .github/workflows/merge-bot-pr.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/merge-bot-pr.yml diff --git a/.github/workflows/merge-bot-pr.yml b/.github/workflows/merge-bot-pr.yml new file mode 100644 index 0000000000..a2fd7c1c75 --- /dev/null +++ b/.github/workflows/merge-bot-pr.yml @@ -0,0 +1,28 @@ +# https://help.github.com/en/categories/automating-your-workflow-with-github-actions +# https://github.com/WyriHaximus/github-action-wait-for-status + +name: Merge bot PR +on: + pull_request: + types: + - opened +jobs: + automerge: + name: Automerge PRs + runs-on: ubuntu-latest + steps: + - name: 'Wait for status checks' + if: github.event.pull_request.user.login == 'phpstan-bot' + id: waitforstatuschecks + uses: "WyriHaximus/github-action-wait-for-status@v2" + with: + ignoreActions: Automerge PRs + checkInterval: 13 + env: + GITHUB_TOKEN: "${{ secrets.PHPSTAN_BOT_TOKEN }}" + - name: 'Merge' + uses: "pascalgn/automerge-action@v0.4.0" + if: steps.waitforstatuschecks.outputs.status == 'success' + env: + GITHUB_TOKEN: "${{ secrets.PHPSTAN_BOT_TOKEN }}" + MERGE_METHOD: "rebase" From 40302047c5451fc5893b35d82d480de24c4cd93b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 19 Nov 2021 22:49:38 +0100 Subject: [PATCH 0606/1284] Fix --- .github/workflows/merge-bot-pr.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/merge-bot-pr.yml b/.github/workflows/merge-bot-pr.yml index a2fd7c1c75..a2d4eab1f8 100644 --- a/.github/workflows/merge-bot-pr.yml +++ b/.github/workflows/merge-bot-pr.yml @@ -14,9 +14,9 @@ jobs: - name: 'Wait for status checks' if: github.event.pull_request.user.login == 'phpstan-bot' id: waitforstatuschecks - uses: "WyriHaximus/github-action-wait-for-status@v2" + uses: "WyriHaximus/github-action-wait-for-status@v1" with: - ignoreActions: Automerge PRs + ignoreActions: automerge checkInterval: 13 env: GITHUB_TOKEN: "${{ secrets.PHPSTAN_BOT_TOKEN }}" From b40eaf9fb5ba1c65a95c6eb59ec8370c2dd2225a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 20 Nov 2021 07:43:50 +0100 Subject: [PATCH 0607/1284] Fix --- .github/workflows/merge-bot-pr.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/merge-bot-pr.yml b/.github/workflows/merge-bot-pr.yml index a2d4eab1f8..cfa3aa9e7c 100644 --- a/.github/workflows/merge-bot-pr.yml +++ b/.github/workflows/merge-bot-pr.yml @@ -26,3 +26,4 @@ jobs: env: GITHUB_TOKEN: "${{ secrets.PHPSTAN_BOT_TOKEN }}" MERGE_METHOD: "rebase" + MERGE_LABELS: "" From 73065a22a1eb5ca7dbbed9e3dfc1f836bddf57e8 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Wed, 17 Nov 2021 23:07:45 +0000 Subject: [PATCH 0608/1284] The array returned by `array_count_values()` can never contain a zero. --- resources/functionMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 3920e3052a..4e3324a2ac 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -262,7 +262,7 @@ 'array_chunk' => ['array[]', 'input'=>'array', 'size'=>'int', 'preserve_keys='=>'bool'], 'array_column' => ['array', 'array'=>'array', 'column_key'=>'mixed', 'index_key='=>'mixed'], 'array_combine' => ['array|false', 'keys'=>'array', 'values'=>'array'], -'array_count_values' => ['array<0|positive-int>', 'input'=>'array'], +'array_count_values' => ['array', 'input'=>'array'], 'array_diff' => ['array', 'arr1'=>'array', 'arr2'=>'array', '...args='=>'array'], 'array_diff_assoc' => ['array', 'arr1'=>'array', 'arr2'=>'array', '...args='=>'array'], 'array_diff_key' => ['array', 'arr1'=>'array', 'arr2'=>'array', '...args='=>'array'], From eb074fdc9a70542c9d227b26a89031aeeeca7943 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 20 Nov 2021 08:22:49 +0100 Subject: [PATCH 0609/1284] Revert Fix This reverts commit b40eaf9fb5ba1c65a95c6eb59ec8370c2dd2225a. --- .github/workflows/merge-bot-pr.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/merge-bot-pr.yml b/.github/workflows/merge-bot-pr.yml index cfa3aa9e7c..a2d4eab1f8 100644 --- a/.github/workflows/merge-bot-pr.yml +++ b/.github/workflows/merge-bot-pr.yml @@ -26,4 +26,3 @@ jobs: env: GITHUB_TOKEN: "${{ secrets.PHPSTAN_BOT_TOKEN }}" MERGE_METHOD: "rebase" - MERGE_LABELS: "" From 135a9e866479e4a441bb8f8988019339d378d7c1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 20 Nov 2021 08:56:26 +0100 Subject: [PATCH 0610/1284] Different Merge PR action --- .github/workflows/merge-bot-pr.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/merge-bot-pr.yml b/.github/workflows/merge-bot-pr.yml index a2d4eab1f8..aa8c91d70f 100644 --- a/.github/workflows/merge-bot-pr.yml +++ b/.github/workflows/merge-bot-pr.yml @@ -20,9 +20,10 @@ jobs: checkInterval: 13 env: GITHUB_TOKEN: "${{ secrets.PHPSTAN_BOT_TOKEN }}" - - name: 'Merge' - uses: "pascalgn/automerge-action@v0.4.0" + - name: Merge Pull Request + uses: juliangruber/merge-pull-request-action@v1 if: steps.waitforstatuschecks.outputs.status == 'success' - env: - GITHUB_TOKEN: "${{ secrets.PHPSTAN_BOT_TOKEN }}" - MERGE_METHOD: "rebase" + with: + github-token: "${{ secrets.PHPSTAN_BOT_TOKEN }}" + number: "${{ github.event.number }}" + method: rebase From 5615d5bb3bc1f7084c54ad023272f2bae62927ea Mon Sep 17 00:00:00 2001 From: Lctrs Date: Sat, 20 Nov 2021 09:00:24 +0100 Subject: [PATCH 0611/1284] Option to allow generating empty baseline --- src/Command/AnalyseCommand.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Command/AnalyseCommand.php b/src/Command/AnalyseCommand.php index 0fab3d7261..9a987bf5ae 100644 --- a/src/Command/AnalyseCommand.php +++ b/src/Command/AnalyseCommand.php @@ -55,6 +55,7 @@ protected function configure(): void new InputOption('autoload-file', 'a', InputOption::VALUE_REQUIRED, 'Project\'s additional autoload file path'), new InputOption('error-format', null, InputOption::VALUE_REQUIRED, 'Format in which to print the result of the analysis', null), new InputOption('generate-baseline', null, InputOption::VALUE_OPTIONAL, 'Path to a file where the baseline should be saved', false), + new InputOption('allow-empty-baseline', null, InputOption::VALUE_NONE, 'Do not error out when the generated baseline is empty'), new InputOption('memory-limit', null, InputOption::VALUE_REQUIRED, 'Memory limit for analysis'), new InputOption('xdebug', null, InputOption::VALUE_NONE, 'Allow running with XDebug for debugging purposes'), new InputOption('fix', null, InputOption::VALUE_NONE, 'Launch PHPStan Pro'), @@ -102,6 +103,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int $generateBaselineFile = 'phpstan-baseline.neon'; } + $allowEmptyBaseline = (bool) $input->getOption('allow-empty-baseline'); + if ( !is_array($paths) || (!is_string($memoryLimit) && $memoryLimit !== null) @@ -132,6 +135,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int return 1; } + if ($generateBaselineFile === null && $allowEmptyBaseline) { + $inceptionResult->getStdOutput()->getStyle()->error('You must pass the --generate-baseline option alongside --allow-empty-baseline.'); + return $inceptionResult->handleReturn(1); + } + $errorOutput = $inceptionResult->getErrorOutput(); $obsoleteDockerImage = $_SERVER['PHPSTAN_OBSOLETE_DOCKER_IMAGE'] ?? 'false'; if ($obsoleteDockerImage === 'true') { @@ -239,7 +247,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } if ($generateBaselineFile !== null) { - if (!$analysisResult->hasErrors()) { + if (!$allowEmptyBaseline && !$analysisResult->hasErrors()) { $inceptionResult->getStdOutput()->getStyle()->error('No errors were found during the analysis. Baseline could not be generated.'); return $inceptionResult->handleReturn(1); From a19ca0e9923e74a65f8b02b210b8e43168adc03f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 20 Nov 2021 09:05:15 +0100 Subject: [PATCH 0612/1284] Note about --allow-empty-baseline --- src/Command/AnalyseCommand.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Command/AnalyseCommand.php b/src/Command/AnalyseCommand.php index 9a987bf5ae..d77a5280a0 100644 --- a/src/Command/AnalyseCommand.php +++ b/src/Command/AnalyseCommand.php @@ -249,6 +249,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int if ($generateBaselineFile !== null) { if (!$allowEmptyBaseline && !$analysisResult->hasErrors()) { $inceptionResult->getStdOutput()->getStyle()->error('No errors were found during the analysis. Baseline could not be generated.'); + $inceptionResult->getStdOutput()->writeLineFormatted('To allow generating empty baselines, pass --allow-empty-baseline option.'); return $inceptionResult->handleReturn(1); } From f97260015405c2f4b3aa48f63a9ec86d502fc806 Mon Sep 17 00:00:00 2001 From: phpstan-bot Date: Sat, 20 Nov 2021 07:58:26 +0000 Subject: [PATCH 0613/1284] Update PHP 8 stubs --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 8afed26c9a..df0951ae1d 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "nikic/php-parser": "4.13.1", "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "4.3.80", - "phpstan/php-8-stubs": "0.1.26", + "phpstan/php-8-stubs": "0.1.36", "phpstan/phpdoc-parser": "^1.2.0", "react/child-process": "^0.6.4", "react/event-loop": "^1.2", diff --git a/composer.lock b/composer.lock index e2da260b2d..92a2870519 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "9f57d02b359afa76d0fba7a1113a3c28", + "content-hash": "8359e3ddbbcd537e9e2fab4d22f11a24", "packages": [ { "name": "clue/block-react", @@ -2164,16 +2164,16 @@ }, { "name": "phpstan/php-8-stubs", - "version": "0.1.26", + "version": "0.1.36", "source": { "type": "git", "url": "https://github.com/phpstan/php-8-stubs.git", - "reference": "b106710f125984f79dab06f5f4c45275fda10af0" + "reference": "f42f2919fd58ddf4ba9b2ddeba4a1201144626fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/b106710f125984f79dab06f5f4c45275fda10af0", - "reference": "b106710f125984f79dab06f5f4c45275fda10af0", + "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/f42f2919fd58ddf4ba9b2ddeba4a1201144626fd", + "reference": "f42f2919fd58ddf4ba9b2ddeba4a1201144626fd", "shasum": "" }, "type": "library", @@ -2190,9 +2190,9 @@ "description": "PHP stubs extracted from php-src", "support": { "issues": "https://github.com/phpstan/php-8-stubs/issues", - "source": "https://github.com/phpstan/php-8-stubs/tree/0.1.26" + "source": "https://github.com/phpstan/php-8-stubs/tree/0.1.36" }, - "time": "2021-11-19T13:07:03+00:00" + "time": "2021-11-20T07:57:45+00:00" }, { "name": "phpstan/phpdoc-parser", From 01d41d9982aef4ea6928e072991f4d781f51cd32 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 20 Nov 2021 09:49:53 +0100 Subject: [PATCH 0614/1284] Workflow to update phpstorm-stubs --- .github/workflows/update-phpstorm-stubs.yml | 47 +++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 .github/workflows/update-phpstorm-stubs.yml diff --git a/.github/workflows/update-phpstorm-stubs.yml b/.github/workflows/update-phpstorm-stubs.yml new file mode 100644 index 0000000000..0f76d681ed --- /dev/null +++ b/.github/workflows/update-phpstorm-stubs.yml @@ -0,0 +1,47 @@ +# https://help.github.com/en/categories/automating-your-workflow-with-github-actions + +name: "Update PhpStorm stubs" +on: + workflow_dispatch: + schedule: + # * is a special character in YAML so you have to quote this string + - cron: '0 0 * * 2' + +jobs: + update-phpstorm-stubs: + name: "Update PhpStorm stubs" + runs-on: "ubuntu-latest" + steps: + - name: "Checkout" + uses: "actions/checkout@v2" + with: + ref: ${{ github.head_ref }} + fetch-depth: '0' + token: ${{ secrets.PHPSTAN_BOT_TOKEN }} + - name: "Install PHP" + uses: "shivammathur/setup-php@v2" + with: + coverage: "none" + php-version: "8.0" + - name: "Install dependencies" + run: "composer install --no-interaction --no-progress --no-suggest" + - name: "Checkout stubs" + uses: "actions/checkout@v2" + with: + path: "phpstorm-stubs" + repository: "jetbrains/phpstorm-stubs" + - name: "Update stubs" + run: "composer require jetbrains/phpstorm-stubs:dev-master#$(git -C phpstorm-stubs rev-parse HEAD)" + - name: "Remove stubs repo" + run: "rm -r phpstorm-stubs" + - name: "Create Pull Request" + id: create-pr + uses: peter-evans/create-pull-request@v3 + with: + token: ${{ secrets.PHPSTAN_BOT_TOKEN }} + branch-suffix: random + delete-branch: true + title: "Update PhpStorm stubs" + body: "Update PhpStorm stubs" + committer: "phpstan-bot " + commit-message: "Update PhpStorm stubs" From 2c924ef5ee50fb86266e8d025af7b38fe000530b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 20 Nov 2021 09:55:16 +0100 Subject: [PATCH 0615/1284] Fix --- .github/workflows/update-phpstorm-stubs.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/update-phpstorm-stubs.yml b/.github/workflows/update-phpstorm-stubs.yml index 0f76d681ed..e010e0cbb3 100644 --- a/.github/workflows/update-phpstorm-stubs.yml +++ b/.github/workflows/update-phpstorm-stubs.yml @@ -34,6 +34,8 @@ jobs: run: "composer require jetbrains/phpstorm-stubs:dev-master#$(git -C phpstorm-stubs rev-parse HEAD)" - name: "Remove stubs repo" run: "rm -r phpstorm-stubs" + - name: "Update function metadata" + run: "./bin/generate-function-metadata.php" - name: "Create Pull Request" id: create-pr uses: peter-evans/create-pull-request@v3 From c00ba355ed572a30fc7685751006847d2c2b28ea Mon Sep 17 00:00:00 2001 From: ondrejmirtes Date: Sat, 20 Nov 2021 09:06:03 +0000 Subject: [PATCH 0616/1284] Update PhpStorm stubs --- composer.json | 2 +- composer.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index df0951ae1d..314631cdd0 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,7 @@ "hoa/exception": "^1.0", "hoa/regex": "1.17.01.13", "jean85/pretty-package-versions": "^1.0.3", - "jetbrains/phpstorm-stubs": "dev-master#fdcc30312221ce08f3a4413588e2df4b77f843fc", + "jetbrains/phpstorm-stubs": "dev-master#54bed109a1fefe0b8d81d1edc39ba98bbb573a72", "nette/bootstrap": "^3.0", "nette/di": "^3.0.11", "nette/finder": "^2.5", diff --git a/composer.lock b/composer.lock index 92a2870519..47511af86d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "8359e3ddbbcd537e9e2fab4d22f11a24", + "content-hash": "e029f492264882311cc63606a4e76b6b", "packages": [ { "name": "clue/block-react", @@ -1348,12 +1348,12 @@ "source": { "type": "git", "url": "https://github.com/JetBrains/phpstorm-stubs.git", - "reference": "fdcc30312221ce08f3a4413588e2df4b77f843fc" + "reference": "54bed109a1fefe0b8d81d1edc39ba98bbb573a72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/fdcc30312221ce08f3a4413588e2df4b77f843fc", - "reference": "fdcc30312221ce08f3a4413588e2df4b77f843fc", + "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/54bed109a1fefe0b8d81d1edc39ba98bbb573a72", + "reference": "54bed109a1fefe0b8d81d1edc39ba98bbb573a72", "shasum": "" }, "require-dev": { @@ -1389,7 +1389,7 @@ "support": { "source": "https://github.com/JetBrains/phpstorm-stubs/tree/master" }, - "time": "2021-11-04T13:27:30+00:00" + "time": "2021-11-18T20:39:03+00:00" }, { "name": "nette/bootstrap", From b0b6b6778446d4007909e91539cb9399e900c556 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 20 Nov 2021 13:17:34 +0100 Subject: [PATCH 0617/1284] Test PHAR integration tests in phpstan-src --- .github/workflows/compiler-tests.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/compiler-tests.yml b/.github/workflows/compiler-tests.yml index ca0ffc885a..ec312bace6 100644 --- a/.github/workflows/compiler-tests.yml +++ b/.github/workflows/compiler-tests.yml @@ -45,5 +45,12 @@ jobs: - uses: actions/upload-artifact@v2 with: - name: phpstan.phar + name: phar-file path: tmp/phpstan.phar + + integration-tests: + if: github.event_name == 'pull_request' + needs: compiler-tests + uses: phpstan/phpstan/.github/workflows/integration-tests.yml@master + with: + ref: master From 89c16a61a4cb341eeccdf41237e063f75b847ca2 Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Sat, 20 Nov 2021 23:15:38 +0100 Subject: [PATCH 0618/1284] Add ThrowableReturnTypeExtension (#795) --- conf/config.neon | 5 +++ src/Type/Php/ThrowableReturnTypeExtension.php | 31 +++++++++++++++++++ .../Analyser/NodeScopeResolverTest.php | 1 + tests/PHPStan/Analyser/data/bug-6001.php | 11 +++++++ tests/PHPStan/Analyser/data/generics.php | 2 +- 5 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 src/Type/Php/ThrowableReturnTypeExtension.php create mode 100644 tests/PHPStan/Analyser/data/bug-6001.php diff --git a/conf/config.neon b/conf/config.neon index 7f494ce35b..0b8b0ca3d3 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -1268,6 +1268,11 @@ services: tags: - phpstan.broker.dynamicFunctionReturnTypeExtension + - + class: PHPStan\Type\Php\ThrowableReturnTypeExtension + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + - class: PHPStan\Type\Php\ParseUrlFunctionDynamicReturnTypeExtension tags: diff --git a/src/Type/Php/ThrowableReturnTypeExtension.php b/src/Type/Php/ThrowableReturnTypeExtension.php new file mode 100644 index 0000000000..3fc33738a0 --- /dev/null +++ b/src/Type/Php/ThrowableReturnTypeExtension.php @@ -0,0 +1,31 @@ +getName() === 'getCode'; + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type + { + return new BenevolentUnionType([new IntegerType(), new StringType()]); + } + +} diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index e9ffac52e5..475ed7d688 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -530,6 +530,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4743.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5017.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5992.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6001.php'); if (PHP_VERSION_ID >= 70400) { yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5458.php'); diff --git a/tests/PHPStan/Analyser/data/bug-6001.php b/tests/PHPStan/Analyser/data/bug-6001.php new file mode 100644 index 0000000000..0e950940cd --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-6001.php @@ -0,0 +1,11 @@ +getCode()); + +assertType('(int|string)', (new \RuntimeException())->getCode()); + +assertType('(int|string)', (new \PDOException())->getCode()); diff --git a/tests/PHPStan/Analyser/data/generics.php b/tests/PHPStan/Analyser/data/generics.php index 032fa71f17..f35120282e 100644 --- a/tests/PHPStan/Analyser/data/generics.php +++ b/tests/PHPStan/Analyser/data/generics.php @@ -1400,7 +1400,7 @@ public function process($class): void { } function (\Throwable $e): void { - assertType('mixed', $e->getCode()); + assertType('(int|string)', $e->getCode()); }; function (): void { From 3f105d917116f3e63d42a7005d1fa1d17d429ba8 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 21 Nov 2021 07:35:21 +0100 Subject: [PATCH 0619/1284] ReflectionClass::isEnum() --- build/baseline-8.1.neon | 9 +++++++-- composer.json | 2 +- composer.lock | 16 ++++++++-------- src/Reflection/ClassReflection.php | 11 ++++++++++- 4 files changed, 26 insertions(+), 12 deletions(-) diff --git a/build/baseline-8.1.neon b/build/baseline-8.1.neon index dd3aea97f7..fdd42f9d3d 100644 --- a/build/baseline-8.1.neon +++ b/build/baseline-8.1.neon @@ -6,12 +6,17 @@ parameters: path: ../src/Reflection/ClassConstantReflection.php - - message: "#^Call to function method_exists\\(\\) with ReflectionProperty and 'isReadOnly' will always evaluate to true\\.$#" + message: "#^Call to function method_exists\\(\\) with ReflectionClass and 'isEnum' will always evaluate to true\\.$#" count: 1 - path: ../src/Reflection/Php/PhpPropertyReflection.php + path: ../src/Reflection/ClassReflection.php - message: "#^Call to function method_exists\\(\\) with ReflectionMethod and 'getTentativeReturnT…' will always evaluate to true\\.$#" count: 1 path: ../src/Reflection/Php/NativeBuiltinMethodReflection.php + - + message: "#^Call to function method_exists\\(\\) with ReflectionProperty and 'isReadOnly' will always evaluate to true\\.$#" + count: 1 + path: ../src/Reflection/Php/PhpPropertyReflection.php + diff --git a/composer.json b/composer.json index 314631cdd0..c7552beb7f 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "nette/utils": "^3.2.5", "nikic/php-parser": "4.13.1", "ondram/ci-detector": "^3.4.0", - "ondrejmirtes/better-reflection": "4.3.80", + "ondrejmirtes/better-reflection": "4.3.81", "phpstan/php-8-stubs": "0.1.36", "phpstan/phpdoc-parser": "^1.2.0", "react/child-process": "^0.6.4", diff --git a/composer.lock b/composer.lock index 47511af86d..bfa16cbf20 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e029f492264882311cc63606a4e76b6b", + "content-hash": "726c160e140e8440885ca68bce24d3ec", "packages": [ { "name": "clue/block-react", @@ -2094,16 +2094,16 @@ }, { "name": "ondrejmirtes/better-reflection", - "version": "4.3.80", + "version": "4.3.81", "source": { "type": "git", "url": "https://github.com/ondrejmirtes/BetterReflection.git", - "reference": "56f215f08c4421f69e2d0e82096601b0b4fbaa2c" + "reference": "9c0125e45ecb730c34136bcc45eb725158d44ca6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/56f215f08c4421f69e2d0e82096601b0b4fbaa2c", - "reference": "56f215f08c4421f69e2d0e82096601b0b4fbaa2c", + "url": "https://api.github.com/repos/ondrejmirtes/BetterReflection/zipball/9c0125e45ecb730c34136bcc45eb725158d44ca6", + "reference": "9c0125e45ecb730c34136bcc45eb725158d44ca6", "shasum": "" }, "require": { @@ -2113,7 +2113,7 @@ "php": ">=7.1.0" }, "require-dev": { - "phpstan/phpstan": "^0.12.49", + "phpstan/phpstan": "^1.2.0", "phpunit/phpunit": "^7.5.18" }, "suggest": { @@ -2158,9 +2158,9 @@ ], "description": "Better Reflection - an improved code reflection API", "support": { - "source": "https://github.com/ondrejmirtes/BetterReflection/tree/4.3.80" + "source": "https://github.com/ondrejmirtes/BetterReflection/tree/4.3.81" }, - "time": "2021-11-11T09:24:16+00:00" + "time": "2021-11-21T06:43:54+00:00" }, { "name": "phpstan/php-8-stubs", diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index fc4a1bc6f6..92239f9b9a 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -521,9 +521,18 @@ public function isTrait(): bool return $this->reflection->isTrait(); } + public function isEnum(): bool + { + if (method_exists($this->reflection, 'isEnum')) { + return $this->reflection->isEnum(); + } + + return false; + } + public function isClass(): bool { - return !$this->isInterface() && !$this->isTrait(); + return !$this->isInterface() && !$this->isTrait() && !$this->isEnum(); } public function isAnonymous(): bool From 19d5638994d4e7396a87a8a9afefa9008d148027 Mon Sep 17 00:00:00 2001 From: "Eirik S. Morland" Date: Sun, 21 Nov 2021 05:31:14 +0100 Subject: [PATCH 0620/1284] Resolve deprecated tag also from parents --- src/PhpDoc/ResolvedPhpDocBlock.php | 22 ++++++++++++++- .../Annotations/DeprecatedAnnotationsTest.php | 7 +++++ .../data/annotations-deprecated.php | 28 +++++++++++++++++++ 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/PhpDoc/ResolvedPhpDocBlock.php b/src/PhpDoc/ResolvedPhpDocBlock.php index bba06a95fd..011d5c060a 100644 --- a/src/PhpDoc/ResolvedPhpDocBlock.php +++ b/src/PhpDoc/ResolvedPhpDocBlock.php @@ -3,6 +3,7 @@ namespace PHPStan\PhpDoc; use PHPStan\Analyser\NameScope; +use PHPStan\PhpDoc\Tag\DeprecatedTag; use PHPStan\PhpDoc\Tag\MixinTag; use PHPStan\PhpDoc\Tag\ParamTag; use PHPStan\PhpDoc\Tag\ReturnTag; @@ -189,7 +190,7 @@ public function merge(array $parents, array $parentPhpDocBlocks): self $result->mixinTags = $this->getMixinTags(); $result->typeAliasTags = $this->getTypeAliasTags(); $result->typeAliasImportTags = $this->getTypeAliasImportTags(); - $result->deprecatedTag = $this->getDeprecatedTag(); + $result->deprecatedTag = self::mergeDeprecatedTags($this->getDeprecatedTag(), $parents); $result->isDeprecated = $result->deprecatedTag !== null; $result->isInternal = $this->isInternal(); $result->isFinal = $this->isFinal(); @@ -633,6 +634,25 @@ private static function mergeOneParentReturnTag(?ReturnTag $returnTag, self $par return self::resolveTemplateTypeInTag($parentReturnTag->toImplicit(), $phpDocBlock); } + /** + * @param array $parents + */ + private static function mergeDeprecatedTags(?DeprecatedTag $deprecatedTag, array $parents): ?DeprecatedTag + { + if ($deprecatedTag !== null) { + return $deprecatedTag; + } + foreach ($parents as $parent) { + $result = $parent->getDeprecatedTag(); + if ($result === null) { + continue; + } + return $result; + } + + return null; + } + /** * @param array $parents */ diff --git a/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php b/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php index ea25a9d06f..ffdc198290 100644 --- a/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php +++ b/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php @@ -131,4 +131,11 @@ public function testNonDeprecatedNativeFunctions(): void $this->assertFalse($reflectionProvider->getFunction(new Name('function_exists'), null)->isDeprecated()->yes()); } + public function testDeprecatedMethodsFromInterface(): void + { + $reflectionProvider = $this->createReflectionProvider(); + $class = $reflectionProvider->getClass(\DeprecatedAnnotations\DeprecatedBar::class); + $this->assertTrue($class->getNativeMethod('superDeprecated')->isDeprecated()->yes()); + } + } diff --git a/tests/PHPStan/Reflection/Annotations/data/annotations-deprecated.php b/tests/PHPStan/Reflection/Annotations/data/annotations-deprecated.php index 0d4db04b06..1095002d92 100644 --- a/tests/PHPStan/Reflection/Annotations/data/annotations-deprecated.php +++ b/tests/PHPStan/Reflection/Annotations/data/annotations-deprecated.php @@ -187,3 +187,31 @@ public function deprecatedFoo() { } } + +/** + * @deprecated This is totally deprecated. + */ +interface BarInterface +{ + + /** + * @deprecated This is totally deprecated. + */ + public function superDeprecated(); + +} + +/** + * {@inheritdoc} + */ +class DeprecatedBar implements BarInterface +{ + + /** + * {@inheritdoc} + */ + public function superDeprecated() + { + } + +} From 090019258b691bc1fcc565967da21f4686b740de Mon Sep 17 00:00:00 2001 From: hbrecht Date: Mon, 22 Nov 2021 10:08:34 +0100 Subject: [PATCH 0621/1284] Update functionMap for XSLTProcessor as per https://www.php.net/manual/en/xsltprocessor.getparameter.php https://www.php.net/manual/en/xsltprocessor.transformtodoc.php https://www.php.net/manual/en/xsltprocessor.transformtoxml.php --- resources/functionMap.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index 4e3324a2ac..f70db6cd96 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -13210,7 +13210,7 @@ 'xslt_set_scheme_handler' => ['', 'xh'=>'', 'handlers'=>'array'], 'xslt_set_scheme_handlers' => ['', 'xh'=>'', 'handlers'=>'array'], 'xslt_setopt' => ['', 'processor'=>'', 'newmask'=>'int'], -'XSLTProcessor::getParameter' => ['string', 'namespaceuri'=>'string', 'localname'=>'string'], +'XSLTProcessor::getParameter' => ['string|false', 'namespaceuri'=>'string', 'localname'=>'string'], 'XsltProcessor::getSecurityPrefs' => ['int'], 'XSLTProcessor::hasExsltSupport' => ['bool'], 'XSLTProcessor::importStylesheet' => ['bool', 'stylesheet'=>'object'], @@ -13220,9 +13220,9 @@ 'XSLTProcessor::setParameter\'1' => ['bool', 'namespace'=>'string', 'options'=>'array'], 'XSLTProcessor::setProfiling' => ['bool', 'filename'=>'string'], 'XsltProcessor::setSecurityPrefs' => ['int', 'securityPrefs'=>'int'], -'XSLTProcessor::transformToDoc' => ['DOMDocument', 'doc'=>'DOMNode'], +'XSLTProcessor::transformToDoc' => ['DOMDocument|false', 'doc'=>'DOMNode'], 'XSLTProcessor::transformToURI' => ['int', 'doc'=>'DOMDocument', 'uri'=>'string'], -'XSLTProcessor::transformToXML' => ['string|false', 'doc'=>'DOMDocument|SimpleXMLElement'], +'XSLTProcessor::transformToXML' => ['string|false|null', 'doc'=>'DOMDocument|SimpleXMLElement'], 'Yaconf::get' => ['mixed', 'name'=>'string', 'default_value='=>'mixed'], 'Yaconf::has' => ['bool', 'name'=>'string'], 'Yaf_Action_Abstract::__construct' => ['void', 'request'=>'Yaf_Request_Abstract', 'response'=>'Yaf_Response_Abstract', 'view'=>'Yaf_View_Interface', 'invokeArgs='=>'?array'], From 21e866737583bc214ef3ebf43d0036a57ed01cb9 Mon Sep 17 00:00:00 2001 From: Brad Miller <28307684+mad-briller@users.noreply.github.com> Date: Mon, 22 Nov 2021 10:28:26 +0000 Subject: [PATCH 0622/1284] Support for @phpstan-property @phpstan-property-read and @phpstan-property-write --- src/PhpDoc/PhpDocNodeResolver.php | 56 ++++++++++--------- src/Rules/PhpDoc/InvalidPHPStanDocTagRule.php | 3 + .../Analyser/NodeScopeResolverTest.php | 2 + .../classPhpDocs-phpstanPropertyPrefix.php | 28 ++++++++++ 4 files changed, 64 insertions(+), 25 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/classPhpDocs-phpstanPropertyPrefix.php diff --git a/src/PhpDoc/PhpDocNodeResolver.php b/src/PhpDoc/PhpDocNodeResolver.php index 024f2489bc..2ac3b96556 100644 --- a/src/PhpDoc/PhpDocNodeResolver.php +++ b/src/PhpDoc/PhpDocNodeResolver.php @@ -97,37 +97,43 @@ public function resolvePropertyTags(PhpDocNode $phpDocNode, NameScope $nameScope { $resolved = []; - foreach ($phpDocNode->getPropertyTagValues() as $tagValue) { - $propertyName = substr($tagValue->propertyName, 1); - $propertyType = $this->typeNodeResolver->resolve($tagValue->type, $nameScope); - - $resolved[$propertyName] = new PropertyTag( - $propertyType, - true, - true - ); + foreach (['@property', '@phpstan-property'] as $tagName) { + foreach ($phpDocNode->getPropertyTagValues($tagName) as $tagValue) { + $propertyName = substr($tagValue->propertyName, 1); + $propertyType = $this->typeNodeResolver->resolve($tagValue->type, $nameScope); + + $resolved[$propertyName] = new PropertyTag( + $propertyType, + true, + true + ); + } } - foreach ($phpDocNode->getPropertyReadTagValues() as $tagValue) { - $propertyName = substr($tagValue->propertyName, 1); - $propertyType = $this->typeNodeResolver->resolve($tagValue->type, $nameScope); + foreach (['@property-read', '@phpstan-property-read'] as $tagName) { + foreach ($phpDocNode->getPropertyReadTagValues($tagName) as $tagValue) { + $propertyName = substr($tagValue->propertyName, 1); + $propertyType = $this->typeNodeResolver->resolve($tagValue->type, $nameScope); - $resolved[$propertyName] = new PropertyTag( - $propertyType, - true, - false - ); + $resolved[$propertyName] = new PropertyTag( + $propertyType, + true, + false + ); + } } - foreach ($phpDocNode->getPropertyWriteTagValues() as $tagValue) { - $propertyName = substr($tagValue->propertyName, 1); - $propertyType = $this->typeNodeResolver->resolve($tagValue->type, $nameScope); + foreach (['@property-write', '@phpstan-property-write'] as $tagName) { + foreach ($phpDocNode->getPropertyWriteTagValues($tagName) as $tagValue) { + $propertyName = substr($tagValue->propertyName, 1); + $propertyType = $this->typeNodeResolver->resolve($tagValue->type, $nameScope); - $resolved[$propertyName] = new PropertyTag( - $propertyType, - false, - true - ); + $resolved[$propertyName] = new PropertyTag( + $propertyType, + false, + true + ); + } } return $resolved; diff --git a/src/Rules/PhpDoc/InvalidPHPStanDocTagRule.php b/src/Rules/PhpDoc/InvalidPHPStanDocTagRule.php index f17d2df84e..24134e707a 100644 --- a/src/Rules/PhpDoc/InvalidPHPStanDocTagRule.php +++ b/src/Rules/PhpDoc/InvalidPHPStanDocTagRule.php @@ -33,6 +33,9 @@ class InvalidPHPStanDocTagRule implements \PHPStan\Rules\Rule '@phpstan-impure', '@phpstan-type', '@phpstan-import-type', + '@phpstan-property', + '@phpstan-property-read', + '@phpstan-property-write', ]; private Lexer $phpDocLexer; diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 475ed7d688..0fb0833e1c 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -564,6 +564,8 @@ public function dataFileAsserts(): iterable } yield from $this->gatherAssertTypes(__DIR__ . '/data/filesystem-functions.php'); + + yield from $this->gatherAssertTypes(__DIR__ . '/data/classPhpDocs-phpstanPropertyPrefix.php'); } /** diff --git a/tests/PHPStan/Analyser/data/classPhpDocs-phpstanPropertyPrefix.php b/tests/PHPStan/Analyser/data/classPhpDocs-phpstanPropertyPrefix.php new file mode 100644 index 0000000000..f08edc152b --- /dev/null +++ b/tests/PHPStan/Analyser/data/classPhpDocs-phpstanPropertyPrefix.php @@ -0,0 +1,28 @@ +base); + assertType('int', $this->foo); + assertType('int', $this->bar); + assertType('int', $this->baz); + } +} From b8b7eb58326e4beea7ac4ad21f95180f2a439dcb Mon Sep 17 00:00:00 2001 From: ondrejmirtes Date: Tue, 23 Nov 2021 00:02:10 +0000 Subject: [PATCH 0623/1284] Update PhpStorm stubs --- composer.json | 2 +- composer.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index c7552beb7f..31cbd33be5 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,7 @@ "hoa/exception": "^1.0", "hoa/regex": "1.17.01.13", "jean85/pretty-package-versions": "^1.0.3", - "jetbrains/phpstorm-stubs": "dev-master#54bed109a1fefe0b8d81d1edc39ba98bbb573a72", + "jetbrains/phpstorm-stubs": "dev-master#08a6c2191819c501f5ad5ef8c7fd9705316d7110", "nette/bootstrap": "^3.0", "nette/di": "^3.0.11", "nette/finder": "^2.5", diff --git a/composer.lock b/composer.lock index bfa16cbf20..b406705709 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "726c160e140e8440885ca68bce24d3ec", + "content-hash": "59111ae499b0cf02094db5a97eec37e3", "packages": [ { "name": "clue/block-react", @@ -1348,12 +1348,12 @@ "source": { "type": "git", "url": "https://github.com/JetBrains/phpstorm-stubs.git", - "reference": "54bed109a1fefe0b8d81d1edc39ba98bbb573a72" + "reference": "08a6c2191819c501f5ad5ef8c7fd9705316d7110" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/54bed109a1fefe0b8d81d1edc39ba98bbb573a72", - "reference": "54bed109a1fefe0b8d81d1edc39ba98bbb573a72", + "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/08a6c2191819c501f5ad5ef8c7fd9705316d7110", + "reference": "08a6c2191819c501f5ad5ef8c7fd9705316d7110", "shasum": "" }, "require-dev": { @@ -1389,7 +1389,7 @@ "support": { "source": "https://github.com/JetBrains/phpstorm-stubs/tree/master" }, - "time": "2021-11-18T20:39:03+00:00" + "time": "2021-11-22T17:13:27+00:00" }, { "name": "nette/bootstrap", From 7f337a4e1c98aef5c1c2956f65bc0daefa538fab Mon Sep 17 00:00:00 2001 From: hbrecht Date: Tue, 23 Nov 2021 07:47:29 +0100 Subject: [PATCH 0624/1284] imap_delete / imap_undelete: correction imap_delete and imap_undelete both have parameter $message_num of type string. All other imap_* - functions have $message_num as int and $message_nums as string. For imap_delete and imap_undelete, it really should be string $message_nums. I also opened a bug at php for this. php doc has correct type, but comment is wrong and parameter names should be changed to be consistent for all imap_*-functions. https://bugs.php.net/bug.php?id=81649 --- resources/functionMap.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index f70db6cd96..b8f7effddf 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -5208,7 +5208,7 @@ 'imap_close' => ['bool', 'stream_id'=>'resource', 'options='=>'int'], 'imap_create' => ['bool', 'stream_id'=>'resource', 'mailbox'=>'string'], 'imap_createmailbox' => ['bool', 'stream_id'=>'resource', 'mailbox'=>'string'], -'imap_delete' => ['bool', 'stream_id'=>'resource', 'msg_no'=>'int', 'options='=>'int'], +'imap_delete' => ['bool', 'stream_id'=>'resource', 'msg_no'=>'string', 'options='=>'int'], 'imap_deletemailbox' => ['bool', 'stream_id'=>'resource', 'mailbox'=>'string'], 'imap_errors' => ['array|false'], 'imap_expunge' => ['bool', 'stream_id'=>'resource'], @@ -5265,7 +5265,7 @@ 'imap_thread' => ['array|false', 'stream_id'=>'resource', 'options='=>'int'], 'imap_timeout' => ['mixed', 'timeout_type'=>'int', 'timeout='=>'int'], 'imap_uid' => ['int|false', 'stream_id'=>'resource', 'msg_no'=>'int'], -'imap_undelete' => ['bool', 'stream_id'=>'resource', 'msg_no'=>'int', 'flags='=>'int'], +'imap_undelete' => ['bool', 'stream_id'=>'resource', 'msg_no'=>'string', 'flags='=>'int'], 'imap_unsubscribe' => ['bool', 'stream_id'=>'resource', 'mailbox'=>'string'], 'imap_utf7_decode' => ['string|false', 'buf'=>'string'], 'imap_utf7_encode' => ['string', 'buf'=>'string'], From b6c61bfcfccbb9b5ff0f973462a51912651124df Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 25 Nov 2021 17:23:23 +0100 Subject: [PATCH 0625/1284] Update Nette dependencies --- composer.lock | 54 ++++++++++++------------ src/DependencyInjection/Configurator.php | 6 +-- src/DependencyInjection/NeonAdapter.php | 2 +- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/composer.lock b/composer.lock index b406705709..2edf6cce82 100644 --- a/composer.lock +++ b/composer.lock @@ -1393,29 +1393,29 @@ }, { "name": "nette/bootstrap", - "version": "v3.0.2", + "version": "v3.1.2", "source": { "type": "git", "url": "https://github.com/nette/bootstrap.git", - "reference": "67830a65b42abfb906f8e371512d336ebfb5da93" + "reference": "3ab4912a08af0c16d541c3709935c3478b5ee090" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/bootstrap/zipball/67830a65b42abfb906f8e371512d336ebfb5da93", - "reference": "67830a65b42abfb906f8e371512d336ebfb5da93", + "url": "https://api.github.com/repos/nette/bootstrap/zipball/3ab4912a08af0c16d541c3709935c3478b5ee090", + "reference": "3ab4912a08af0c16d541c3709935c3478b5ee090", "shasum": "" }, "require": { - "nette/di": "^3.0", - "nette/utils": "^3.0", - "php": ">=7.1" + "nette/di": "^3.0.5", + "nette/utils": "^3.2.1", + "php": ">=7.2 <8.2" }, "conflict": { "tracy/tracy": "<2.6" }, "require-dev": { - "latte/latte": "^2.2", - "nette/application": "^3.0", + "latte/latte": "^2.8", + "nette/application": "^3.1", "nette/caching": "^3.0", "nette/database": "^3.0", "nette/forms": "^3.0", @@ -1435,7 +1435,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "3.1-dev" } }, "autoload": { @@ -1459,7 +1459,7 @@ "homepage": "https://nette.org/contributors" } ], - "description": "🅱 Nette Bootstrap: the simple way to configure and bootstrap your Nette application.", + "description": "🅱 Nette Bootstrap: the simple way to configure and bootstrap your Nette application.", "homepage": "https://nette.org", "keywords": [ "bootstrapping", @@ -1468,9 +1468,9 @@ ], "support": { "issues": "https://github.com/nette/bootstrap/issues", - "source": "https://github.com/nette/bootstrap/tree/master" + "source": "https://github.com/nette/bootstrap/tree/v3.1.2" }, - "time": "2020-05-26T08:46:23+00:00" + "time": "2021-11-24T16:51:46+00:00" }, { "name": "nette/di", @@ -1616,16 +1616,16 @@ }, { "name": "nette/neon", - "version": "v3.3.1", + "version": "v3.3.2", "source": { "type": "git", "url": "https://github.com/nette/neon.git", - "reference": "1f4e5f6a30bf45b6c2c932be7396ea70692ee607" + "reference": "54b287d8c2cdbe577b02e28ca1713e275b05ece2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/neon/zipball/1f4e5f6a30bf45b6c2c932be7396ea70692ee607", - "reference": "1f4e5f6a30bf45b6c2c932be7396ea70692ee607", + "url": "https://api.github.com/repos/nette/neon/zipball/54b287d8c2cdbe577b02e28ca1713e275b05ece2", + "reference": "54b287d8c2cdbe577b02e28ca1713e275b05ece2", "shasum": "" }, "require": { @@ -1635,7 +1635,7 @@ "require-dev": { "nette/tester": "^2.0", "phpstan/phpstan": "^0.12", - "tracy/tracy": "^2.8.8" + "tracy/tracy": "^2.7" }, "bin": [ "bin/neon-lint" @@ -1678,9 +1678,9 @@ ], "support": { "issues": "https://github.com/nette/neon/issues", - "source": "https://github.com/nette/neon/tree/v3.3.1" + "source": "https://github.com/nette/neon/tree/v3.3.2" }, - "time": "2021-11-09T01:00:15+00:00" + "time": "2021-11-25T15:57:41+00:00" }, { "name": "nette/php-generator", @@ -1881,16 +1881,16 @@ }, { "name": "nette/utils", - "version": "v3.2.5", + "version": "v3.2.6", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "9cd80396ca58d7969ab44fc7afcf03624dfa526e" + "reference": "2f261e55bd6a12057442045bf2c249806abc1d02" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/9cd80396ca58d7969ab44fc7afcf03624dfa526e", - "reference": "9cd80396ca58d7969ab44fc7afcf03624dfa526e", + "url": "https://api.github.com/repos/nette/utils/zipball/2f261e55bd6a12057442045bf2c249806abc1d02", + "reference": "2f261e55bd6a12057442045bf2c249806abc1d02", "shasum": "" }, "require": { @@ -1901,7 +1901,7 @@ }, "require-dev": { "nette/tester": "~2.0", - "phpstan/phpstan": "^0.12", + "phpstan/phpstan": "^1.0", "tracy/tracy": "^2.3" }, "suggest": { @@ -1960,9 +1960,9 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v3.2.5" + "source": "https://github.com/nette/utils/tree/v3.2.6" }, - "time": "2021-09-20T10:50:11+00:00" + "time": "2021-11-24T15:47:23+00:00" }, { "name": "nikic/php-parser", diff --git a/src/DependencyInjection/Configurator.php b/src/DependencyInjection/Configurator.php index 1da912305f..9f3fd8d7f0 100644 --- a/src/DependencyInjection/Configurator.php +++ b/src/DependencyInjection/Configurator.php @@ -5,7 +5,7 @@ use Nette\DI\Config\Loader; use Nette\DI\ContainerLoader; -class Configurator extends \Nette\Configurator +class Configurator extends \Nette\Bootstrap\Configurator { private LoaderFactory $loaderFactory; @@ -39,12 +39,12 @@ public function loadContainer(): string { $loader = new ContainerLoader( $this->getContainerCacheDirectory(), - $this->parameters['debugMode'] + $this->staticParameters['debugMode'] ); return $loader->load( [$this, 'generateContainer'], - [$this->parameters, array_keys($this->dynamicParameters), $this->configs, PHP_VERSION_ID - PHP_RELEASE_VERSION, NeonAdapter::CACHE_KEY] + [$this->staticParameters, array_keys($this->dynamicParameters), $this->configs, PHP_VERSION_ID - PHP_RELEASE_VERSION, NeonAdapter::CACHE_KEY] ); } diff --git a/src/DependencyInjection/NeonAdapter.php b/src/DependencyInjection/NeonAdapter.php index ea41b22669..7c63867e44 100644 --- a/src/DependencyInjection/NeonAdapter.php +++ b/src/DependencyInjection/NeonAdapter.php @@ -14,7 +14,7 @@ class NeonAdapter implements Adapter { - public const CACHE_KEY = 'v13-remove-deprecated'; + public const CACHE_KEY = 'v14-update-nette'; private const PREVENT_MERGING_SUFFIX = '!'; From a298105f2a061ccc37f57a43361fd18615db2380 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 25 Nov 2021 17:28:11 +0100 Subject: [PATCH 0626/1284] Regenerate baseline --- phpstan-baseline.neon | 54 +++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 30 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 2c51a460da..2978444d5c 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -91,11 +91,10 @@ parameters: path: src/PhpDoc/ResolvedPhpDocBlock.php - - message: - """ - #^Call to deprecated method getInstance\\(\\) of class PHPStan\\\\Broker\\\\Broker\\: - Use PHPStan\\\\Reflection\\\\ReflectionProviderStaticAccessor instead$# - """ + message: """ + #^Call to deprecated method getInstance\\(\\) of class PHPStan\\\\Broker\\\\Broker\\: + Use PHPStan\\\\Reflection\\\\ReflectionProviderStaticAccessor instead$# + """ count: 1 path: src/PhpDoc/StubValidator.php @@ -260,20 +259,18 @@ parameters: path: src/Type/IntersectionType.php - - message: - """ - #^Call to deprecated method getInstance\\(\\) of class PHPStan\\\\Broker\\\\Broker\\: - Use PHPStan\\\\Reflection\\\\ReflectionProviderStaticAccessor instead$# - """ + message: """ + #^Call to deprecated method getInstance\\(\\) of class PHPStan\\\\Broker\\\\Broker\\: + Use PHPStan\\\\Reflection\\\\ReflectionProviderStaticAccessor instead$# + """ count: 1 path: src/Type/ObjectType.php - - message: - """ - #^Call to deprecated method getUniversalObjectCratesClasses\\(\\) of class PHPStan\\\\Broker\\\\Broker\\: - Inject %%universalObjectCratesClasses%% parameter instead\\.$# - """ + message: """ + #^Call to deprecated method getUniversalObjectCratesClasses\\(\\) of class PHPStan\\\\Broker\\\\Broker\\: + Inject %%universalObjectCratesClasses%% parameter instead\\.$# + """ count: 1 path: src/Type/ObjectType.php @@ -313,29 +310,26 @@ parameters: path: tests/PHPStan/Analyser/EvaluationOrderTest.php - - message: - """ - #^Call to deprecated method getClass\\(\\) of class PHPStan\\\\Broker\\\\Broker\\: - Use PHPStan\\\\Reflection\\\\ReflectionProvider instead$# - """ + message: """ + #^Call to deprecated method getClass\\(\\) of class PHPStan\\\\Broker\\\\Broker\\: + Use PHPStan\\\\Reflection\\\\ReflectionProvider instead$# + """ count: 1 path: tests/PHPStan/Broker/BrokerTest.php - - message: - """ - #^Call to deprecated method getFunction\\(\\) of class PHPStan\\\\Broker\\\\Broker\\: - Use PHPStan\\\\Reflection\\\\ReflectionProvider instead$# - """ + message: """ + #^Call to deprecated method getFunction\\(\\) of class PHPStan\\\\Broker\\\\Broker\\: + Use PHPStan\\\\Reflection\\\\ReflectionProvider instead$# + """ count: 1 path: tests/PHPStan/Broker/BrokerTest.php - - message: - """ - #^Call to deprecated method hasClass\\(\\) of class PHPStan\\\\Broker\\\\Broker\\: - Use PHPStan\\\\Reflection\\\\ReflectionProvider instead$# - """ + message: """ + #^Call to deprecated method hasClass\\(\\) of class PHPStan\\\\Broker\\\\Broker\\: + Use PHPStan\\\\Reflection\\\\ReflectionProvider instead$# + """ count: 1 path: tests/PHPStan/Broker/BrokerTest.php From 5a6c1c53dbe6b8a970312b4f8aa32905f77f2963 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 25 Nov 2021 17:30:13 +0100 Subject: [PATCH 0627/1284] Open 1.3-dev --- composer.json | 2 +- composer.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 31cbd33be5..081c3fa283 100644 --- a/composer.json +++ b/composer.json @@ -60,7 +60,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.2-dev" + "dev-master": "1.3-dev" }, "patcher": { "search": "patches" diff --git a/composer.lock b/composer.lock index 2edf6cce82..8672146983 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "59111ae499b0cf02094db5a97eec37e3", + "content-hash": "748b290b5e4f91d469ac1ce71a3bbb1f", "packages": [ { "name": "clue/block-react", From a30769fdc19d86a13a3b4e8ebffc45d0603b9d17 Mon Sep 17 00:00:00 2001 From: phpstan-bot Date: Fri, 26 Nov 2021 00:09:42 +0000 Subject: [PATCH 0628/1284] Update PHP 8 stubs --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 081c3fa283..c9fb3cfe4c 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "nikic/php-parser": "4.13.1", "ondram/ci-detector": "^3.4.0", "ondrejmirtes/better-reflection": "4.3.81", - "phpstan/php-8-stubs": "0.1.36", + "phpstan/php-8-stubs": "0.1.37", "phpstan/phpdoc-parser": "^1.2.0", "react/child-process": "^0.6.4", "react/event-loop": "^1.2", diff --git a/composer.lock b/composer.lock index 8672146983..db3d81e816 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "748b290b5e4f91d469ac1ce71a3bbb1f", + "content-hash": "0b9556c56b7adbc07f5fcff10ab14ae3", "packages": [ { "name": "clue/block-react", @@ -2164,16 +2164,16 @@ }, { "name": "phpstan/php-8-stubs", - "version": "0.1.36", + "version": "0.1.37", "source": { "type": "git", "url": "https://github.com/phpstan/php-8-stubs.git", - "reference": "f42f2919fd58ddf4ba9b2ddeba4a1201144626fd" + "reference": "275e4fff5a66c9f39872c4dd43cfed75f27a31a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/f42f2919fd58ddf4ba9b2ddeba4a1201144626fd", - "reference": "f42f2919fd58ddf4ba9b2ddeba4a1201144626fd", + "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/275e4fff5a66c9f39872c4dd43cfed75f27a31a2", + "reference": "275e4fff5a66c9f39872c4dd43cfed75f27a31a2", "shasum": "" }, "type": "library", @@ -2190,9 +2190,9 @@ "description": "PHP stubs extracted from php-src", "support": { "issues": "https://github.com/phpstan/php-8-stubs/issues", - "source": "https://github.com/phpstan/php-8-stubs/tree/0.1.36" + "source": "https://github.com/phpstan/php-8-stubs/tree/0.1.37" }, - "time": "2021-11-20T07:57:45+00:00" + "time": "2021-11-26T00:08:47+00:00" }, { "name": "phpstan/phpdoc-parser", From 1056f69741277825491a7c3c269819418472f3ab Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Fri, 26 Nov 2021 22:53:10 +0100 Subject: [PATCH 0629/1284] Add support for the `key-of<...>` and `value-of<...>` types * Add support for the `key-of<...>` type * Add support for the `value-of` type and add bsaic tests * Add more tests --- src/PhpDoc/TypeNodeResolver.php | 12 +++++ .../Analyser/NodeScopeResolverTest.php | 2 + tests/PHPStan/Analyser/data/key-of.php | 41 +++++++++++++++ tests/PHPStan/Analyser/data/value-of.php | 38 ++++++++++++++ .../Rules/Methods/CallMethodsRuleTest.php | 16 ++++++ .../Rules/Methods/data/call-methods.php | 50 +++++++++++++++++++ 6 files changed, 159 insertions(+) create mode 100644 tests/PHPStan/Analyser/data/key-of.php create mode 100644 tests/PHPStan/Analyser/data/value-of.php diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index 39ac549568..3b0fec753d 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -505,6 +505,18 @@ private function resolveGenericTypeNode(GenericTypeNode $typeNode, NameScope $na return IntegerRangeType::fromInterval($min, $max); } + } elseif ($mainTypeName === 'key-of') { + if (count($genericTypes) === 1) { // key-of + return $genericTypes[0]->getIterableKeyType(); + } + + return new ErrorType(); + } elseif ($mainTypeName === 'value-of') { + if (count($genericTypes) === 1) { // value-of + return $genericTypes[0]->getIterableValueType(); + } + + return new ErrorType(); } $mainType = $this->resolveIdentifierTypeNode($typeNode->type, $nameScope); diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 0fb0833e1c..ffcc6d973d 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -55,6 +55,8 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/native-types.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/type-change-after-array-access-assignment.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/iterator_to_array.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/key-of.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/value-of.php'); if (self::$useStaticReflectionProvider || extension_loaded('ds')) { yield from $this->gatherAssertTypes(__DIR__ . '/data/ext-ds.php'); diff --git a/tests/PHPStan/Analyser/data/key-of.php b/tests/PHPStan/Analyser/data/key-of.php new file mode 100644 index 0000000000..f52eb2effd --- /dev/null +++ b/tests/PHPStan/Analyser/data/key-of.php @@ -0,0 +1,41 @@ + 'John F. Kennedy Airport', + self::LGA => 'La Guardia Airport', + ]; + + /** + * @param key-of $code + */ + public static function foo(string $code): void + { + assertType('\'jfk\'|\'lga\'', $code); + } + + /** + * @param key-of<'jfk'> $code + */ + public static function bar(string $code): void + { + assertType('string', $code); + } + + /** + * @param key-of<'jfk'|'lga'> $code + */ + public static function baz(string $code): void + { + assertType('string', $code); + } +} diff --git a/tests/PHPStan/Analyser/data/value-of.php b/tests/PHPStan/Analyser/data/value-of.php new file mode 100644 index 0000000000..c05fda0fda --- /dev/null +++ b/tests/PHPStan/Analyser/data/value-of.php @@ -0,0 +1,38 @@ + $code + */ + public static function foo(string $code): void + { + assertType('\'jfk\'|\'lga\'', $code); + } + + /** + * @param value-of<'jfk'> $code + */ + public static function bar(string $code): void + { + assertType('string', $code); + } + + /** + * @param value-of<'jfk'|'lga'> $code + */ + public static function baz(string $code): void + { + assertType('string', $code); + } +} diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 4e57f68b1e..63478a75c3 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -500,6 +500,14 @@ public function testCallMethods(): void 1751, 'See: https://phpstan.org/blog/solving-phpstan-error-unable-to-resolve-template-type', ], + [ + 'Parameter #1 $code of method Test\\KeyOfParam::foo() expects \'jfk\'|\'lga\', \'sfo\' given.', + 1777, + ], + [ + 'Parameter #1 $code of method Test\\ValueOfParam::foo() expects \'John F. Kennedy…\'|\'La Guardia Airport\', \'Newark Liberty…\' given.', + 1802, + ], ]); } @@ -787,6 +795,14 @@ public function testCallMethodsOnThisOnly(): void 1751, 'See: https://phpstan.org/blog/solving-phpstan-error-unable-to-resolve-template-type', ], + [ + 'Parameter #1 $code of method Test\\KeyOfParam::foo() expects \'jfk\'|\'lga\', \'sfo\' given.', + 1777, + ], + [ + 'Parameter #1 $code of method Test\\ValueOfParam::foo() expects \'John F. Kennedy…\'|\'La Guardia Airport\', \'Newark Liberty…\' given.', + 1802, + ], ]); } diff --git a/tests/PHPStan/Rules/Methods/data/call-methods.php b/tests/PHPStan/Rules/Methods/data/call-methods.php index f8838371a5..081b913ea1 100644 --- a/tests/PHPStan/Rules/Methods/data/call-methods.php +++ b/tests/PHPStan/Rules/Methods/data/call-methods.php @@ -1752,3 +1752,53 @@ public function doBar() } } + +class KeyOfParam +{ + public const JFK = 'jfk'; + public const LGA = 'lga'; + + private const ALL = [ + self::JFK => 'John F. Kennedy Airport', + self::LGA => 'La Guardia Airport', + ]; + + /** + * @param key-of $code + */ + public function foo(string $code): void + { + } + + public function test(): void + { + $this->foo(KeyOfParam::JFK); + $this->foo('jfk'); + $this->foo('sfo'); + } +} + +class ValueOfParam +{ + public const JFK = 'jfk'; + public const LGA = 'lga'; + + public const ALL = [ + self::JFK => 'John F. Kennedy Airport', + self::LGA => 'La Guardia Airport', + ]; + + /** + * @param value-of $code + */ + public function foo(string $code): void + { + } + + public function test(): void + { + $this->foo(ValueOfParam::ALL[ValueOfParam::JFK]); + $this->foo('John F. Kennedy Airport'); + $this->foo('Newark Liberty International'); + } +} From 90883d596d58c045661e9d8e13c0895b1982f6fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Sat, 27 Nov 2021 15:41:18 +0100 Subject: [PATCH 0630/1284] Fixed ErrorFormatter tests in terminals with console support --- src/Testing/ErrorFormatterTestCase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Testing/ErrorFormatterTestCase.php b/src/Testing/ErrorFormatterTestCase.php index 8a3dbd8214..4b74974aa2 100644 --- a/src/Testing/ErrorFormatterTestCase.php +++ b/src/Testing/ErrorFormatterTestCase.php @@ -27,7 +27,7 @@ private function getOutputStream(): StreamOutput if ($resource === false) { throw new \PHPStan\ShouldNotHappenException(); } - $this->outputStream = new StreamOutput($resource); + $this->outputStream = new StreamOutput($resource, StreamOutput::VERBOSITY_NORMAL, false); } return $this->outputStream; From 29e86266bee18ef8e5d04c92f1862bb484015b2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Sat, 27 Nov 2021 16:01:55 +0100 Subject: [PATCH 0631/1284] No need to skip these tests anymore --- tests/PHPStan/Command/AnalyseApplicationIntegrationTest.php | 3 --- tests/PHPStan/Command/AnalyseCommandTest.php | 3 --- 2 files changed, 6 deletions(-) diff --git a/tests/PHPStan/Command/AnalyseApplicationIntegrationTest.php b/tests/PHPStan/Command/AnalyseApplicationIntegrationTest.php index 96a2cfe99f..3aff69511a 100644 --- a/tests/PHPStan/Command/AnalyseApplicationIntegrationTest.php +++ b/tests/PHPStan/Command/AnalyseApplicationIntegrationTest.php @@ -39,9 +39,6 @@ public function testExecuteOnAFileWithErrors(): void private function runPath(string $path, int $expectedStatusCode): string { - if (PHP_VERSION_ID >= 80000 && DIRECTORY_SEPARATOR === '\\') { - $this->markTestSkipped('Skipped because of https://github.com/symfony/symfony/issues/37508'); - } self::getContainer()->getByType(ResultCacheClearer::class)->clear(); $analyserApplication = self::getContainer()->getByType(AnalyseApplication::class); $resource = fopen('php://memory', 'w', false); diff --git a/tests/PHPStan/Command/AnalyseCommandTest.php b/tests/PHPStan/Command/AnalyseCommandTest.php index 5fddff391f..a6b59e47f1 100644 --- a/tests/PHPStan/Command/AnalyseCommandTest.php +++ b/tests/PHPStan/Command/AnalyseCommandTest.php @@ -80,9 +80,6 @@ public static function autoDiscoveryPathsProvider(): array */ private function runCommand(int $expectedStatusCode, array $parameters = []): string { - if (PHP_VERSION_ID >= 80000 && DIRECTORY_SEPARATOR === '\\') { - $this->markTestSkipped('Skipped because of https://github.com/symfony/symfony/issues/37508'); - } $commandTester = new CommandTester(new AnalyseCommand([])); $commandTester->execute([ From 447cd102d946a2d9883ac82793195275f54296a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Sat, 27 Nov 2021 16:46:40 +0100 Subject: [PATCH 0632/1284] Added tests that ClassAttributesRule supports enums as well --- .../Rules/Classes/ClassAttributesRuleTest.php | 14 ++++++++++ .../Rules/Classes/data/enum-attributes.php | 27 +++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 tests/PHPStan/Rules/Classes/data/enum-attributes.php diff --git a/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php b/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php index ff1428002e..340897c51d 100644 --- a/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php @@ -97,4 +97,18 @@ public function testRule(): void ]); } + public function testRuleForEnums(): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + + $this->analyse([__DIR__ . '/data/enum-attributes.php'], [ + [ + 'Attribute class EnumAttributes\AttributeWithPropertyTarget does not have the class target.', + 23, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Classes/data/enum-attributes.php b/tests/PHPStan/Rules/Classes/data/enum-attributes.php new file mode 100644 index 0000000000..01d7e84238 --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/enum-attributes.php @@ -0,0 +1,27 @@ += 8.1 + +namespace EnumAttributes; + +#[\Attribute] +class AttributeWithoutSpecificTarget +{ + +} + +#[\Attribute(\Attribute::TARGET_PROPERTY)] +class AttributeWithPropertyTarget +{ + +} + +#[AttributeWithoutSpecificTarget] +enum EnumWithValidClassAttribute +{ + +} + +#[AttributeWithPropertyTarget] +enum EnumWithInvalidClassAttribute +{ + +} From 79e0a792a14a088af37e386fa93746d7e8b6c687 Mon Sep 17 00:00:00 2001 From: Stefano Arlandini Date: Sat, 27 Nov 2021 19:40:02 +0100 Subject: [PATCH 0633/1284] Fix the signature of the value returned from the `date_parse` and `date_parse_from_format` functions --- resources/functionMap.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/functionMap.php b/resources/functionMap.php index b8f7effddf..c082585ecc 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -1548,8 +1548,8 @@ 'date_isodate_set' => ['DateTime|false', 'object'=>'DateTime', 'year'=>'int', 'week'=>'int', 'day='=>'int|mixed'], 'date_modify' => ['DateTime|false', 'object'=>'DateTime', 'modify'=>'string'], 'date_offset_get' => ['int', 'obj'=>'DateTimeInterface'], -'date_parse' => ['array|false', 'date'=>'string'], -'date_parse_from_format' => ['array', 'format'=>'string', 'date'=>'string'], +'date_parse' => ['array{year: int|false, month: int|false, day: int|false, hour: int|false, minute: int|false, second: int|false, fraction: float|false, warning_count: int, warnings: string[], error_count: int, errors: string[], is_localtime: bool, zone_type?: int|bool, zone?: int|bool, is_dst?: bool, tz_abbr?: string, tz_id?: string, relative?: array{year: int, month: int, day: int, hour: int, minute: int, second: int, weekday?: int, weekdays?: int, first_day_of_month?: bool, last_day_of_month?: bool}}', 'date'=>'string'], +'date_parse_from_format' => ['array{year: int|false, month: int|false, day: int|false, hour: int|false, minute: int|false, second: int|false, fraction: float|false, warning_count: int, warnings: string[], error_count: int, errors: string[], is_localtime: bool, zone_type?: int|bool, zone?: int|bool, is_dst?: bool, tz_abbr?: string, tz_id?: string, relative?: array{year: int, month: int, day: int, hour: int, minute: int, second: int, weekday?: int, weekdays?: int, first_day_of_month?: bool, last_day_of_month?: bool}}', 'format'=>'string', 'date'=>'string'], 'date_sub' => ['DateTime|false', 'object'=>'DateTime', 'interval'=>'DateInterval'], 'date_sun_info' => ['array', 'time'=>'int', 'latitude'=>'float', 'longitude'=>'float'], 'date_sunrise' => ['mixed', 'time'=>'int', 'format='=>'int', 'latitude='=>'float', 'longitude='=>'float', 'zenith='=>'float', 'gmt_offset='=>'float'], From a345704f8ddf83f905e44f227266ca1db2c591e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Tue, 30 Nov 2021 20:34:20 +0100 Subject: [PATCH 0634/1284] Added EnumCaseAttributesRule --- .../EnumCases/EnumCaseAttributesRule.php | 38 +++++++++++++ .../EnumCases/EnumCaseAttributesRuleTest.php | 56 +++++++++++++++++++ .../EnumCases/data/enum-case-attributes.php | 45 +++++++++++++++ 3 files changed, 139 insertions(+) create mode 100644 src/Rules/EnumCases/EnumCaseAttributesRule.php create mode 100644 tests/PHPStan/Rules/EnumCases/EnumCaseAttributesRuleTest.php create mode 100644 tests/PHPStan/Rules/EnumCases/data/enum-case-attributes.php diff --git a/src/Rules/EnumCases/EnumCaseAttributesRule.php b/src/Rules/EnumCases/EnumCaseAttributesRule.php new file mode 100644 index 0000000000..a09d59b39a --- /dev/null +++ b/src/Rules/EnumCases/EnumCaseAttributesRule.php @@ -0,0 +1,38 @@ + + */ +class EnumCaseAttributesRule implements Rule +{ + + private AttributesCheck $attributesCheck; + + public function __construct(AttributesCheck $attributesCheck) + { + $this->attributesCheck = $attributesCheck; + } + + public function getNodeType(): string + { + return Node\Stmt\EnumCase::class; + } + + public function processNode(Node $node, Scope $scope): array + { + return $this->attributesCheck->check( + $scope, + $node->attrGroups, + \Attribute::TARGET_CLASS_CONSTANT, + 'class constant' + ); + } + +} diff --git a/tests/PHPStan/Rules/EnumCases/EnumCaseAttributesRuleTest.php b/tests/PHPStan/Rules/EnumCases/EnumCaseAttributesRuleTest.php new file mode 100644 index 0000000000..ba5eb5d182 --- /dev/null +++ b/tests/PHPStan/Rules/EnumCases/EnumCaseAttributesRuleTest.php @@ -0,0 +1,56 @@ + + */ +class EnumCaseAttributesRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + $reflectionProvider = $this->createReflectionProvider(); + return new EnumCaseAttributesRule( + new AttributesCheck( + $reflectionProvider, + new FunctionCallParametersCheck( + new RuleLevelHelper($reflectionProvider, true, false, true, false), + new NullsafeCheck(), + new PhpVersion(80100), + new UnresolvableTypeHelper(), + true, + true, + true, + true + ), + new ClassCaseSensitivityCheck($reflectionProvider, false) + ) + ); + } + + public function testRule(): void + { + if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Test requires PHP 8.1.'); + } + + $this->analyse([__DIR__ . '/data/enum-case-attributes.php'], [ + [ + 'Attribute class EnumCaseAttributes\AttributeWithPropertyTarget does not have the class constant target.', + 26, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/EnumCases/data/enum-case-attributes.php b/tests/PHPStan/Rules/EnumCases/data/enum-case-attributes.php new file mode 100644 index 0000000000..74c4f6c4ff --- /dev/null +++ b/tests/PHPStan/Rules/EnumCases/data/enum-case-attributes.php @@ -0,0 +1,45 @@ += 8.1 + +namespace EnumCaseAttributes; + +#[\Attribute(\Attribute::TARGET_PROPERTY)] +class AttributeWithPropertyTarget +{ + +} + +#[\Attribute(\Attribute::TARGET_CLASS_CONSTANT)] +class AttributeWithClassConstantTarget +{ + +} + +#[\Attribute(\Attribute::TARGET_ALL)] +class AttributeWithTargetAll +{ + +} + +enum Lorem +{ + + #[AttributeWithPropertyTarget] + case FOO; + +} + +enum Ipsum +{ + + #[AttributeWithClassConstantTarget] + case FOO; + +} + +enum Dolor +{ + + #[AttributeWithTargetAll] + case FOO; + +} From 9e31bbfa4dd2ef2a7eef29bc31f1c060b78336f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Tue, 30 Nov 2021 21:09:23 +0100 Subject: [PATCH 0635/1284] Added EnumCaseAttributesRule to config.level0.neon --- conf/config.level0.neon | 1 + 1 file changed, 1 insertion(+) diff --git a/conf/config.level0.neon b/conf/config.level0.neon index 9411e88475..bb0e229581 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -32,6 +32,7 @@ rules: - PHPStan\Rules\Classes\NonClassAttributeClassRule - PHPStan\Rules\Classes\TraitAttributeClassRule - PHPStan\Rules\Constants\FinalConstantRule + - PHPStan\Rules\EnumCases\EnumCaseAttributesRule - PHPStan\Rules\Exceptions\ThrowExpressionRule - PHPStan\Rules\Functions\ArrowFunctionAttributesRule - PHPStan\Rules\Functions\ArrowFunctionReturnNullsafeByRefRule From 6aa017997dbd64d1e7d13c7c8d747f8079cc8997 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 30 Nov 2021 19:40:30 +0000 Subject: [PATCH 0636/1284] CleaningVisitor: retain @phpstan-var and @psalm-var --- src/Parser/CleaningVisitor.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Parser/CleaningVisitor.php b/src/Parser/CleaningVisitor.php index c8dcb4ff10..09bab4ba1d 100644 --- a/src/Parser/CleaningVisitor.php +++ b/src/Parser/CleaningVisitor.php @@ -51,7 +51,11 @@ private function keepVariadicsAndYieldsAndInlineVars(array $stmts): array return in_array($node->name->toLowerString(), ParametersAcceptor::VARIADIC_FUNCTIONS, true); } if ($node instanceof Node\Stmt && $node->getDocComment() !== null) { - return strpos($node->getDocComment()->getText(), '@var') !== false; + foreach (['phpstan-', 'psalm-', ''] as $tagPrefix) { + if (strpos($node->getDocComment()->getText(), '@' . $tagPrefix . 'var') !== false) { + return true; + } + } } return false; From 593cc3c9f6ae503e4be239d8aa97e3a497704bee Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 30 Nov 2021 20:51:07 +0000 Subject: [PATCH 0637/1284] tests --- tests/PHPStan/Parser/data/cleaning-1-after.php | 2 ++ tests/PHPStan/Parser/data/cleaning-1-before.php | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/tests/PHPStan/Parser/data/cleaning-1-after.php b/tests/PHPStan/Parser/data/cleaning-1-after.php index 3eb72d6ca5..d47292a536 100644 --- a/tests/PHPStan/Parser/data/cleaning-1-after.php +++ b/tests/PHPStan/Parser/data/cleaning-1-after.php @@ -38,6 +38,8 @@ public function doFoo() /** @var Test */ /** @var Test2 */ /** @var Test3 */ + /** @phpstan-var Test */ + /** @psalm-var Test */ yield; \func_get_args(); } diff --git a/tests/PHPStan/Parser/data/cleaning-1-before.php b/tests/PHPStan/Parser/data/cleaning-1-before.php index a0cf69ae15..5afdffd96f 100644 --- a/tests/PHPStan/Parser/data/cleaning-1-before.php +++ b/tests/PHPStan/Parser/data/cleaning-1-before.php @@ -68,6 +68,12 @@ public function doFoo() /** @var Test3 */ $foo = doFoo(); + /** @phpstan-var Test */ + $foo = doFoo(); + + /** @psalm-var Test */ + $foo = doFoo(); + if (rand(0, 1)) { yield; } From 9b7c8249943090b72127960cfc046f75c7f7efde Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 30 Nov 2021 20:51:21 +0000 Subject: [PATCH 0638/1284] Updated cache key --- src/Type/FileTypeMapper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Type/FileTypeMapper.php b/src/Type/FileTypeMapper.php index bf0b251c0a..8508df9f7f 100644 --- a/src/Type/FileTypeMapper.php +++ b/src/Type/FileTypeMapper.php @@ -217,7 +217,7 @@ private function shouldPhpDocNodeBeCachedToDisk(PhpDocNode $phpDocNode): bool private function getResolvedPhpDocMap(string $fileName): array { if (!isset($this->memoryCache[$fileName])) { - $cacheKey = sprintf('%s-phpdocstring-v11-inline-vars', $fileName); + $cacheKey = sprintf('%s-phpdocstring-v12-inline-prefixed-vars', $fileName); $variableCacheKey = implode(',', array_map(static function (array $file): string { return sprintf('%s-%d', $file['filename'], $file['modifiedTime']); }, $this->getCachedDependentFilesWithTimestamps($fileName))); From 7d8c29d15bd96c71d52e5e4c08c27d2eefb7f4c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Tue, 30 Nov 2021 22:44:02 +0100 Subject: [PATCH 0639/1284] DuplicateDeclarationRule checks duplicate enum cases as well --- Makefile | 1 + src/Rules/Classes/DuplicateDeclarationRule.php | 18 ++++++++++++++++++ .../Classes/DuplicateDeclarationRuleTest.php | 14 ++++++++++++++ .../Classes/data/duplicate-enum-cases.php | 10 ++++++++++ 4 files changed, 43 insertions(+) create mode 100644 tests/PHPStan/Rules/Classes/data/duplicate-enum-cases.php diff --git a/Makefile b/Makefile index c1f305b635..ea10bd4ce4 100644 --- a/Makefile +++ b/Makefile @@ -28,6 +28,7 @@ lint: --exclude tests/PHPStan/Rules/Operators/data/invalid-inc-dec.php \ --exclude tests/PHPStan/Rules/Arrays/data/offset-access-without-dim-for-reading.php \ --exclude tests/PHPStan/Rules/Classes/data/duplicate-declarations.php \ + --exclude tests/PHPStan/Rules/Classes/data/duplicate-enum-cases.php \ --exclude tests/PHPStan/Rules/Classes/data/extends-error.php \ --exclude tests/PHPStan/Rules/Classes/data/implements-error.php \ --exclude tests/PHPStan/Rules/Classes/data/interface-extends-error.php \ diff --git a/src/Rules/Classes/DuplicateDeclarationRule.php b/src/Rules/Classes/DuplicateDeclarationRule.php index eaee0ead9f..7752389d59 100644 --- a/src/Rules/Classes/DuplicateDeclarationRule.php +++ b/src/Rules/Classes/DuplicateDeclarationRule.php @@ -3,6 +3,7 @@ namespace PHPStan\Rules\Classes; use PhpParser\Node; +use PhpParser\Node\Stmt\EnumCase; use PHPStan\Analyser\Scope; use PHPStan\Node\InClassNode; use PHPStan\Rules\RuleErrorBuilder; @@ -45,6 +46,23 @@ public function processNode(Node $node, Scope $scope): array } } + $declaredEnumCases = []; + foreach ($node->getOriginalNode()->stmts as $stmtNode) { + if (!$stmtNode instanceof EnumCase) { + continue; + } + + if (array_key_exists($stmtNode->name->name, $declaredEnumCases)) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Cannot redeclare enum case %s::%s.', + $classReflection->getDisplayName(), + $stmtNode->name->name + ))->line($stmtNode->getLine())->nonIgnorable()->build(); + } else { + $declaredEnumCases[$stmtNode->name->name] = true; + } + } + $declaredProperties = []; foreach ($node->getOriginalNode()->getProperties() as $propertyDecl) { foreach ($propertyDecl->props as $property) { diff --git a/tests/PHPStan/Rules/Classes/DuplicateDeclarationRuleTest.php b/tests/PHPStan/Rules/Classes/DuplicateDeclarationRuleTest.php index 5ea6be4bd8..6af31bfe33 100644 --- a/tests/PHPStan/Rules/Classes/DuplicateDeclarationRuleTest.php +++ b/tests/PHPStan/Rules/Classes/DuplicateDeclarationRuleTest.php @@ -73,4 +73,18 @@ public function testDuplicatePromotedProperty(): void ]); } + public function testDuplicateEnumCase(): void + { + if (!self::$useStaticReflectionProvider) { + $this->markTestSkipped('This test needs static reflection'); + } + + $this->analyse([__DIR__ . '/data/duplicate-enum-cases.php'], [ + [ + 'Cannot redeclare enum case DuplicatedEnumCase\Foo::BAR.', + 9, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Classes/data/duplicate-enum-cases.php b/tests/PHPStan/Rules/Classes/data/duplicate-enum-cases.php new file mode 100644 index 0000000000..99982de3de --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/duplicate-enum-cases.php @@ -0,0 +1,10 @@ += 8.1 + +namespace DuplicatedEnumCase; + +enum Foo +{ + case BAR; + case FOO; + case BAR; +} From be98a32d24f2e2d4d05219262dc4111035271b9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Wed, 1 Dec 2021 12:22:32 +0100 Subject: [PATCH 0640/1284] DuplicateDeclarationRule - it's not possible to declare enum case when constant with same name already exists - and vice versa --- .../Classes/DuplicateDeclarationRule.php | 46 +++++++++---------- .../Classes/DuplicateDeclarationRuleTest.php | 10 +++- .../Classes/data/duplicate-enum-cases.php | 14 ++++++ 3 files changed, 44 insertions(+), 26 deletions(-) diff --git a/src/Rules/Classes/DuplicateDeclarationRule.php b/src/Rules/Classes/DuplicateDeclarationRule.php index 7752389d59..459ddd5606 100644 --- a/src/Rules/Classes/DuplicateDeclarationRule.php +++ b/src/Rules/Classes/DuplicateDeclarationRule.php @@ -3,6 +3,7 @@ namespace PHPStan\Rules\Classes; use PhpParser\Node; +use PhpParser\Node\Stmt\ClassConst; use PhpParser\Node\Stmt\EnumCase; use PHPStan\Analyser\Scope; use PHPStan\Node\InClassNode; @@ -31,35 +32,30 @@ public function processNode(Node $node, Scope $scope): array $errors = []; - $declaredClassConstants = []; - foreach ($node->getOriginalNode()->getConstants() as $constDecl) { - foreach ($constDecl->consts as $const) { - if (array_key_exists($const->name->name, $declaredClassConstants)) { + $declaredClassConstantsOrEnumCases = []; + foreach ($node->getOriginalNode()->stmts as $stmtNode) { + if ($stmtNode instanceof EnumCase) { + if (array_key_exists($stmtNode->name->name, $declaredClassConstantsOrEnumCases)) { $errors[] = RuleErrorBuilder::message(sprintf( - 'Cannot redeclare constant %s::%s.', + 'Cannot redeclare enum case %s::%s.', $classReflection->getDisplayName(), - $const->name->name - ))->line($const->getLine())->nonIgnorable()->build(); + $stmtNode->name->name + ))->line($stmtNode->getLine())->nonIgnorable()->build(); } else { - $declaredClassConstants[$const->name->name] = true; + $declaredClassConstantsOrEnumCases[$stmtNode->name->name] = true; + } + } elseif ($stmtNode instanceof ClassConst) { + foreach ($stmtNode->consts as $classConstNode) { + if (array_key_exists($classConstNode->name->name, $declaredClassConstantsOrEnumCases)) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Cannot redeclare constant %s::%s.', + $classReflection->getDisplayName(), + $classConstNode->name->name + ))->line($classConstNode->getLine())->nonIgnorable()->build(); + } else { + $declaredClassConstantsOrEnumCases[$classConstNode->name->name] = true; + } } - } - } - - $declaredEnumCases = []; - foreach ($node->getOriginalNode()->stmts as $stmtNode) { - if (!$stmtNode instanceof EnumCase) { - continue; - } - - if (array_key_exists($stmtNode->name->name, $declaredEnumCases)) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'Cannot redeclare enum case %s::%s.', - $classReflection->getDisplayName(), - $stmtNode->name->name - ))->line($stmtNode->getLine())->nonIgnorable()->build(); - } else { - $declaredEnumCases[$stmtNode->name->name] = true; } } diff --git a/tests/PHPStan/Rules/Classes/DuplicateDeclarationRuleTest.php b/tests/PHPStan/Rules/Classes/DuplicateDeclarationRuleTest.php index 6af31bfe33..78b33179c5 100644 --- a/tests/PHPStan/Rules/Classes/DuplicateDeclarationRuleTest.php +++ b/tests/PHPStan/Rules/Classes/DuplicateDeclarationRuleTest.php @@ -82,7 +82,15 @@ public function testDuplicateEnumCase(): void $this->analyse([__DIR__ . '/data/duplicate-enum-cases.php'], [ [ 'Cannot redeclare enum case DuplicatedEnumCase\Foo::BAR.', - 9, + 10, + ], + [ + 'Cannot redeclare enum case DuplicatedEnumCase\Boo::BAR.', + 17, + ], + [ + 'Cannot redeclare constant DuplicatedEnumCase\Hoo::BAR.', + 23, ], ]); } diff --git a/tests/PHPStan/Rules/Classes/data/duplicate-enum-cases.php b/tests/PHPStan/Rules/Classes/data/duplicate-enum-cases.php index 99982de3de..0b637942c2 100644 --- a/tests/PHPStan/Rules/Classes/data/duplicate-enum-cases.php +++ b/tests/PHPStan/Rules/Classes/data/duplicate-enum-cases.php @@ -6,5 +6,19 @@ enum Foo { case BAR; case FOO; + case bar; case BAR; } + +enum Boo +{ + const BAR = 0; + const bar = 0; + case BAR; +} + +enum Hoo +{ + case BAR; + const BAR = 0; +} From e61ad9575a000b410efcf2824ef3a4c87083e1b6 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 1 Dec 2021 13:03:44 +0100 Subject: [PATCH 0641/1284] Copy all environment variables when running PHPStan Pro --- src/Command/FixerApplication.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Command/FixerApplication.php b/src/Command/FixerApplication.php index 698b24d7e9..ff9031dac8 100644 --- a/src/Command/FixerApplication.php +++ b/src/Command/FixerApplication.php @@ -313,7 +313,7 @@ private function getFixerProcess(OutputInterface $output, int $serverPort): Proc throw new \PHPStan\Command\FixerProcessException(); } - $env = null; + $env = getenv(); $forcedPort = $_SERVER['PHPSTAN_PRO_WEB_PORT'] ?? null; if ($forcedPort !== null) { $env['PHPSTAN_PRO_WEB_PORT'] = $_SERVER['PHPSTAN_PRO_WEB_PORT']; From fc1c6854cd253edb4fba799e3c4eb0b97a7629a2 Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Wed, 1 Dec 2021 13:24:48 +0100 Subject: [PATCH 0642/1284] Allow destructuring of objects implementing ArrayAccess --- src/Rules/Arrays/ArrayDestructuringRule.php | 5 ++-- .../Rules/Arrays/data/array-destructuring.php | 29 +++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/Rules/Arrays/ArrayDestructuringRule.php b/src/Rules/Arrays/ArrayDestructuringRule.php index de523a58c3..047b9b6c36 100644 --- a/src/Rules/Arrays/ArrayDestructuringRule.php +++ b/src/Rules/Arrays/ArrayDestructuringRule.php @@ -14,6 +14,7 @@ use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\ErrorType; +use PHPStan\Type\ObjectType; use PHPStan\Type\Type; use PHPStan\Type\VerbosityLevel; @@ -65,14 +66,14 @@ private function getErrors(Scope $scope, Expr $var, Expr $expr): array $expr, '', static function (Type $varType): bool { - return $varType->isArray()->yes(); + return $varType->isArray()->yes() || (new ObjectType(\ArrayAccess::class))->isSuperTypeOf($varType)->yes(); } ); $exprType = $exprTypeResult->getType(); if ($exprType instanceof ErrorType) { return []; } - if (!$exprType->isArray()->yes()) { + if (!$exprType->isArray()->yes() && !(new ObjectType(\ArrayAccess::class))->isSuperTypeOf($exprType)->yes()) { return [ RuleErrorBuilder::message(sprintf('Cannot use array destructuring on %s.', $exprType->describe(VerbosityLevel::typeOnly())))->build(), ]; diff --git a/tests/PHPStan/Rules/Arrays/data/array-destructuring.php b/tests/PHPStan/Rules/Arrays/data/array-destructuring.php index bfe0ff5421..8dd4b625e0 100644 --- a/tests/PHPStan/Rules/Arrays/data/array-destructuring.php +++ b/tests/PHPStan/Rules/Arrays/data/array-destructuring.php @@ -22,4 +22,33 @@ public function doBar(): void ['a' => $a] = ['b' => 1]; } + public function doBaz(): void + { + $arrayObject = new FooArrayObject(); + ['a' => $a] = $arrayObject; + } + +} + +class FooArrayObject implements \ArrayAccess +{ + + public function offsetGet($key) + { + return true; + } + + public function offsetSet($key, $value): void + { + } + + public function offsetUnset($key): void + { + } + + public function offsetExists($key): bool + { + return false; + } + } From 0938424d68cf64bf0bc9378e30014c8a3090d071 Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Wed, 1 Dec 2021 15:06:23 +0100 Subject: [PATCH 0643/1284] Spreading of general arrays should not lead to a non-empty-array --- src/Analyser/MutatingScope.php | 2 +- .../Constant/ConstantArrayTypeBuilder.php | 3 + .../Analyser/NodeScopeResolverTest.php | 1 + tests/PHPStan/Analyser/data/bug-5287.php | 59 +++++++++++++++++++ 4 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Analyser/data/bug-5287.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index a15d08bac9..c987a27457 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -1774,7 +1774,7 @@ private function resolveType(Expr $node): Type } } else { $arrayBuilder->degradeToGeneralArray(); - $arrayBuilder->setOffsetValueType(new IntegerType(), $valueType->getIterableValueType()); + $arrayBuilder->setOffsetValueType(new IntegerType(), $valueType->getIterableValueType(), !$valueType->isIterableAtLeastOnce()->yes() && !$valueType->getIterableValueType()->isIterableAtLeastOnce()->yes()); } } else { $arrayBuilder->setOffsetValueType( diff --git a/src/Type/Constant/ConstantArrayTypeBuilder.php b/src/Type/Constant/ConstantArrayTypeBuilder.php index aebd713e9e..04c93f3c73 100644 --- a/src/Type/Constant/ConstantArrayTypeBuilder.php +++ b/src/Type/Constant/ConstantArrayTypeBuilder.php @@ -105,6 +105,9 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $opt $this->keyTypes[] = TypeUtils::generalizeType($offsetType, GeneralizePrecision::moreSpecific()); $this->valueTypes[] = $valueType; + if ($optional) { + $this->optionalKeys[] = count($this->keyTypes) - 1; + } $this->degradeToGeneralArray = true; } diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index ffcc6d973d..68294b6ef3 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -533,6 +533,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5017.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5992.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6001.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5287.php'); if (PHP_VERSION_ID >= 70400) { yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5458.php'); diff --git a/tests/PHPStan/Analyser/data/bug-5287.php b/tests/PHPStan/Analyser/data/bug-5287.php new file mode 100644 index 0000000000..3bcb5eb4fb --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-5287.php @@ -0,0 +1,59 @@ + $arr + */ +function foo(array $arr): void +{ + $arrSpread = [...$arr]; + assertType('array', $arrSpread); +} + +/** + * @param list $arr + */ +function foo2(array $arr): void +{ + $arrSpread = [...$arr]; + assertType('non-empty-array', $arrSpread); +} + +/** + * @param non-empty-list $arr + */ +function foo3(array $arr): void +{ + $arrSpread = [...$arr]; + assertType('non-empty-array', $arrSpread); +} + +/** + * @param non-empty-array $arr + */ +function foo3(array $arr): void +{ + $arrSpread = [...$arr]; + assertType('non-empty-array', $arrSpread); +} + +/** + * @param non-empty-array $arr + */ +function foo4(array $arr): void +{ + $arrSpread = [...$arr]; + assertType('non-empty-array', $arrSpread); +} + +/** + * @param array{foo: 17, bar: 19} $arr + */ +function bar(array $arr): void +{ + $arrSpread = [...$arr]; + assertType('array{17, 19}', $arrSpread); +} From f9708de27e8642a934e7ce38109add5d32c140c1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 1 Dec 2021 15:28:37 +0100 Subject: [PATCH 0644/1284] Update Box --- compiler/build/box.phar | Bin 1778669 -> 1815932 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/compiler/build/box.phar b/compiler/build/box.phar index 76da031be91598cd7504a2e032057180fb434711..d0476cfc4c82d53868b9027167151930e66e848f 100644 GIT binary patch delta 1384825 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 udRaL1)o)wt()Bjq)Re&e_tuR~&erzo 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 z z?d=&;MSs21P2hPNTp6D4sr%x#cX!vo1vA?^A8I3eVk{Thz3~npxjM?>A`ke z`R|^Beq87;UF+4}k3BH81xLTD(NW9-q6x@~#B0w9=Z`W&D?y^dp>58b2A3+-a~ z`OW9s;YfL{9Q9tUZT?|29E9yV1)5)fd=oZ*xqrU-S=hGQU*GtAc>A_2`=n1v8E79y zkZw^zdE%_9$dl12U!bf`j6}WTqP;Ri8rv&PU%Fpm@5PJmzgHgeTej~>r_-7T*n-J_$~wsEl{9H17wFM^OC(E|<=<9pU3N({2|hqIXyYa%Z^M}Fpz#oy4i1%v-kbl~fAgk))#;Yx=NnP)AyYNzPN+O(FY z$qbUx#Np}EnGj);ZkK#m)uGY9`p23{M#t99roebvlWgc|E_9^!r4J_>oo6AzsbLx3 zTxD9~65^L?419&IF@4aweKkSufPYNxcG`0O+_k?^_e|MY-9NX!0@Ii%C^PK0MMkA1 zN$nZ*$#&0m)+WjMbWl0MMV9D>R`^jqfk11k6G#(TU>6%V8yjooyo}itXW@pXk|?bH zUqtm36G0GUjrcqxvR8;c2K1-WFl-aC;?Z$2voIkygkjZ>)`u~kTHmU965`gdEJyO|9+2?T?>n{way3$;yFWVwYMvAjn6bmDR$#*w^Rb z$EvCfU}rEf5a{go+kA{7kAL3-4^K?uN$h`5kcy>H@AeOrm%K6a@UPGy#1;n(*9W1p z>zZCxBHWo?lIhb1scHJ9KWB%+-BPBj2>cR4-_3zH*X9V6s{7s?yy)VsDM_*@JNfPs zwuxPidPSMnC+%MzGgKr676K!HVgAKNRn<(R;*cz*!w0K?7Tof3`S zi3*3HZSWh@F`Ob1tc4nXMY|#*YD9_cLAgylE-DTh8knpqXwwVc4lRgM)dIS}yr4AD zM2lGn=&}`3++lQ)Xv;{G9#h!;I@z6(Hm`da;b_Mhw2;f`*dNry$j=#(4@fxd9kavS zXA7%MU3(ErqXBCoWPfgSkY-^G+*?NRk4bQWpey zVuhg3qlbt{(1V2W5^KzfVW}r%t-&-HAtFxYhlcBI2@R~<<$uM5>zXlPQE=`@K%nK_ z;|y{y2^zf{(G6Az9tB&ige5qE*#UIXfeGBj()43qiUN!X8F0k~)%2Z4&;V26y5u+{quS{-h18NpaV@hHOy;1QC1}hq; z904|zQEdd{Q-3AYuGbu9u)0F7+KjQs04KB~>m+&&{g5bZW`a z>OatMcwlyCTB8&|hlLg>at3;gWr_qa zPgFtS0;aw6>8my5z$ppKy4{=iXBWfX_~gX#;BsOQ@Wym^srzV){F=1GA_KC_ zBbwn0*p#FkH$mIVOaqeK0Nhp$_Ecet3`doq6CmdiDlJ@WXX7; zAvy5+W+X;dn4sG^3{Ud{f~ZH{R*c&R2M%2qMSq2T6e7rC$RfxQK%rxZqkSnO>cP+w zrbfUe6|4b2vBUN3pVwD{$(=|m&eDGmt(iaHB^TwVO)fHne&i7K{Wl^IOrF?-<2hQ& zdGX0PUvr@A*(B=axiVgTK&`{)VJG2x;GLWQ3~tUU51W?Pp4Wi(2qkvK6`CFt5zbl_ zU4PHE(*WC{zprK8$=wOoL}0-)6VJ#k3ouET8ba&MM2ZdIpbZru^{a#kXysH2^8JN= zQN(9Z$Qt5pNhqKp1w3fveStrRhO0UE6AbbtnuyA; z%u-k3yGdRt&z`jmF6B*>?^WM5>2vqiK1Tte_2ti&@t3d�v-?J7?(r9-8woPVAw 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

>1SVo zO>ZXSPH%FRQygc8GKO5nBgkl*SC-t5CiZU`~=Uy8}I<_MdQ#ERBv@j+h<;o#{NC!G92{HYBc3t;(DJI zrq9-CqR>`hL>1CJWGX3KEMl4;O=p4c!AUpqmQ~W1FM-b}3u{!$?o?VrH%Sc_gkFWV zNSTM;kbrJXi*rQV;Aby_aPK8cKki*JkY;AVz>mKmf96jB@ENv`N5chip3Y+p~{3uSQXq|x# z0KJisf4neASBw*(X$-hy6&R<;C`p-|x}7;&h7WJTn7dFf#kI&qoJ7-zt^*heX4_$H z&<(do=Fc5X-hd$@^Oo$dC!M8kVj4X-<*eKd-Vw-v1emD9B0XYT8tYMi|J(XMti5pM zv%A~|ID;_w2R6X>PwibHs5(#!0kB3;D9N&{e}}BjdZiOmoz&ehWg(YjI3QQV==b{u zIRi&5?qNCuQKsA?WvJxiJveS5%V*}W%CmMhXFW`+r>tJ{)tjgA7LgWtiKZymi4fAi zXz-{Kzb-~XiXoS3?YM^unTYISQ=kF;nql=2e9M=CIJubx+t0J&jv1Y=R)iFFt&fd@rO^hpPmUY&7Gq8;nU zM6)%YQ%(7&dtW7Yku(}zFMT&ic$y{f17|J3nL>9#-`aW5U`2s9F%0_G3HIN1}7HM&~36vj`&yxXGh-c7lke|bvy zq48e!&!0@XL@9U<=QmNczf=YIkrYhbATaTrfc5wBB|*p;4^RP5gVOYs{D?0Au48zi z2>}`dD(T8rwN4quCdJ#jDNow6DyXrvTTN=4R?RB?il&p8u_u~cEsNMy*=ZtjRflme zn()6c$98D|s|rDRzARm$Djflre`SGg#ER~C60}&UmFq*OYc{80=T}hq{JGG$syme3 zI@#$pjiB{b*Pgg=i@qtkqq@l#iO?~#yfoA}SKL(N#hr|xr_^(HfDerCd(|@)1Gk#X z?FVlECry_;_~mFcMs(wQZa^qL5_|~;V~K%#oTkj`{zSz1C zI8l9z>E=z3K;CU_d}ZJv=Z`o=uqPW@+}b^gYXd-;%GJP>zoLx)YoSfX1}|4*vePc5 z!rlAOpTj~s#h>%=q6qvL&47ac_;LE~{q@`Ts^#&6kGZgjVT$N)rxi&QTOwC z9ctI1S+tHd=wThLPIP70e-m{6161`&g4ZTZyJotNG`bHPEwxWH@S|qk~ZI@?d0;X@7lv>9$|;4hk89q3Z7DUTel6K^-yh%S=0rbu$}y4cvwi8XFt|9w5W z{jIhIZd=>dzwI3Da@)A^zn=o9dQ2&i9aH{xE^(bi>G{ryUCX{se@9X^v;^6#DN-dV z#~Q~^)*pSOzCw2akOT>UrefdqZd&~@u>cmki^YC{^!EqT>*>;x?@VYkb%vA-ewa-z zXIJlnJKNsa-QA&-wl24O9qNsGKSqIn?E3NRrKMR!NfeLXdrlO+UK-*bg59T17}0R) zeu=y6c^e1g zn3g=|^zf|H9-N;H4m+Kb_t3k^d#9m$>%^2Sd-RTm1=!eguA%}gik;z20d}hcdJ^!} zix=$gA3aE(0$D`kXLy=5>pPVGvs6f6ZIcY7*A$ z18o4PbAER8`A@95DMoyTO`v?z7Jxoqe0tYDW6kY!gy$W*F%2D$!FSlO<73v?6@%Ud zfkz!*9=8Ww=Vupf*09&1_3`kyBbv6DcTVqO4oOJ<+3{wSgOKF*$w%Hu2tK*|I~|HS zTLN~7{Bsaee|^s%kV3(#>umq8i)rT9a(D7T;cIZnGxe zOK9$j=J7E!>~+?{oxzsPT5M;q$q|9jv9ZG%_xQ+Be>}iri_h4{M@~c&A2--z7auu} zeyveZ@@W{1X2aNpGnVlIg5eJ!RlAWiw&cc++}JZVP=i`oD~IoyT3fjlptX^tbb0Ds>c~Ehp3FWSea#uyUtD@XBqTEwa?x`sERFr#0lzfwRRagNjhbeQ7 zXl)HzTZ7itpta3tTN<=24ce9lZOe#uL&dtGe`?&k+F>M56JPw#Hf78gFUuHGXRnvY$gj1!-sk9DBz+T;pqG>_zR@ zcnE+g@#%<;otYOO@<=}oRPF8D%(DLt131(CI@C&AT78OzVi&aV>ZpJFa))zdQLPrbzrHBLoJyEFpEXazmDbf2s3t z^COUcb&!`IBoCO+W612G%d&D%*j?%e$XXsu{i^~nq{HBrV)>%bao#>VJ2}g0HVgLl1t6$ltQdJ5^? zln!G$()5I3CswKYa_vG6T<^u4T}V8kX2&$U*{&fV^Vh`*yLnn z-kNne@_wVZXkO5zbMp&65b8x#gAFN3HG%W5Ta-gitGh<+sfuuuC8XuWmX_(d%TwTHWd)HzcvglbM?A9!HLFI}h!!Wnp;yls{?r{d zLzgEsikvI;I2L*Ax&DBQf3%iVkZaA1X2xeWrWegFqd2VRt>6TmZWGI(S$WH9+GHXXZ*dljCB39Nekpq&AVtfN9=W&7_-JXPmHK3tM+?Y7iDFv5J=E022B2YRE z62Kz;V;00TBCa3B)ESXrOh&=%GVLhaIc5i#xWUX{4H$iO>%O-3fAXN-gJ1V=wac*| z#;>q3>(|%T)*1(m2M}SNu`3#XgzdbT4g*k}EAFg7=F1G@6M4OMZYgnmGV^IPbf&=g zB^c5OT*5VR;(fvtmo3ySTGmR;)(`f9&;HA=$YibYV)e_vqCm(XSX6R_?shBRU%`Xw+eE@}YN)aN>?sT;H~Af<%W{Rxkj zDX~E6^(?r02`Y_-V!!7b)=%)Q6@!)NI{0!LL@pZSIzwW_!TghuY3ccu))K?@2#ual zgDLUmnGJeNY;aGtcH#vBkV`N|joi4`aP0bQ8 zMW_Han29@~3uR>;(oAS*eaan4S{Fvurr(keH=L34@MsZXZnQ2oS zB$poPO9)AC7PT!&o`YqA--ntBixNybn10o5!|W#|e{lNc)zn!ih&ox)9KN5rAx|W* zTH3{zFG;(^07Kfv7RpsoHXh5%dTu15csVJ<@Lur7q0HOrbneNzCc+!GRr@@=n!;&x zL@jX|M3IZ(v=b91gnbtKi2YbYU(LKu0%kkeDC0W%;<9RBc@jEwfGB&vOsl1Gj!Cnm z(YMm@f9mE_tsGUihYFQ~VN;lHDniLWo}1^7R%zt&4Sq(wSyAN#-u-a}trr zhwg2;ab_!J!&g@q&KDjHnlp2j->4GSAE`4wPL;u*v18OS93x&bOnij&s!%sm)xKF> zS(y?nBp&|^&#lfh-=?-17kva_6bE7Uwv9D*f5RvqC_$)h882O?&X@!LX*c>^8ATt% zRcCR8HlWGoAr_8w7_|5y3)akUw_!O^No2(t#~;#QT+vHi_#--oMMq^PIk^Q27Y^fqu~MU1h?>~=ZwR{- zf3IOYW9z0Pq53ZQ)Kzv`^2stxlCBE!p-+W?s|74n3(U&-lqgQf(T^{)b{l$YC$xnn z8K&@rKZ*OoFo=0wBY$Nm67vYN^5ttMx>l}9pB(*5xN|p{;<`7ZCwGF=n<7%}EYIgG zhHH|QWu{D#a`VakL%NrI5N=~8zciQ=f7pu0$`r)&rau!5`zp;3*%i1O0lkss4=8h8$h$7-bV&>UsO zmzm-mjpCVHEQ{`e5{RJd=}uVGYLM@+JjK!nyp2)Ev~y#O*DnQ{dt)zfVgS|6e|(n4 zE($`0Fvvc6ZH*IufOinN6)g0y`nfge{gmCk5gc=i3hHLbaowzn}jQqpI#1um2WOIOBbHimkE)?F#$+ zi0n$ySJ?E#B;^)qvB)U{=>j$5f1(<-U`FQjhtt8&?X%9&$>+h*2l74HS-01XNf3S% z0G=bB7es(j2s_=D1t;4YunO|P!cuTy)S}y5vS8(c@UNh&%HVEFd0!!2TLgPnWmhJx zzP(QgdZmB z6)fPXeV9!>cZh)^uq5t>E6Eaniw3XhVn5NjQon903#S)ZS@xZim?_IjFR!(Gt^L68 zX?s0N_3il?z)XSG;clhF_#K@SuP~Rj?Mv^#W z@`S}QT&+-_-%M6C8sFdte`PT|lN)@LyOK2*E@C``)kBX0)I9v(?)@*=y=uGtyR>}B zn!MD*uWI`e!9I6Pwp%sHSR{$q`$ZF@*7~<3MvG)YEnPK9@kWX#s!;QJ+|v|jQolK< zY(LThcfGvCr>SJs#cPFyYF%21R5Q%EG|%Y` zufzIoLqBi)$UM7cnCP1)s(vJy6qZd24U^)sNwHzltCC5t3UkDZ>}Ek+#}jWr{n&-* zWJHJT)wz{F+@WFZdF-9H^ex;(Blzc`*1Cm`)p~6;Nw59+eDLRslk;{5IxfyXuI|b& zm^_rJW_CT|8@`!If4)@_@9jijZE5aKfV~X7T5pjT1dx<`5~IU#E1_8S)yZDjWKTEQ zz$klnXw)Hk4iNW#q6+FnrC!pG|1!4u$1Yyl{##_kxP}k0V{)E5Dgi=t%?A z68j_%Ffglp=Tv0;GMM0d;s5^UziN3NQ*{%cRI&fgz#?g!f1`3+eAlrCi-OwoOP3~@ zI5(6;vykFDmUx8-NFm^$5%Gf56Uyqm{#^&}j@Hhglzn_3cbH8lGC zu=F3zR&8(FHW2=>0E~OUz_ZA*nb)`&mH;0o}WwMPg&~I^bxW+bB;dXH#jMoQ<;%~!sK&4pXSj= z{?zOJ^#1)Xl+qukKldHSf9t?o_!2V-GnP=m-;4+adC|0-z`cBk~DkUrzWf56T1B!Pfu&w@p<0+NQ1uz;o# z4W~0M=0q``b|6bA!L>(57e?>${B&?N9B3%xrHYAy1(6UksRWzmiiWUYD#jg*K=NE* z&;bvPzrHie(?BDml_1GOdOjOGp{Dd&8m{epa{wPU18<}kCbYWS2D_~vK~*kRFkl4w zW_Rf1BQh}rj^33Eq6a1bA3PPHpOEe7_l^qayYAeW-gfCbcVRq+pETBAe z(cp1#QwtvxQ)adZXc&PlMarjtQ)7_ppY_>*1fJlgnKlFd8CJM;DT(n&jr?h6ps= zcT8<1W{C(vo;?I=J>(4Ybc|+6BC+ne-b`Zdyw+qmxcxe~y>p3(0(WdGx48%>&?_r| ze`E)*#SpC52mC$SlGe-b-c`xh!VOfY2|l{xyA~VwS3=q+D~R>oe{kXD1=j!fO$crk z)gZ4TQzEBpuq99obVImqA>OnQ(`a$V5)UD z*7-r@4K1hsw(38FLAxmpP z8`A+#6-|{iwAZr{yLnPBdb(K;3thK1DPIeCjame{Ro1P>cCWatiayf-o9AAde{r7W z3L=KXvpO6q>03>KvhG;jb)--p4C@o3=3fUkdcHg=TA7&=xSrw>wJX+L!^JE7F$tzZ z!xVt~CpG)^jBX7(RNX(pdi+ME>7TY9pWvDJ+W#yer5k$%@hbLc5OZ+9l7xl!9?$^1 zfqPI-^s0?^2KrE9nUnF9B+GN5E_=;YpZg&L&XKyf9ODQM=&WlCHm^5N3H2%#bHXpZZBe* zA~^wz!3Cavg4(8e1w7W^`g&mVJ;XfN`}*7#vO%>RDof{Ymp^3!b$`8`X>;4iwfEo8 zr_hU3O{OXixG$5N`#iQ5TN%kxMA@F)QkAO-G)Rm=fP+Pr=2pJ@?FIWrbf1GvB_;{s z{Lil&joz34`hUm$@rNG_8CpOQ6Mj_7JcM(g!# zHk*v;_J{xd;f(!vjJK|1o=J?eKiQ5;!?VAiksu(iXMYyA@c(|CPDZ9hClh(`6xhp%7H{(APK@z=lpD1Sb~*MseQug1}LUzxi5 z#*AK%)Y$rd6h;9#@}U3tm)wy}$JT7MUQL!ubFx^@R%A4qPSz89Hl3K(bld9aHyUjN z;&`FK6vN054sXIJ-iF5g{*JHA3 z^+*m(iT`W3&VSahW6z8n-wUhy%Y|u;mg~iMzF15qqv>?IUb4qvPv+Z^wX)5g`qy4W z1CO}vj>wFR$7{l#`Fvz=x0CgBvKm`!GFy#COFE+cpAi#YjoP2lh%T(<)}E31YCPWB zWNzBCkvTU_Yhe?*ApIR3w#~@$eSLxdw))?#dg7xoVD7nYLy z_0*hgXDk&g7nVIX*R%O*b|OH>z&B|ab_Zr^k?Cq}&n7IUC`%12&CwZ4%;uEk2BX>H zgb$XHIDf=-{vcf32m`a-)7IQBS$<&7=k{znCQCXV+4I$Cv7AildbOUM^7QJN-45t@ zwWQPKcsm`f)=O)_VsC5`b3s-tx@`MoP*MlyzGuckK)vWX;<>CJwELV*M;0Bk>~pnU zQkKEl)|SrA`D|{jCf0P?o)(fvL;~Wv)D_Q&`G1Y*OVsXcwOY|Ju_?7CEMbqQESVFQ zQ7qPz(Rxf5?Qd;^vkebzd#YH?*o$gU=yqmJ>=jGoTlSd7bFy5F77L;jtS^4>jdn!g ze7Z8pYRn3rHIrT}$BV^iOqu(YJ)?xSmlyug2@TWr!#JQFK^c$MCQEM&-(tDzY)k3b zUVqz*$;g_qH*4N1&jYdZJ%<{hZ#nVdtI0}E5*cZMT+4nIOF}KOoXnQX@oLRnu~bJ{ zK{Q#K)5UCjGFx~i!5b?+99d*N(9n+q_V(uCGWefrDPl36E+?zmm}NmM36iyKS(EX6 zwVo{&3zpONd`@q2FP(4Lc(mMZ*Y?CV?SCc9A=tOslqJSBU7K_}AGN<>`}nYpcf7cL z6Pi8`VW%IK&28s%b3K|YMpJubP9|G>Os(;3y`|e_M>*T<=eN-J>OryCD>Ej9zdB1+ z=-HFeWNNee$((&3w?8e<`R1606D8XG%U^jwYUyrnjwUwCr|tRJBCLKgA>)}vSbr?B zC&ki)l7nhevsvP0X?j7~=WMlHvR8CWZEHN*&X-eL4L+dX9nT_Lm)3%?rnB|J9<%2# zXGOzuytFMcTFs{uy5@OMgPS~2Gx;l4McllG5j_~Zw)iy^-)q&l@qD&k%}ti0O~%Xd zWVEuDtQeZxWMnSp+op$I@?x?C6o1uUd%7IYt+mNC*9(@7vFE&El8c!&9Om!9@o#hlk=QgQorc!HtsdL_h(MIAmf`FJ&$ga`g z*)gd$l4$mMECoG1eI~($C}t9|`4}K}FDscCTvsd`%Ak6u>1HL?HWiso8pOWIyGe~b zh0u4^qn^6f)6+j#*>mG~>3?f7UG}tQ`N-4Lji08$Y2Rlg?0C9i?b|Fe`+MCV?@18m zhSR==va)o2$u-z_f1gHIZ}41!M|ecM=+QYSzdQHD{6=>4GYvwXd#B;(`_S%Kost2_ zN@Z%?*Y_{<+40_gczR-gNp0snZFxG`n0tC4spYJvrMxVj-A2@TRDW~i_cXdYW(l;f zRo27w^s7bL4IhYSxw-ScahA($=;1j}czqw(+ryE+wVs(8_I=Z(VX^tDnqq}QIsfbZ zxTS6BRqOk_Q&UlMS52ZN zS>^w+7T=w5*Ir@Ni?#2m9&36=`!_MugHe_0pim|v#590T)M@p&n!8$2$g)~89@%mV zC#%%Ek>JvfV>n;2GLHl&hB$P|TAK$jxKL`boGg*o=WA_qz<&tyo-zcqatRK6u2Ktu zs?->QLD@nIg{ji4f^wyD2yEp>8Vt;oTB=k{?LAEpCM9|;U8W?uqqw+{YAb?Onv$CJ zoiH$5e`hc;gK6m?R9mI`AZDthQC3%*xOx<_BbE0vABC70>Byv2k=h#1nlY3OTpXpE zNI{9RK3YSo9DmAZCX!zxrumh6rf=~PUX6#^*y`8AGm{o6;}oKAo)gtK7G0*|CUQk)ZyY`N8ByUlil_zzKRykzQyEsB6E$ShmUV5l;+8~Dv ziWR3#dfKSGB2KN#7n2@&o}|cCvDT)z_HOCH4_>t*r)&&h(0w`>Nv5E2zGu~WeFR~y z&yh_1)QgCcu{OhvvRulhMqg94ZakS3ZWw0Y#GZV6Sf z)~5I$CZ_*s2Kc10g9c~x*B?pE?}d2phr_nVm9t>oc~IBp#DeHO{xN*>Y`d#o&WiAndqd%DtB zTkTgc)pZ^?&m^L0^;H?5Yz~9C5B!4@_J57^Dnnwejo@3csqq97J59H|y5Pn<$M|PV zW96V%%oQP?iR)PZa;V#@4wyTy*r|C{OlUd@>xTUCs+N`t7HXb+|Jc_ByH#16Wu`1~ z_fD1E#E=%+uC9p5;`>$IV`8Y``0`mjl&OuDJL*L`9y2oyr}-<-2h#(Uw_SM8UVr)A zT#G`yyI)9T?rEU%)+Z1(a}9`(tcLVV)gM67PQ&eEKup$VRrgym)NuUC3*+O_4lpY-3kB+00FOH7oU-NtU@JWvw)o@yL7nJW4I7`v`+6Qa>p z$$P`FU7qEnW5B$PB3q@8B>Z-Qt$$^uaF!7@i)um3iDL96%D(huD=nf=K9LCerk^ep zy@b@x)URyrBGM_O8+cf|kg)d!j@2jxr7P6xlRtil+qZ`4pv!Cdxeg+noVoyJ%VhA- z9`^xN+w!4?B_*Ip=_MVM)E3N;%<$Ghkfu-qzHw6=_NlHlQWVUgB>D*hlz$%NJgW0O z@jSJ(Aoe^->@a+5l?p`&XpvB&Nv0laqDX5D4wqUR-~2|EitQpwCV)lLXNaJaMtzW= zMk5pcJ@Fh<`PG#;RRs{w<%<*b>(;E36l}o^4>V*Yz7~Qs1ruyyQ#C`+5E5GuL#ckk z>im2Iy3+&ib)I7NB$ol90e?~}QZkic(eTdA5IpPW@D+Oy%VtjkYRSnx`b3Q?Xk404 zsxAQ|V*l;~-%DmB>!sZ$5fGKE?NCcIj>_hmBytxvF%^%!nBKF)0>~GMtI#Anxm6{g zMCta2@`6VDG39C$0@7*pkrbH_dP7o@q;-7n`$|bD0V!DHa6q3{lYb1543@0%QgmLV z|HD^m&L<>UQO?ZKQ23Llmb?yOD(axY@z+D6I$C8b$e^OB*HW9vKo!uG`dQva3vuio@U00kYj3=9SCIxmr$+*72HGWJ|za7T5FL5SYIu&BpC!GT8a{g zM8HU@%HWZ)8mRxBSASz*WSI;C*+cAkS_N5dj6k7XoaV+r3T*;J?~sIBQ9$9*uasie zMw7BjW6Yui101-h&}bLQm?g3NR3AMkq38`EBnu+N%#{o3ItRW5a?he5-R7gwTnnUc zmwmRuh(;%Uy12U75(u4_rzg2!|CIB>83KhS0245zJrYd6?0>u+MkhyafJsJA^<$9_|DGS6B4rDzPI*w6zx?)`x3l2ipWXnvuu z)KYo^xrMH~3|q-%W~Jfw#y5$p9@L5%$gy&u8%%Wp)B_C)uU=4rp*%J8Rdl8{-X&cq z#i4HWiGN-u<>)J0>9`fAUQkQ@4XIl~-K&Sb(OLsT*)xhWrRXkO>9`eJ`mv}|b6c{~ zam)VgT{FzQ7L7EVrW4wff?6^HI8{zeHA5xme1)zbN4jeP+st(sR2M;3&3gNcqQKd* z$JFYleP&9^MczjZ% zIFjI93ezrDLI>W<~7l2>V>?=soM_{3;? zlYi%ur8+=mXZ-v9k);^h*_~#nZ-8%P)^Ls!Lo=NeYJ)Vb3@1iMyAaeyLt6<-Kea$g z3VnIf103E4ySQ_vddo2lMrx?*@N_lslxO7{VJkv57YELgPRl`Hx^={e2?A51guwL4 zqc2BlMjcz}^~cJ^bjjM^+*1_2U) zDu>F-PeH-KEGVH8UVBc2Ls(G4Ab*q>Zv5S@BRlUVD@wqSUC}3-3K?(Hkze}3d6O<8fcE~(BOfZet;T?y7myE;2p9&rXJ{#?}m=VUw>5{fc)~A zxXFMy4s=m)0N#(}KzWp)GFL?h;tr4OA#y)YPw5V+1Eu_uH>2Iz@Bz(!-V)C_#@$UQ z{UA2E`40w;|2+3ymzo+?U#0MsJ^hD+i2l;G1aQdrkbbv(6E`3w$pM(zMfT$IOQ>ah zU3LI=aX5q+4ZDh6_w}DKaesLZWbx;n{tYEGshH93XVBm!->lEX?buXbD?}yf0Rpj! z0=`hgBrXY1-;5^V0r)?KNoJLNLC%R2Rimc07nb{nYK}yPMK(LGdN>GL^_N#$G zeMu@I8Sp*%vUeitIw3#2A*m7~zbc&`@Iq8<`f@*fw>uEOQP;zyEq@nDdL5A!;6_PI zW?pcjkQRin`K-J}17|FVVRELk(YXs4WKju+FfE_?AHaE9lNb(W)`td7k5wT6-UGF@ z3g*lX;9GtwI+jLYuf>T?=`faN(vgBjs%KQ?P+70NFycG}lkHyw2QJqE&}Dl-^3xZx*^Rtjz$z`Lr=~W8 zW}-$u$FVAj&ioLwTI!+4uATjvtC9fbESFTo%Iy03{ztFl^?#aHd@ZN76<;MW6@rbq zrw3A8=@F(ABu=jM_$>}2$K&^G{qM$aOpQedm&>;-kv|LKMp+w-nzzn}0&envfISrK z0EnmOfdW~Tr;4^hUwZLjsJJa_gHcbl1+^Fz+lB3LesCbsP%&9m2jhNZ_tRh~+^RYl z_iphmt=9HpFK~oDYZ&77m5mRm6xT>oIagzmy{nu4$K!3OSz(JRteM5p*Jm6r)KtkR3+q5W*yeOPUBj6YAsq(!d1~*}-XtYCFb`tgvRPM$ zu<%YH@-?Yy)3L@X9RRN?tI5Yp%0!Aa%ieBzTgi;FYbdZr?!u^fK;b@*gZBAzx~N=CQJC)onlzx_1_43nrGKt4SM*VshkMue9Nh_0ZBTRr^xj~HpkROy0?gCa7;D7Cqo|!?3q-d-QRCunZH$bZ36mHhr|ENDq z>pm}ik<@m1=>+@emo8kj?8x0g)})|~)@Qq@DvR#^P%X}T?q}3U0ocIkBu(tN>Kxn% zi{N}uydCAe#Qel#$J<>v0d1RX)rr-vgvKiNtM8E`SHPqh9!0)*0g#$nWY8&PkAL*h z4|=q?S{aKXScDgjZL0!t8!Z2Of zO@T1icl`f-{a@d){{cP9YGPIxwtw$0*hnDnHp<38qFeT|ksW;yyUO%Osh9yFTaLENnlPpoQjW)UEZH^1`ZXm)#OZ zgNdGRu_X32jOd~G{gnIs&catc0NzQ}uVahf1L)Jr9rc_1(Ciy9v!RT0OMi}iDmck) zdOkk-kyxkgMNZ4+_9b7H+x2{=uc`djr(T;{7^wk<<3823l4Je4TG=u2_ey@*{km?m zrH7r%sVlCJ+DoWg-K)CBa+@Ai&C+V`PU8YalQ|s9IUGCo188F@Sb-6;1*P3PPqm;l zS?eJ^(BESxpnMj!Sg#;9&VM)_3zx(8rEDd-x{}c>ga=v_zlaY#o@N7NT|^IX6UE!! zRfE#aef`}X9X=bz^}{&e-IE@1U%L*A_u2i1DQ{1PTcw4j!{m#YiSp9EqNC=vYzK2L zUI1m+!V2g(3Mgr+bDi)k%rsqJZ>SjuPV{;o_|Hz)#+o(vHB(K5WPd{-K0qoj^+%|g zY9gc|IXwxshEOvFBB(zHq{pzKuFc|;HL1LgNly*l3ufA`^CDO|4Kx_{#YpxnO3oS- zOR8OW%Pyu@S}C%_>5fw3&2wV$#-hi7nk1r@EKtiXjH(B1)O6#e9tY^DGG@}+aKpu) zdb|Z>=X=+e$@)RosDI(7E6Xvj>7*6#DD51|P^H;dzUtvCOXXsjiJamiG>}j}bezbo zK=Yyh8Qq;giy53|F+)PR@S}}O!lo9LGE%CAjCRL&lkYeqP=hd$L<0R3xEo@(XRHc< zi4+{n@kqx87X65X1{md%YxbG-+Y73yhJ)92GG>1)g;OzGaeu&h6-F-gaCJUg%vLav zL_`B7>9pzW->QNfrA^lR!@sNccC{f~20_Q{=F<$q9tzRi>(v)Qp%3gn-yBs;{phF% zdCoV-6uWI5W7i2=wl>1Q;Sy>$YSJ5 zV4kliEMFlU7k|`r$%#`6d%_f?AVld7 zas?bCmjjcD9geZMQZWrGQRnf9E9W&|x{VWxdJ&~cMk)lR(N9Dr7OixYliUZ67x9sc zkM#RnkADwiu~r5@>jVtfstn?N^uJN>#5iSN!h)6tQ?}=m{(~$?XaL;Mt^+?#xE98` zPTA{z;1e>OXYFJnbc{?!pF})Fsl(SzzKx>5*~Sq~FTnsy>PAf*4g1_DE)D7B?=f*p zf5OCXb%GWQzQOch_%(4f>?@bPI4q}P)i=30?tc*YtxoWQ;SCn|6=^i&U+C+1Kd^>C zu1KRH-||CN28-yrIvO~iXc-cl4#wW-01^K{1IN5^_!epIhVtHg_J?5t(&-8 z{(s8pJdeAFd#Rx5K0DD6LXe3-Ax_U9P@z$uKhSvYErcbJfB|5jrbC?9_(aQVuLaK0dnhJdWeLP>eCa&m_}1;nBrn2r5lMV=@7{Sf1^Q5Z_gF7ibU zL-4jpYZ5;_y$!TGKgCJI%wFGj+D!o?NPkt}9Pa}^ZrZ#{14c+`6N$kG?GY|{Ou3NI zCg39OkdM#u%x_%57hL^!5~Mt19XvG&B* z1~#FGN2fnwuZc}-;$90`+`j3(23g#DxBHW1_b%!K<#Kpz{&!3?|2rDlX7AY14Ye^d zkxUbp^1<2d(J_T63g-iDU(TTj$A6@NNOFl2Mr|rpLIT3AO{J=MwW%5>Ms2E!39e1m zC{b!tRZMtos=(>{E+roFQ_n=PIdPwQs)V|g z;}Dr*Q5KCAr{5uCMCf-YuwnE&UXG6BK13zF){ib3Y>@TeVsHQ z4{F;v^(4fvla+<>c=`I`aDVS0_MjMYX&J5TFiNih_zYY#XfC8y$2C`|G!axAPw4TVgu zoWUYX)KJJic_BrMGARb-$~Lb-n&?vz1mpRh{%!+BDy{>E>D=^Fg?~nA1JF(!!0s6} zBE20nN>G?d2`d#{9*z-4At-gAFpzBt3M`r%6x2{Q@$ncmpVg#;l*&QTo(V?%K*&H* zUI5#!6i{CD_FXBW@L720tnmS-NtJ*=*PiLd7Df)8DuIFc9*QRQZ4{Y7N9>E2{W zHIYVHAnLszMi*3jH>n&h5kkOWc`yzR6N2Id6en_o4jcw2YJ?6L#zE-~kUC%()b|YY z{o3rQ>t4QKB%(|Tio`boM5@5$S}0J+53)fNi22C3W76NhIDbb=grGQlzcGZjQ0f3- zKJfq{4V&j$7*IGPm6;R}=`#u1juuoTN|k^>|8l5nT?IbEO14Z20|_BvOC4<(j7^^f z3~-x*SBn1wh_;3US@nf5yb+5el1=!v&49 z9uV$e`*cAOX23#TBYO>+;~0$X6*Ywmw7{clX-64+ZTC4dA^ zPZuAV0Wd?6T1FxTM!NJ&atwhlQ^J&Lp+U)UICc+gc)fy`tAasq@E+(5(gVFEJJv>k z9CA^l%GD6TpeJ861L_}y(aw3{pgh|GBvV8sY&oJEf`6!ToOeG)=*byvpn!uk+G%eb z4?9M=Bq}59w2c-@?ZY?PIpL>qw6KB>=V<4Jowv}!s(t)InG<7*O9BU--?E7gz|w^Y zfV@9Fy$dM$28<0RWJnz#%sBxZkdd#Ekqt|Hi0p}{~;lLq8 zSE>XB@-bL^D8{9^#{dyBP!u<$*T5+>5=IIk;2^<(QM~xTi)I9xL1BW}116y-%s z{e$j0CwVe0Ak>xP5!VGy{}f265){Y}FkqMp9Djrol=@++1O>wB8xTbB?f`-G-u1W8 znWzFN5rX3Q5k?UqWS}T~lU3;Cb4imZ0f9acH#+wZM+dqrQ9u>dfM6&EDZCK9_FTuK zA0BUS5NI+jFckFY+9DL-@Y*8O4cSuI6dl1MWPm7Goo1W|qn}Jd`LH#rlE&9xlqxZx z2Y=b-EP$hIn}G1akN_WO6bBf^Ya#^1k+Z};L&sw|P$q?fgb+iC5ESRTgVTeT2mx^r zX1xg-;H9w#LLU{n<`+%C%q8@NR zx;E@w9)lg@g#iv5B`~j|BOe1tDgl8ImVZMF9NZd|gv?C(YB2ZQHiBNjA2Pjcpsh*tTukHaE7djqQKl z+wbDLnx2_H-Br^)=TukK^He~;xO?1j;PN6{+hHj~$j7WJEFVi{RYA1FaFowyGcY-_ z4OBg__95P5=`I;zIho7W{D>Pr2Ozw)JT zVw#Su8+aXTQE~M*=RR#WmE8y_z&z%(4Kt*p#sw4s zp;6ABU3dahld4CdT~f~t_gqU}{u}DGf+}_f5Effwlo0kMq$l4iPu$4iYc#^ZiqQ_U z!m7kws3?mW4fv%v%r~4UeJT`ISl0XrYo)V-w|VP&Q>uhM5B_$jxd<>llJu(HLz66- z#TA@Vxct+G-9uo#BRRt(}dFgt<_4vqnuoiM5T`K30@eZq_V1K&A$ z;*57{GsF&oiPW_>19UodbW{L53}~mTK6))~u0}(H(u)VrVAXu`RJ3WmL0zJj)L93I zYvL->s$?cIXon;WDmUQ0U?SCbf51EMN)fY6KN5D>jcq=dW|c(v^_a8%Uqd6xe1vL~ z7znB!L+;wq)3Hl-sebT)gJA69fzLzj3c=1Vl^A0UHAi6-;O8PEvrZc0z@oXbFf_ke zxU531rVa&~qEuCw*L^5n{wiptLnXAz5t5%$Dl3dMyCUiv#9~17d2YM7E(z z*F;y5<(sh^2msj2IaQG!)ZfT{v_h+G`_>FlrkX&w*UQizv5U=aQG)WlijcS*nZF|A zQJW$SjD8R49^z4fwb1wnxF116qJ%4sb#fUZGIY*nGBq;Mz<`2QoK#d7wRu?mx})&C zoOAO;Q6KxDR6s6lt9s7$+YuT12mxbgs>|;MJ9Izv5dwrkzZX$R5xI{NqT6_zX1vC! zdkIf&L{q>Uk2STIz-qLN21g?DNDRQBgT0`Idi!~b1p1ft=8zW{Y>T|M=YMY4l2e(6 zMUbBR85a0&4_2-h4s1d8NDeB*RvAY{Hy_Ye@3_znT7*i;LJD>~(QPMJ&l;%#Pl74j zeqk@ftpI`^bBxz;(CONtM^>l;B3@UmsCok)!#FJU2DnP`Gwa>*?S;LwAiur7_bNyb zIEO~pW=Wx)b^96~f%K*5Gf>d9NX5`FK7yvV8ei6N#W!t?UE7#|J(db3;h7zrB|`iq z-XUD{m9)Lfd>6PoXPb_lpCGz+QJe9KV+uk3*aD&vAR?Of_AlhB6RZ22Lswc;fNTo< z5X+S32a>VhGaHr61%pjunYCab(2d=-Rf-?{Iu@e>=-;c z6r5R2glA}Tgl>nd_FZOzVFsEX2JRqV*lA!1b@@y-@lk|%I5BdWd>SR%mKXkaS0G-$-Jpfwfh(q$p$Ai9&IdqW*EMa3LJaIUpES zp?*M~lC<`w#mYcna;8V+&K9osI(ux*!*|pH9DOAnIKpjZ=gD>IYWT)udLoi6Fk*1* zhXN#SCS9uLsaW!wGeg9l*~9lYA zqkpg zp5E`!WQf4VKg7)m|0+(vE&DG7m2WxM4kFCi+6k%s;Z}nj*5+OgO8wJG0-!UXZYn%a zn-&7yv(+#P-UV7If)Mg`MlkFE0&$du!PWFA*TzS8D%S>%N4F!PyjrXK+ddIO#b#I$ zg|~7EuT&>!(6&R^Oj_KJ5lQ`86OfR$i^L_4bKGeek^8CnvL!Z-fhy%)mfDa|VI39z zIPRqDyieUY81y=TQusp_4A`U2*e5EdFqv|u>-CPnQ^yv-5~Si!6kt&D+8<>SH{5-? zc~|uPEqN~!z?s6tSriQM2X7{>ooKDZN zV0+)`yHBHGnGdF<2^7LsynPTCp?t4{>}X8|jx zSjpStF290rfo^c1xpa=tP;Bq`Yc#2DQ{)T#SVFL@!~aE-v%wR)7X33mMTh5$uOd|s?0SS=3` zZGEkxP=tBV&um7^Q2@{MfH0&Psa5xBzQrOw1rxJF_V2RdZzaw#qQtv(!n}?YKaMgY zr-%V*P68(*!8NXM(C|tOfSdhCN?;4;~ zh`Ks@6DvL9J7tHx%+ZLf;$J0s*l{8rKF_LBp&=Kn9M$!NwW*sE3}QT!@F@I<+8hQ)|5#b=A7%^~e= zK-gMA97h=ZAdL4ZI+)d1q3Hc02MbX9iY|@HAM;4A z68PV;hXWdQP;zCchACbH%6Ib|4twm;CkfJ zvwO)tI^a;Dtc|+AjJ7a9uP1Ks_K=2A^f-ZvhM{tTyMPulnq7SeJX(WV9^@kaUmU84 z4mNt$xM4gCwKdye8y*9~t5iSNx|_<A6KAb0NJ+~xEvc~p6s@M{E1kMhxFR70Nk~3$ih?oFuyEb zBUM=4$4ymy!<3EozkhMLz{J+Z>wr7V=$p+6Uv6!GJJ)6Wfk$e4UNlO^M`cF}KL%=& z#KrGanJ|IWKaONnuxQ@}$^t!%4?K!^COch)6^2x$LJ-7x5yufpYgVqm1L%e!j7?JB zAPh_eLmqyia3R~07Z>pYalhljmyL>Z*7zb)Yqf^>%?!ck^U?V}x#STU?}WgMsjE}a z0kz@haC@RbZmS!c=Kts!vQL3n!Um_ok5puc1c|Bo^C>32KKDi5M{9_c@OJ~Q zkQTjT5Nu}xlJNWpml~3q`aWrm1q4IF{ou)_0 z;V+xXoPvgl3q>?gDu$*hYC<$3iZlUzgIZv{_18pMKX3vuFSH^6LNMot1L6GY9s0z( zr{M&-f`vO6HQce%s$Z;N)DUG7(v5&T${DsJwmpU_nxG-gKTywuARnht z(>+4O+|Tjl2SlO=QKgG3!XU?m z8z#Y|BM_`t-wV-GrK0r*?u%~;rw2hWpu~ED(J%x;y0!w2WJS!a$!Xj*p2_q9L5Bae z&PZUXG)e+PTbRS8^E@ii1cM4{;G=2t{lmy8-2L;5U??zZ5eMsoUci?USsb_Q%8Zm-Op%`HhyJ6f4!DAauu2Vz%*b}K6(%D zGQUk#RW>}@^S2q+L!S+pe!ah^o5u#R#W8}O{6El{liY6=)Hm(aL0C$Go0N*CHWm-M z?S#UbM8G1yhx11-;QMIG$mCJ)FO2-@N$zm;Z^;P2_suoo7vg{TphJ$7n%;Ny#nAB6 zv#@f5nN_3rkJ3P*XW0uV!?^rkejY0*f)=dc(GnIMJz-|;K^R3+-H zXCJ79RVa|}=iW%0+buW6hZuI3oMY=xdO1y1z@^4*?LOo6m)RhzZbL@x4qsRIufJ}h zy0>?o*I#GpW*1$&k`r#c2B|&}8E>o8C?-)L#;1gtCPTf}WMsh1rjQWvPM0~(=8KGp z#|1J_BkeiRFq}S~SxrT1{Tch>Dinx&1$elQyLr4~WT5}RL)drG^cf*(zuYGi{5A>1 z2mV=U_8ptt7$EKoEMrQJ*l}s!&+oLv0&D8tRT$!4U7LXjn7;9U%<}Sw;N{~7*TM7U zo(dggbdSSq?#zMpO$GH(ogF6IYo&MyJjIX-K5=$UWU?IRfea!?1^8v#sk1=|1HQpj zF#Z$d8-{pR5(_+F+93UR%tOm>40xHRxI5{mCY6#ffQyecB_h+fAp^*fX8UyR%K-Of zv>35{V{!)_a0(C8;%a3!>s=6=gGlYZ;G&L|#PQ!?h_qA>WmtGZ*2G2akD$nmibGg( zK;>2mT%6omW^RMjDZO`x^!YmA1c3#*J9tJTfumTQKQm~1;3U0oOoQgUCu!I_vo!Lu zWVr-BvWhCqES)#f)_vm?_7-1kpu2X`J5_~%`&SExKhcTz){w!PwGfbPgP0tyhtqfU zp6r-^3<+pgu4YEncg~Ho9&5!jUhqritKP5tl?m|ogX#CXI+S!O=wuMAtn{eDg(7FwaM~Q=5i^;*;rena*EL zv2Yyf&l~D$quRQj+eIT@DA=c;noVpu9tsQ_#QwIe84#9v^dpZ zd42bLeiG?thm`~X|FA7|gP?G&as*B~o4jz7JbYdE0fvcF39^q?6 zd~kmzRT!zHK%lbuWrhmTLeK;$lITH>gB2;SP>SX1l&21a+Rq>mKr4R`nmz$lL|pcQ z9~^+Jrmc3@oTCX0;s6u38w2lA`+ zcqJ>H@X=3h>uxv^!CQzi>j5u9h#i^3isP-V>@W`gBB@ee+UH_NQZNVv=74F~*a1ni z-FL}(*CiO)ff?E$l?#^!1_tX(r4v>7BvFY{7{zRUcvy4OF#%3AQ)5 ze7r@?{<#}eI?ho3ET==IlEyb|j)nd2npt-fCnN^xLYc){SbhBB^OG{A8uN$HmOF!) z!iW@Ks;{oKD%%W--WDJ3cn^n)@ZXQmRYPpG9t`Qt0~Jna)R3r$V&JwIL^q8hV|O0U zUPT-5G$iC$CF_@^@VkN3Kp@-Klw+rx{;is(V?yOQbEjcZxlQ0`OF z%)eW9HU3918HT%jbG+`p?ay7Ov)pr+i)@d8525ZIr6dwWnW=a{Ofm35FTuUH&2}8m;lvF1<+aL~NT;sxqjc~*b&6oC zjE6}0iby+o!eE3tH~z#i7JJO$mwVsVBDC+OXk@XArxFqb@Ss9lQeSCZcP_K!{O|*9 z5crj>OtXALNSAw=rGtJ7oO7@ehVj&ZZQH<~o7-CX%nH`V1xU|ko|f_bVnZ< z!!EufX-^Mj&~2)WNVsMMcLz(nJrmUxlRKinfzmym8+^xW+A4=<8%7Bw*TK6pfU{-j z@`4Imf{L<*8|HthN~mTBn$tljJ9TT%ex1#WG|62TKV@2<{kLKyX*`ot?`r|Q)356e z3ePfs$)p>!eLxw?G5vHNBLMPQ1To8rg1SA&^l%KblupnQ@_%DOTl zko~M+Cev zHq_qX0y8ul|87-5p$YvG1POOcFROROi$E4E*fjZQ-gUUtJsZz0S@8z|aoctK9P7I2 zUXWe&;X82xpLoka^_kS$G>_Zx@3`q4?&x3|`f~GgC4S-e0XWo(0biK1HYw~R+L9KAEIp8^277up! zkv+d8-yo}hF*Hr?;2s1hnwM2_uL+9g<6~Z_&s10TSgXG?)YbH=T~4TGVK#I-882sG z$}&94zQ2oiuXeq|*(J-7;q9o?NU4KTuw^KMpqz(TD>t;ikIAw^1!}8%Oo%d|i)IY% zfwAy31VD3R9HV+2fXUG57O6Udr{$N6WQNk&67 ziqz@9MMiozI$(B83b?Xm6`77tvGr=Y;e#}FEf;1w4(UXH>(0a`bO6f;utkN=TSvNr zJfC<^i$1h6K+WEGOIC;^5jci|si}%v3{ zzcGv(4NT$^oR9<1-&Im@bP-S{rrP&{|IA{<@?nrc@T2Rv+=rn}TUsF-`I`P>aPo(7 zsd59_tUxN2_T1cQmA;+RTLLXK-#)1uUHSAJZzwW1^&-9@b>jXAjFJ5E%qpdh9n8oO z3}t6~g(Sc}f@gR76WTHQGRYG={sL(^RJJeeAUsi9X1Nb2Ct+^TYS8AhCq6eInAzHs9XY7dPRr?yXzw)?zsofT$SySJ{!IJY$s7kZ@k%W`Sa~qgt}c zOn4fhM2aOF^vx{pSG&InDWF>){R&NcHiwK?<48aVx&+U};|ID0s@Ezxy-<_@>ySx- zvt?69CFcYnas?=Pj)~^xt78m?1wqYyz4SQtnYKVFr-G!tX*tp-(Ml9gYg_iC_0W#W*z>1HkZv;hx!oyx? z5(w&ORzPVyo8FJfhblpk4+uy|qh2H8snR}#14U9lClrVpVvddM`MLKhPdzj~j0c>i zsF#&ZnGrbXJcTbKAh)Ylk-i6lU{~9l8sP<$nlqiNcy_dqOAB zK^U{~8D7#e_j=tkdxwd?c>oLDAix<9kfc*Gehez2BrgsUk_b zCYL`HFZ<_BwUg{K~B)O-gX-6xR zynk2T#dLtWVQ}F{A;9#|K?i zp*ZK>lc4Gs3^<8&h))X({KTQB_P={1$vTXuk5p08Z%u}K*E{kRo17Xxxc4F^X};pR zPiJ18g@L2$5=0&&Dx$;S(=+-+B~6CSY*hK(Pj0qu8qs3v%*{wnz)9Z*f_=4o!%V{W)vJzLsaynk_Eh~FCwfD4?=7-2tZ;J*WB65iX z8r#X5(bWm#PalNk%q=sVx`}`MdJCCPQfrK0UxZe@Si9lopipz|7f z$JT@@HwFxGYJvmo^vkx@4FGumbek&^W<2Zg86w2Knz5 z4TGw;HR^ED&S$o~Gqp+Sz)0+CTC0+V6ZANulnbL^w_=Z6=(T8<^vTaWzDY7hf|MW7 z`^;ZQ>g!1j+A=*$FRLqF|9<`B`1ft=1J{}qh$TIUf5+GtAEWE}WN}l4dqaJVw3~o_ zuiEo!)OLOX=u*3uGM;azwEjN!`#n!@I?b*?!b_GN^GkXD}` z8^|wlOY9%mtCHOz`(&ug{#2`HN$HvX*OU=iq*$e#VJ5@F+&Aj2Ff*o?joL(MJBZX) z+TkaRrwTM+=SuibQ}`ObPCVv>mVnblsW<%U#EGS`ZGFfE1>BwkR%Ogg?2VT3L=vs9 zJy`slJO^o=U3}#*jzCHuy+s zoWsM_*HU*5nclq}v%q)p=Cil64F*5KUa*W5&@)awxib`8X&Q6^4M~&bx0~ptJp~jd z?~7}~;P~+Z3bQ{-2n8i~=b7AK8A)bg0u^3!SkQfS83Lp)7VURkBcuLrP7?vb9vdA` zK7sl9*P1U84W6hy&EwP5%e^5s>|VL))AU{G?Ix>L3W)NEzI1A4;{l=X$DUbTZ#bec zfC&`-U@nFGTZr-+g7)wv4mgebWTC-%#%R;7T8Mdj%Uq_c@dRkvrME8I6XVL(&3IfH zr7~F?rrR%jGrA2nT83=O&VU^1_l950>!Qx@D~A^AkIiVHsVzE?iYs<5p~c8NrXB<2 z9>Gg__pFB3>{woGx}WT}&h-n?yTd9k0I#GqHXY1yl{1b)Sk2dv!UKsyl(ep$liB1n zsvxYuWoPj-bNg(h0JAav9^Th2nZm99y;98ba+o8JC3zlHE4lP}CybL(3yMZiAv>+5 zwFf)8uz>-C96eAo=BOWY>Yo3yW5d`K|A3c_EZLMipJzzs#K~O$NP*gKA9|^F0J1{k z_tsW7(Y>PtiyJC;+-PI{rt(zF^fT5w=ajfEthVX92E>#{n!}a)gOi&CC{=+jG9>ekgI}M%}ROH7he(3H$&G$%) zs5&U?jY+)}1!>5#~Oc54K~N zr2)wj0S+zCBIYs&coYB9Rg9?)PM#4if_;nG8~FR%%p-1l4!KjOk!RbvzTd?@$2P%; zV5j>}6k>C7y*@zMqdocGCD~fG!yMrt zlL#m|-JAzJjs(G6mRAo@n+Qk`fhXLHXg=0msw)jJMzQ(;lB#NhU;!00+aP*dT^&`8 zzR7T$u-s0SUP1(+NnARLa!J`r;ZUa#IxA`it;#rS$UFKU#LhEc@Z~-uUOpkXtI?=k zEq0P7I@8(hVK9J8@?UbV`EUS^lkUo0FIqQKEui z+-WLD+;o~+kY8XG_5#d2n-PWknCVm+!ctUvpFt+G5f9)G>&U?5eNto8)uO9slDlzf zOERfHsRf9uHJGdbF8mf)Q(v5H9|7zJ*uOOUpN53&O`f2V5}5*_^k37b7}!x3!z6um za)TClc1fn072e9kw1~PyQvc2|PNm4IzRO3d+cKCPaz;%}&sef+4NNwQY*)5okU}Kgz^0DQ@c(~EB&Ick2 zpvD&`^fjDgk*4Rzi8NxcPqXiHgkXq1C3EZmQp6v^a?zwtDSl$(c?NQKCE62c!i6rg zTqoGxhC+y>sWgC$lG6HEevF*jP<|^}lwg&B z5~>N?K^Qp6${=D-(=i+^YJDhO11owBu-d&y`c*JTh^2b-&O#zBWUx^WjM8Zw&6pMv zDunhcjj4NaI;qmY{PJ1V+7tn%KV)H=MKFTYL{N*9E?i*r{ssz_UwPL_x)A+$>Aky- z!Z7+D)0u%H9xTbE`c{}w1DwEF5l{nA7hM(dT*haqsTny4cmVOLAL2e0H@KzjhV?r( zJ|9tCqHLHDk(%;gJIY5EbBvD0qgmnEt%+n60crau5iw9SArYdzHU_jYbzan%G*1CIKySEX#C7n@!$=$!wvyB~`l!+=(`PMkCIR%)0=fVC- z=|{F|s3>8)2s&NcolUR1AEN??D9maXrh4MXT;#26(HW*Q+1*eOCylzkmgkQIau>`q zAt?+X;hr{BQ@Pm~$ZKLDqkBt_kpT7G<|#M_SM{jVCM4KCestdLncoLHk z49%a~GOS9S-MfQ?#K?*y`afoRE5*HD!JLPNXlVJwAhylF@g9mMARp(dkP3q`j`D*- zYGwNnAU6Mbha+)+eDaXFDcE8S%VX~fbsw{U3xQ@0HBi0Yb@2-W&)nVtP;q+&5O&Un zHGu`}0n++Yl&+DJKq7$dH%Ey4ayPIbrb?(VAf2_7pkYf2w+j6_*D;_QPA@f>L~Z2* zyX27QiKqRv=)_t3)Thp{&dIFm+-xcXh~_qS?C(K?S(64>_gG5N`|S@Ug76M}aqQy1 zq=1Fv)IRX|e+jXg!;^vM0E@=4|I!rRCyIhf0Dz{WDgg`MRG1nPt2xhOWy1v=(T!_l z447?DC#Zo9#-~a^5c}Pqz-_!)iZbDye$|8YyM&{UkFo&-rk<7Ye)dEB%GjOu&n7+$ z2ngfuz!?Z5;^MDw;{OS$1-MH})vpRc5m;x3RL=ZuWE+1e-I#>M6QK+=6#Tbn)(G?r zIB(QsSOtZY?@+*!0u~ft0wZItE9B20hwOzfC3alBuv^Ekx6_LS+H9X07Mu|7a~ZrS z5m|N?0~O3ax%U?29ZJN@hRFa+CH#@(HX$!GKMEoMgp*WKkjRt_8{(xT6$9LQLmCs( z@??@4+5LLbq~-m+kj-E>U70m39)%nL(aqp7wZa=%`;Se0gVrcM{uyY$Kghsc*=Rxn zhCX1ouUn;@3h7!ICj{ocCdag_@A}LIZtPEA^uG62ge_={fV3ah6^nzDK?$}C2$7d! z_ar3r%b6Q$z5w1Q>TV6dZxbmq9S&UJ%8Y~waypS zXzzp#&-n#y@_{<5VN(KbA(}?W!@t;d*!*vutbsPBeOHGE3uB3UY`H3k7l214S{;We zr+PkWO*h{5S-;Wl>5y4kKvUQTA6~%{g8`<%s{ohue$vH1nQ09_9rfyNml5ery1sD? zg4%1n*&Og7cP~q3c&Oz*$3y~{y5#jTwP@g{;f&tY@NtlSsK5SWjphGx67ah-w0$JT zW?dgLo>81prl+jsBr9f1G#bl-w-oeFxdck}wrre#H~@ z0#>nXdncFe$0=8J+KW9h6fbLL%6sM$EiN+mFByzXlP3r(46eEPc&Dk>c z%B_B8*f0_wr+G#lG5*v59dJmkel4Wh$!>n&_hVR{)RcY5^kS|0KOW%y89?|Uznv`h zJx#^=asChVi}5FKwz5wr-i()Z!S7DRIXVW$XYa2`4{(Ib+n7{L3&zf+Na8Vxnbj6W zov#jnZh1r@aBQqvSl&ttEbyARi2>hIYavpop+luiq7xBV<5Hd{M4p!(2u}?QKk|<( zSf61qa>LOn+>Ye;KG-)xa4g9!?l1fM=ngRAHm&=RBw(RWVtV z0LuImiWZQsR}M(1&&&?%Dm`(bvarjjl5@oyxVpqp@BC8KARZwm{Gbl6KfIJ)B_rZ;Ce5DB+wBKmY@yx3cpJ&^sR8QKYEJ)qA-WKFLG6kSh?ryTO7;7M<`ET!+c&MO@2mLV6v~1WF@;7I z8d~+kI6@*wko>kg@t+?5|jBe*P`7I1Ls{bOQD*<%x}WC1Nh`}k)hW2+3%dsi4oT_{P1ot!R;BEuF&S~&N+yA%``gBC`L1e3jXJ7@}l z`TM9Im3s^=4O{HhWwr^FvP3yJ?coFFuh}p`42*Az4#{Gc-Ahu!xYY9*rI zhS_B#ziHFrS65VgI;WS6;RPxAG;}3*k~x;NWHBeKzhiV^z40<2*cWXQ(wdT2y3NkN zqHIcMY6k(gtHoT2#nI;_3|Q_$L2>FUk#=-X zx6lF2)a27J6Hyv~I!i!e2f8`5=wvqrU__8d)SR2OMfmto#XsgI;PVz@ui@a-e7!lU z<^Uz$?4HKCXr*d<0J?O}*_e~pc7n#^Y7!c1uL}nMjOGn9OoS>H$V7wjNLqHh zz%5L#qa#$a&JYDa5#^@{sB?BmV)CG)tQqiws83Qr zhbrX(8e^B9xVdPJB@K%A!VD>FQcNPUp#*bG5$g}qFeBf%y@5L)fKueY5~vPgh(bE@ z9_8qJ_d=42XoCJNOA&@bTB)qi;_MK_gem!imP2iWMm$EoGfcOA$4+MT-w-Pocp!_x zihu%ZwcbDmJlZ&v(kZlWy3dvRq9R%BG2a`}zCk3#o znGYWERBh)&$fQdnu}Go5CEW}DZ|G71X@FDny7{l!D!}uRQh1>O9dXjv8M_i+F%G!0 zA-hTQ2hx^cp&=BpoW}G3k}(*$-y6QW!!73OQ)U#FHpU33p@5FUiMa(Cc7VlPzp z#z~BU*khcy;~*fl_HRNCZ0j*`tYNWsS`y@5Qqk{_km7MdZ}<**e3&4=hD&fOCT#vt zAmW?~Gobe6QRIOKRcWdid`SS1u`FHZR?4e%f{k*BsK(AkhXPS{+GQy&iS^eGc8Jty zcdT}K8EV+NJKF+$ZQITrrE-=59nP?K+t|{2n%9nF?j09cSzdq^DWK7Kb$OI-yTT)` zI{=a{PdJnS+1FGYN6>u5)JYszvlo5yLnG^C*%lIeGAUjPv8gL*3rX?sb8c6Z8->H7 z81Q9n>#+PzCZX;~uuS%HC-qTXcgH%j>fe~R0h+5?MnPmi%nm+HnN*lJYk zlXKatfVY9gVEuM?7$EjY{i|;BTfew$A%@1G(D}Cx7^BU8SUH;|u4*>kDf%5l!~*_3 zPDJ1di-+vTwCcG4<*Qa^)_%ordTn%);de1IHCqL0G8h|UFKqm-IbP}uDS{Z}LBZkGxgOpe}CY)=2a zsU$_IOP~P3pi7Z-Wc`wf8Bfj1@Vv0a2}X_hTdPDW_R&Wp=jvHGHij%ra5B^7+>-!m zuxL3HM%hKxehP5y6lWt!>n35Ypmr+fipFCuYA06J)TF#6$6VppT*kQ_TP``)TQ5Wy z!fUX@2U9^)w`dqgfFyu_M0CS*e7^J4KAtx9>ML|;MQgxR0}PfA~Im4O4e~N3<_QC;i(i>x_Lk+$tTMn}OTjYdZ^h@!apA zZnSeQlZwG|93HO$CKiQHMba&hio#Lgvm_!_`B8O$r&hANJKF;Xa<7T3+~6n(zM-Z~ zja00w60jMWA^=p3!Mu&BmXOS)6p0g?iazb$PO<1HF}9F2bNTa2qMvl?_7lN;4P^6QhLc@NXgy0=X@QD$n2Cs1^)Mf;agLqNMdHLL?7BuSVBF1u*f+9sLa+#Sd zfYb5CNF>E03!dMVo2V5Fd4}ugj;Tm+@oIVdinVe2_DMk0(QXGuh-k9+gLT6Wp48&n{oxh-Q3MLPeYmvEl zazaFHw;}};_ngUC5*?V7{6kL81)oZhbUci}%*|^wslUSVm_gXsF8{cI$x{jnH$`Yp zAc^M3P#W%?k&>9Ha3P0hj0YObfvB)G6o(fm{&a2K7&7AZMi3QSu=gHEl`+X1h$m~5 z_Ii(AS*P{+e*NA1{@r?ggJ2H=sMfTy!&`~}{Hwkx7>L^CWb<1h%Fb# zs3@>0uG3JRekJLKNX~N$*m26d$2b^+1&mvc^_+c{)-YHcD&U}+>Q7Qcu+qNEwKva zR9D~F+v$02-SS+3U+jH1`W0o+T;SbRqxI_HB9`;Pw6X=mz*Y*s%MGY7Oc^l;>PAh_+_9@#K5I>or}8-B;U* zAWudUNg~m>#B$;|{K2l)qoIbM3>iHLfdf7-))0vKquIo%sjA#0K9;xcOeB~=LG-n7 zj-2ndDMX{Z`el-W8`0WgUc;K8ePoBvfeLNcl*s-T#SZUp%_|WBlDpO7D~EUkl6W?% z!b;^0j!ia7w37CK#(=OQT6N8)h3WFiEUF~q=^QSi)s|^vcY^E*Fr{1|l(NVUU=lUO z#beUgUIsk(vJqM&h7()-U!Wy+$Z=xwcXwELQf2ZCT?`e&3}H~SXmc9JGOXvhD~5u3 z3W#DTsxk2fTG*2Q&6rS|hNeG-2cmF`FJg-_Bs_==g7d?7{!U#p+FYE=JVU^FAh zI63wt2$h~jvUkKOFtgk+WUzktFUP(gPHH<;ruc^^M5fXK#)cA$=mPH)F-fzZ!pZx? zSWW&8exyO$Faw}$bT9Y7P)1X?%i@$FBJAtkCwSiDqUNKpXW6yo)@Xip0LK^2f6Z#FSY${cS_*EoiO(@zeDXc3>Fr&+_mV z7WCS2^UxGjFmRJnnN)KZPRX(;)gyT`2c@Ws-f19j9Q!0FQEOj`RGxQ~Od$lNF_k|X)Q;fBCG+%9n5zijKHE{QjSpOfnuUSAC9P&{DKEfEL( z0IU^`n_5WCOd=-_9)aBM*@A2yv`#D8L*UQ%FmAspe0>g4Xpf`0NGrZzMMYtK5QiG`43}^^DFm>n5aus1scrvw z0C#W$q^8UB=;RWMFdOesz$;lHI}-WFa~`uP>Hu;a#|Uh0kawPH&YSmA(*eQI)4*@@vXwV9fHWN}?Wu?jh#M9g7F`__iNR_gPQSB8C??w< zu2IYTa8nuPe34}>+lb>7V+V-+^I)_g@B?FEBw`lFeKfO$>KvpM%N)yF#39VtMiZ8C z+6NDbIuJRkBLyEB0Yf?RDF~Z1N5#2F_X`c1Q$a4*IGjD>yE?Bj;^R^_8Q4@%Knb2E zwEyEe*8kW#$0p5!2HTcx+qP}nwr%?<+jezzsmr!)+g-Np>G#f!nE5cj;Y3E9%(eGk z#n0kWiOUnNT}E^yNQdC@F=rEMP2@!Gw{TmQ$M#W)JsAmbe4{gWF2Q0GSfN5a-s8Wi zW6ZGH{jk?=Hdx?TL__d!u;=sC+dsTOcmVR;ylp_}G$OgP2 z6Wu@ao77xwoqVg`#ln5~UC4R+3+de%_(I{Ukom1qk+tG}iYGA|KpR=8q=6z^W430@ z)M_*p2}>0DH0a06QQ6%v!K23H3j?Q(LPLzi^)O^(Ncq|fr&+NlnCTxLZH)ikqJ0TJ z&&P4)*SN96d8G_w9A13ult_+pjOM3?cKaia4ef^|*VJR8zC%L>*343XG5tVf57P9c zCf~?Wk7f1i1czY(z+zk-GCw#sc?zq1KKnagrrlv@5!I&2i*9h3#aKyZjb;rbRw@{C zVqLYti24}CR0~FCJ`q9Ge1H8NnpYJMLuwd;Y?CkV(2$OPDl`SJCcBT$N&*QwSccjf z&dj!95P$A_Ruy!}rem0=NxsmSuQ^Tuwy3<10|OQ`B{&)cpoGFlfBQYtKjh1BL_IS@ zdebfIuEMNVQ0qpPimypcUI!kC>fV%7(ZekoH0%%wk1NxR%!!y9e^O!A=K&!ZNDyu$ zz4jE$F+$|lZ>2pady3P51qJe-8^8?iorMWz;Sn8XhD~Bq~jfLq*D=u^0Upd}dZ3gyYKe<-1LFDGQ!(psfKL zqs=H~QAE`7z+Z@-O;R-!;!iA^^v1pI1UUF?!T-paTohjn#^(ju^ksCpR0P8#|) zPTE`>FmP<4CwB@uET(V8SVzMcZ>&z$dMKsD?4E&H$8%~H+y0B}_rO3(KkQa<{1X5a z997dd`na;jw|5&4mMDX`|EuvFq;LT|ocP#`sGeyGYQaPvSeEQe_R#%l0`j-;NT1## z&_?ng#=Z@x<2Z2;!#dC|v9bz*9m$yhHaqoq1K|Lu#YZFJ9!zbkWn^z27(5 zxmbp0i%nkFs4t0Y@ZLsbyEqqhCnNCOD7F-~E+MBzkS^Rt#>ayI(q-jWo~oGYvI3$O zz&i`B7&+G@KB-;m{i`CzIPB|XPSi&Ry)DjTrkx1HOk&V+AWw-24P4`&TRg?!43+~Ls z6e)Qi_tA+yJqD(nduhdZaU9E2hH}yn5V}>(sSl{Yb)vX^TeD#1Ht#cAz;?(&`IPne zS08_f0=>tpFKfz^m^7>SC0!<5dRZx)1RcY)P@;Onx|L@4MFY zo=KX^Cy}DcqOGj8jS5FFnx8tx>=x0gBjJ_Uv1Q^8t#7#z6`vWkqY;@=KGR zLzYWBb0)Mar8nT|v&(alGr_8O{rl|X^4h^?Y2I%qon@ZcJo}jYx!UMSUIpR9{;}F?(ff*rh4p$Q zdgX|4^L2N=A3Y0ym^qN63Fxc=%(i`hfEt8U9?i zeXBI|_kj3GPzj*8Y;E%c_-&k=@G-YGUL3I-2y&WK(epP)%@`Y!XimF_q`7o?TO58T z@Bnzb?{5UB_cLm}&m0a!cl|S)AB~@{O9b7w^#GDTYaIMX>j>7*liA^_TiH2=Gug%~ z9tTzHgg;Mo#ur|Kz5{_-+afcYTTc0$2%AX3UG++iuPN8KPm@ z0arW}wx4<}`sf_MO@m^sf&ar*>kma}mqAxn-rg<7ZX#l+b9RZJB@@-F#QvaNnq_X? zeL`OTlC_-U`#iyJk{X_JqIRh4ho3T?*Vt)rU5XkKMu@Y@IYiQ&c{nNo044rv~#|NM)dI49@06 zoaQtd4*(<0(8u_+*(N3W7RqGm#otSQ!Tl!IzFlOyztdord8fd!D}#=FAXXTEX}rnIXfO;fyU(sWvE+)b<-)PSpmDl+E|2K84S2YdS< zoY)L2#7ZQ^;6*(E3?lu1m}TSP;}itU4C7E7R~&Jhan8;ht-sF#-x3IrP* zetsp+0~`fkW{(j%l1}iZaC=+-=5mzPu}*i==c@R`to!;=;|MZDP7QhNW>G}$;HH5j zuI(TgG_2GarnlLRMR}L}=Pm{l^btg#ftr&dP*4WYJ!cXpLfX%@3gWPvrQbrD%!(q@ zLA;h9%2rBzt3625e!{B&Wn*!rf(SuIZh7`6*%`uDHUaFPahC`r!FDJb&7Aq&3S3z2 zpDxFI%3JpkIr84w?D^lnJ0z@9>`fp}3-V-y5Ifc(U1w3!$}URLDyf@703%hdLHZfT z;(RY4&Xwowi5&%-gqkrh7^WOis;|GOSniYqI8gum*!8}_Msk)K9r|fL1+)%Qc(K=Gk&)2Ab66<9dTkbtOXN#&I-M^aHxmBkXGVDEHT+YX;z{e^Kzc% zWTl*cThDt8Rg|#r6BBrx-kkgboLT$<%EsLh`K}?+^6=*2IkU?&8leM-Xi*&BCm7Ly z8<+T$We)_0#Z*neo;U+Obi;uRN*)b9BKh$P*mmhUzYy5`@4V8Zg#~>x4o*oa2`(Ad z`6He@&BzjXrw$1`5|AV=3gSbvvGn3=KJR)dBI@idtvodO7jab34$$Z>tJqJ=?)w#_ z-`sm-LjRh)`R+(TlDP*Tq;z$JU(slF)a^HJfZmZoR!DoZ1f<8brFCg)sED!=-|S*bw9x8pAY z)rk=~y@6g6R&vex>&@Grsnjk2+r%5cwZG)fP^d|MB1D8af7##mW|Wh9rX{4n^ATXv@;~fos+3 z38WVugXkUS%vMx70bwR(3hmv9f$Q}Cl*4x1l0BU=^i;Dl5jz}cLVUIW_IwI%^{l*) zC;TUB-u9{EnbS?`>$}O|yUfX-OUo~+^d(U%kgV~*8E0)3=Nryb{Y}k z`ww_igHpH%H!`{sQy*q&r&TT(F9%t%q0on}#ud%R8~wB^6*He?VMJq?#%k8clX`1x z9^?E6ck7WVH`ry_D$?@MMdgtpTS`{vgjVG^JI->V2miTC+9c>0JcQF``xFK%fBGYT z{RDpwWQD#(^?W&Ccg<@J{q7pUCUDc>>3%u~&~E0FyixD#Y4$0O1YAWWD{Xy3k!et? z_G(0Nmh@oeQqB?+aGmr*@D5!V@G8h=N_7EkiTea9b47%Zn}T(9byXO!JP>YCKH=$J zf{wboXg|S$ z*Mj2y(<$n#@ol!!lzm8fLXN^d-AQ-$gk@eP?|Zaf&(ijg=V`y{5#alQq0fB)6~lI8 zaHzj3n}`h<6ItNh<;~3(nf34!5aju0`qx&%7cJ&bk=`?>-PgxpJvr%Kcrw%Pk={{Y z44MGtc{4xi!ZS9wx4wT_O(j!)TaPC*57F*sg$6d*bWS$(KRl;;sHx?%D3it+k0uMw zt_tbfOzfe7&#s=~4muYgYnV^xVg6bhSjt!SC659yNG@+(Dow95F9`<}VI*->t;qO} z9||^XaIQCLv4>VCoZ)l{cv<7d8}AQ#ks5h$6pd4 z(_%nJnS<0veB;G9i(hcSSoX$82xk(@LV>JTVvcjBRO_NfIl5!kw+^B{LlS!XV!}*& zy&F|_a6q`*^@fcM$oxXnYdj6wKG001LD!Rq2oWLn6N*o^CZ_c8RzIyg%%k$EV#p_% zVlPimX2dISiDerx)YdSP%tqs5p)~@iG^oD@Rw5Osl&IHw1>F-Hb`2(I3oMR7~25(u?5AN`7ac@R22oZ@~}AB)&+gF{sw0j1x(7PX@vi zqkWDv*hxe>{w4Ti1$w8_DA_wcKX>)=|Gp@DK*ZcTdLzVi0#WEEY}2|=LK6WXJ~U`q z8Zrp#8+>`6$z58zJdU{yx?|~tG(Ae$bq@#BRc!{CU3c zj2c4){nP*yqnQ4I8?c4^)M#mPGZ3u^goURTmNp;sf}6)Hg4Bu< z2^RDbBY@?O9wNbnIkKue$teXIIY?si!37sYbz6HZbHQ1!Y5kF23OjVSShSF8O|gKk zjDA_F3!_1w{`bt8H4S=TTY6IJu=uMB-7OFWK+Jp+`F`;=n#0sI(PIesxe}cBm=ig~ z_U-v0RNKU9Wv|11ghQhk52r##(Po2ejRR9XPR#e@t*{$|IB6@<(X8#5;5hf z+VIs1Zt({VXV#G{9`M6AIoz35jvMA>a@?es|4#eGY^J~al0)fx_NTw-qqGZGO?Aed z=LV_+QXE6~o6HPe^Nf37`(eoiQY7Xzjral}TsRx@$ zjLhy==J%S3g^PqP6C?i48C5+VCr)J=m?eK9R8#cX2B1l-@*+Sp$Df< zwy0N0ifW_d6U{t36#a=suWXf5u?3e^tv>lFIR`$$6N80_C!fwXtfq*!77McLWOYzI zPf>IANEgG&!`B4pwQM(8H_=+zy~c|-z#-gZw(l%iez(X5+l(-_EMAJ;up3KOy6V_W zyQUGhNsRM4$3iHAb-iz?*DO)o44uyso~RCsuE3d3Dko(YmhLn1-m^)|9P5zmckbGW zuQj1Dq~fowZ~YL{eO(O!0P8EuI_@(e^_*zN`q-5G@!f64#We;2G^^VyZZaW#=NswIe zT-?D3-QMwCpd{o2yq+T0?%sHG-s}u4y_*v^n+V^aF(pGw|4G>3?rTXSELAP#nAXm8 z5iCTO+^{f=Ag~-!;GGp|K(>7qR(GW~Ag;0QEPQtP3TH@=Zy2l4 zyEhNKf&Dul(0b%;We?vB0!1CsSD%o;s0c&5xooLWX$21_sD8dsPZ$WvrHB!c3Jh^A z2@E!|cZb~k?QyAQK0m!AcmHqORVM4D8ap97M-gifMn_yTi+6Iu%H#g;EV3Vyl4~J6 zweh2{A$aKnZR#;pW++62->ITh6-`~Tq6H_Zv*}-1Ks8Kmsg!miUlu1X*_f`#x;o#v z)q{D67KxM;4A{RaA=#gmQ-s`+(^Ld=QQQzr^)~tT*#)3@wH&FExr@|_>P`g#^-PJ= zwBd1}41-~xu%!3Ex7KI}_!OyBa~7CDO15Gj7|;9aQiL93?|jNTH1V>aPGhBvo%&;z z8vQ|6fGl>Z<~OZ4C!vnMXXfyD_u@B?z*hrjPg?^sgUR2H(&s02(5sE=XjL_G)`}g+ zAF%3)s!q4Ue#av{TSr`JkFRUPemaOmIJgoY!Idm%mQG<5qA>)HzJPl8B7)W`f)qRY zoF%Q4C%|ku1-Y`BuGy>=KWY__u9|z}`%<+o;3Qvl`%v&_@@e@g1~Z1K?3#xxyXzz2 ziLYiCuo`fv$72~KCH8QPc=g$FW%YJG9OLf!SjG9%VbI*jamWeC46FHI?5c3&Kg#ol=|K`9QG? zkQ;Epc9@+_x9{Z1@q4)1-d&Z|sdm`iw&OWZ$Q_4eU|d^YO=%1|o(^KN6_MOlP-L9% zC>D{`A=~H@*22#^(IMZX!;DUvA$B#*i`){MvAfqusl{LEbKG%X9z9}dA)GZ35L)YA z(#|w(V7U9KPuhxv^(RJrpr(f=5B-hQXYzsKxVw38jr+DGPGKi0v(L9 zoN=)0uq%dkiad{Gnh*>x5OOz!{|}mmr{>qk=)9Noii9$W5>AR)!#WgkJAi)XPEH2= zp5}6-zEi)LvB?B3LvB4$s8H?{sjeqPF{`hVXFRbnZ9ruQj5*v}iy?cmiJo|IjjnXH z@#*8(iix(SkXo~-bW3&*c@#z3%VeksND;%l7!l7{L!35z?VR8WUxqH{k+`eu41Zpc z71%@se{$5=HDK?{sYL*f8vxz)Kg5>hkHS2rts8NniP_&DD}s=lcN+h$U(vAaN{Yk! z$+`d3V5xdtX2KNtWaikZ2_0BxO^S5Tp}EvW1f>7C?U9=gI)=9g9(BdO5ZlKJbCi+V z7&Yjx{{?T=itV_~D?cgH+vO|G8-Gn2RMP=8POp)WCLv9tsx1ZY&J@s-(S^CWg;-XO1;5kD3GYf^N?s{HSC1!*{CJ4 z>kgSgYEwy2ue5(wF0hB5t_LhSjSN=40l(DsAk0MZgAJbJOcer=gBF(VMQkw#OnAU;2kuJy4c7xAU{pS6w7R(!Y zgc+hchucQS{Bm={$l!!s)DXtmg8<-w(yRWPMq47D(xl{40j$;Wvju?KtqFou zsH2+Fc0#z4nIY6M*MsORhMg6(45n0wt3tNN3XQwF2a8)V)Q9*(xYlOqGQ+*a@AWqnL>#WEgnEy(`-IbN~ zhR_WCqrlHivg1a(}tWMkDHuN#X>ex1W#wZ1ioehJY1vzlWs-?cUlvGYV=v# z6s0gGK7~tbv-wegEHejvFziRLK+V=)p>{ENouw7k>D(2ra|N$2oOuI)P%xY-TR9J4qB^mFeY%z-!%B;PLM(}(3z&>?`%wbVqS+({LSKC z@IhVq|I8Mr02Kb|Kf92^Upcc0q!^x6ZfiUs%(JnSM~x(22Mf5$=yb8x={y&G^5{Ym zLuJTC>@ypWduZTpV|D%)RN+tkJow<6e;k<6k7CHm`?VMb1<6_3t?VI~Y8`5=JBn-a z^8Q}skDo%C+qv!Zi^lv1lK1mONima$Ohk12aX0Yki(0 z051cC2lF4ccT=Zf)qN-|P5v1XBdUrAdXbcD@e3xhQ^!Ncn0-KYA9==rh=06Xrm^n+ zAK?T1Lwq>La*$fGtVP!9No88cJgX5n)sFb2Wc_7_P2zg+A>0!6wAA9JvfD_QHBbgb z`dh#&7S4+ST;`Q#Ru9VYTYg}a%r$o5tN$h`6uigKAmR+@6a~oYs)sswa=+01_4azX z)eg>2FIRKUDk_@f~%X%mn{fiI&?LtyMuWJ!~PbS@@_51DVm}`#Ii~)Sb-{&p%;nzliUCj zi@C#G5*$|umY>l3jSv5wqiAfiQ}@RyO6+Q6hW&yFNad9x80cH1gRlpEvqcEnP@8tV zwMs|FY`)XBwGn%FG|^U@0g&`c?s=2@{@;C)7~oX&QjD@A+A1LGvHvh*!#oxyYUpp1 z-#gV@n<3h=1JvbucW_rxb;`aZcxnJ8xo&C-Cj9ijv!+TYS$OT!RM35jdUdmfp>E}? zWoK3Pf>jASr05}V#1brGQcg8h6Esl6DAjS?S;@v5=pwe-<=`U80zmw=`NyI6>Q76? zqYZB35S86vNB2gk(w>>X?7M;-l|?$0Blf4^4`Tp$%Cn-S z;*zEv)^wZcI=s!SzMz5hod#YeQHKDY!8 zS|?FGPL}>|D3h)#co48&0;+;mWRW*YCi;wio3kWJNTIvNPJM593)-4y%3ll;NR~ts z$jS1v&?}!5KDjVN;x;G^YH6oYV?~2V%yAtTYx)qL!kM?^w3Mu_lsTOmOTCRH4*Z3_ z-&x^TH1A5G`eCBUY9CWxBWjX{lKFVp86h;%C~FEt=gUe9c(Yj;2cU)(d!glnTettQ zVvi_taoSw(`0du%pdC? zN+U(hKrcy^Q-;Xz21p%Z*`&4Vj10MK+c|T)5jq-yDFVYXWvC>n2eK%p+Td=ro#vFuk)NFy?xi_f@+s4)(lOGP(5C6l zQdnVpRHc;70eqBd)W{@7yzA}5ZBMg{ZFcu})qiq2gng_8{+-23232I~6<@`z9F(-$ zSu_zca)L2d>xSB#rElg15099v0{KO1KDWUOFvNClVhLsB^ma{>`#|-)T%`$V;>@|B zTp1_Ne{C&9@jB~8IMCu67NI)0A>y2HnCnJI zn1eWQa+F&Qm{J-tLFG`O5#s7{djhPEvZ2u!1;giSn1k6VW)dSrU>e+v+Cp@~*Vd%O z3e_ii-Z#;^F&9yYqAO3PkeX@uqOWsL@l-m(Iw1<(!swJyS{M#vzc)Vma(#2l@#!72 z#kvguI^j(`2+vv94=muw6Hu--%+qxgT2@aGGV#P-sEvmV?^uLE-O!WP2D6G@*QZ&L zG`NhF?xOUFPvu*5cFtj|kVR_H;wUIUH147wm9*!P=^2&Jp!C@V3hRzI9gZWCJDiPQ zJ>{wW*0%6k($s};joBcuVdho;ww`peR~pR$DK#B088_C;2DRrTJUBAkTzf8up}tVX zb}3SGvJ_Tc$isFj0#4ZK>a#&~kmm%)31}x*ipK37aQG~bV8cTZ1&c?fgiGud_g!J} zo_gRBN;=YX?aA%&e+t5QQ9g)j2hVLf4A>n+x<~x|Eyhc-K(e0R~SaSzQU#+{OiAUN7M1V;f=CP(h?M zPl!^>Wel2--d|2oZ>|16kDK2wgx83~xSkze&5blI zt?c=JH(z4*{omYl=P15xLr-=W=BAF+{wFcmWRl`=g#xKmP^tf`PBTa(>!8 z#~j6r*C+nORz|=G)iq(m=W>K|!l7l{!S~8htw6jlnPS30y!*xW8pq<>`k^BrN9{4E z5}lhOAJu^`f%y_#~?+# zGkXj1zz$FN3s6>?wAwCskG--!^VftwCy!~KwDwgu`}@YX;ZssRIE7|zmlRXLuqfaw z_4Ogo{XIf90&?;7h{gCMySJRoYIC{eLX@`S;|pAL5kOT!DAIqPd69wN#g8oYdY52~ zjv!>`yQBAA_=C!$uLAfwj(DhL>Daz;@9m`Fx$v$P@Etkn`YtP^4;G#TFg0bjvXAs1 zaS?QfahKF;LgM>pBYpZZCgfb}cV~I_up-bsejsKT?!5RZbJ7%`Vkqz$E*P#(CsR-w z_#2T)w$jBD`3CD=W9*b+{y?@CN+Q_(%a^P6%M1!3b>C=KAz8sZCBo=r7YCDgJa&PDk$Ez}o26p?7JRj`_t42Tcf zEV|WkHgDQO0U@=QnKPbKaKAEVOW8g1+X(8FxC}|;w?3<5NS9qsG9u*CmVZBO1HD#I zyA?xZBsh=5mX_gBlZ~5+vlPEKd{6S1g7^qDT__7VYu;0aZ^NqkJC@p~b%}9~NFKyP z_gk)HgflCzacWO_=JjRJw<@C&0-%*<8&g3;gPza0ck&{>m^GEjM{EDvK8+8kxu-EVDTr&19W*u>{70`dh6Y0d& z-PekXm;dY|ZN`)N`CP>1#wU2(liy;n7eaX8BrN3Expbp&$pA2@-UuQ@=3 zd44tDN<$ToMDBFfP`)W3ASpOeENJKzM1B*mKSPtBWH;ketG*D&9H$$&d;2YTC3ZDe zc=77$sDQ;PmPfD6m&*N@Ua7!0j;}ywVz}QVXIg!m(8+gRG4g=y5irk&fa<5y<(+tY zh<6GSZzXj4;km@zy$`_?j49SF6s>v{!?aj7Koo^iI^i91(38+>AEvt~L zEEmRtl}a>O$W(LB&lx4-bPfT%jzn>_aG#=!=Tqa(E(F;UcRzpb@HsaUs}Dv@@f>?@ zOhozHB=dmxTpGrc2yi1s&3pSBYSp+~4pWs`^DjpI$>vj(8{dxwP!!0T6C1Hfdt@Hd zby*et$%)vV=r^j@&6hVj7l)CpRWj{-WB>+Ak>P%%QR8i1r_6EYA7F5B6%Yi+sziwN ze#fB&N>tn+3U^2PK8ED&xw6FRI!ueHX$%IqKmUvhz-27?jDS4(XnIqjC2cLw4%ib` zrfA+gNJk7h%LG!&(@-8F%+t^={W2f{1n-}@+YF{*XVduSui_O*{NwE3w`?*aS7obBI9L1D`zihV)?|c9Yab=3{&Ma_ zoZc*Q8b0I@G=K@y6fWo3d_Ld>P~tMInDzK+O0?3;X=?#)?a=-!LoT>6nGxNxEV;=z zXv(f-LgB+RaIWVDxoHe?HXXuQbpNLnRSOl04H z;5}p-dBAxJk9UeuWlEqIo?5w8C1`lSf|v#nk7{}r^I028+T}Q}%rOw14GpCdWc#~J zF_P#g&Mw45jWF4`pig!96DFJcSjh^H0S0U=ez|=)Dvd~7wR@L7HPnr+-EwPS?wJ$> z``)icz3I#_35o(7v1-yy#Q{sHXpU5MotGLvC4k4TP^92OcrLqvXmE88gcC2-1Ia&h zqjE+<5H>0tsCd!V#C4f}D7JX0Ez4T%hkvDN3B}Dhqb;D7<7w%G$Q1y!=Lx-l?~#te%p=H#=?<}JSRu}2V=4#kySW9TR(8VSGRB~c3N zX#o|7m`YtvVP@7Z^$DXCNm?1%9<(afFWF{IGNWd;AubB;LHIO({-|1HlYyjGSi@`7 zUeGPCJC2HkS5+d5S+G1U%PF7B0LtVK(JEi|n-Px?WIG6b(+y37WM`U$dp<}W_Avbt z?B=#3t(8{VARN2&7Sxvx%S`MJ&g)6)q5;D>_l8{(L(oFuE8Sd?-V)Ar0xy8O0jlrI zsxEF#l&L9To0Y4d33-nK8b4#g98}9G87h}7PLT^md9!+!=|VPk!;QO}-Mu)c z=d>HrtDl}k4IP;NH>vc5n1lP=ggbIsKMzGSdi&oeZA1aKVdT85_hcXTb;FZPuYmNC zEJ1(2))9x;lc9ao&+nOG+jE!ql`?V(ykE_tx!1XWMdn$gG+fyR$L^AB8bybiOG1#O zwO}T~Pbngk!tx8LqZn5&G0OTV??-$rf-aL)5UweC6J5E(=T;`wZdaM4R-6uM^-kqW zGw15=#l{bQjY?`-dxUBASW&z*fC1F3^+VY6Z@Nw$F)TQ+ ztd^%`7}#DU0{U{37SvU}G;g!>`kEncviga6@iQMv0=k6EwPC0hv>0!8UzU|0{d8A! zTdL(ijg}28P{&*y>rY+B!uzZaie$;ZYqIi)ci9Wl{2Lo5JaAiS6`C9C_y9XUwenTy zFPo9waUJ|HqU$3=B_-empj)DBB((?*wv-+6(%m46`@V*#Qb%l_ygAE{whW298VrUsTRmFC~EwgNRD^JJ{8kD%FOGX$a2c zM3RfL^FhBVVHRemr~#`qh`kv$SFy$oy{hA61aUjNzx)XtqJ3K9l?z>J+CT;zq&4Y` zR;!GxJB6ZVzvavAAdY{O=bPrstLBvQqN9yvPMi`t{z%8F@R&WIyD4cdR?F~P4t@Bo zGoYLPF)E`#(5N_7I#!daVlY3i8mM91YesQ1-zC)oWv^NYEC%GJ!jXJD9o_k4IbSl-J^LMnKMeK;)7;dY} zCoD1{b)RUyDp{kJr?n9ESCEUlVMgIb*0eRLA5qMV3^9gPj^C)njI66lznB% zNJ!zte*uwXG2*Xu&TqCZimGEVt-U!p#XN-rOZOxMIO%hm{~BR`KnKiT6`A{ zqq1KC*)R%;QV~H3=SDMPt?t_vS#)WtCGk!jYW_75eVMtJ1r+p@a=f;_BWq1ARMj@F zvDgcdKW?FGANaIY47*42F5yP090VsoE=+IHGemkpBd=F7_$ozSMl^76=|_fje80eC zNB}Ev3sg@Z^}Uq4*Y-zWb>$2mb&I?QofISZFeB1n}c6r^>u~1ODApCVqn4H%eSCIh}3@W>0&<6j2o4cbYcZYrJ zORnVE`5`_LD8l47rmU@U^UkmL^K*KV9-JQN>t#L-2>08_hp?|s&4t8ck%i_1F#xLr z+*Y|*e6AtF8nSKu-_W_HdI>6TeSP#&#v*N#A&`eWGcHu*SXAsJ_z0%h1~6$chr&J0 z8SnLW`4%@eYZ|%Aj8PF!N_XmFvgGiu;f5OWY$E@4sE*EZc9q{p2>ry>DifGP@u8^4 zxHeD08gCGDR~|w724=l0P?&HtvjE%Vj-9LwjMlKYAbgd5hgFJ&@V;Bz0u8D zCty3!)P6RFlBg>2_7Tb>2+AC;u_;)2an+?2D;kMu>9g&NNanUUp|M2UY0YbnnTTnS z3noIX1aASbmF`pTlEJfI-@TBt&F0g5jr2TQLbyA^mECtL>Lu3%?J9Xn{W{J$ z=#EPccn!u_$v$NChi1E`P=Hwb(Bu&YUe?>Msn5Fnaz>+R*t#k?G*t^|Y=#(Z{0w3R z^R5C&9C3?DfFG|zNe?W#N-Sx#lJln5{v*O{CAL%`#}485s@)vboq z-;_xa#h7)df{(AF{=Z;A2xX(=Sk2;y4DFEJP?K13l$5E3#HK8mI}fUy0+5ITk*G5szeLwMP z2^1_a5z)pG3k5vsRy*M+MQf0uO(#?58xK(3yMsOJ^pa zilxLx7OB7&opKtxML!>cRsh8)^Y;g~Ns(Y?K9!Ef?u^)yINZ?69cks_k3Bh@(8{Ka z>R=;O1P1hu=Z3;v{+gFsGT=eX()DUqf1O7c(dN80WD|Da{f*X$=TLqwfO(&>T|+hq zxTJ`4MDA}L6C{Z-!qNXqHJJ6DM7S2ob<)%;5J`s4X;YX9PJf@vfq#e4mxPsTLF$oh zSThM$<~u8dhtW>*dwJuEtgVARBr#mg%mc-6h5;m(anrhY-dZWEooQ{lSD(^lp&3x?)4bx)5r}ByHX`vp@h+ruZS^{XUMg)fl;NpSN;m5 z-~yy{EU5daB~ZS|TkIVfx42sypzCV1$)PR_WsO|@HQOpej>%Y|uQ?9UeqLK~dd_|v z1|#(Ee_6_`&eQxfp_vw+aWB1UUwiLk0xpxQ1bFDqwmW)Ptgan&SSnr5du0+2jxY zUW@7ShdoR?t2HDahA5s4fKtLe$HKFvT&KULknXKC$=*0e6MPo3lGHxt*K z{|YdeEUK%@q>UiUh%O~UZ^X^bPjA9aD3khqU%!Y_A=wX}=1&+Q_mz7_QesjOfdQb# zPW$c3W13VQHb;yz7-7E)Y3|V3CHs5@twbIiOoUnboWKENT=+cc2 zP7hqI!P5QN_)xyuMEUck9|p}XDc0U$l^Iu}K~7b$qf`S>Q#c7{~TP_t@;B2O?73A8W3&t2#(K7T(6b<&t=!qQvLEmo>m< zT>vyw*$leuYMn|YVvlhspu2$bO*UO#g``jz`^~%s4Kf$!S3Pd+8z7pOD34T)9O?V! zE!Gs1hx&2g_Ig0JGOU3*#cN%8J-mu2Rn0ivU)=r{{?W_Nh3!g#p3Qun?axyNc*u6S zm>q_<$kvN(tw>O@tMi6yXqO%R0OKy18Og8a!hc`iUvx6Ra2RPnn-0K;AQJOPrczpB zbe5KYPtECrOjo4OFLJd$%Q(4bO&zh2x{NzY9@bZRbax}>!7XJ*{J?5Zw!lAIzniA> zzW%+PR(pA&=cGHhE{yQGY5ff4uGb}nlkUp@iVzzvcdMist%{HQzGzWCY#0+UL#eZ5 zDCZjU_Tg%*vS`shM+SH}W21%ONAvYgpRQ{@?lDfeQOb7g3bm?zfmATnNa;b04->zmc{d!zy0kIA62yzoie zXtEe#&uCN|JT<$ z%hMyBvTe+~P22*LD;@Q+t9-oa|_^pGX18)JdN#7jGrV?g7lvc>5DmwPx@FyJ7y*w!-TEdzW^w5xL}a-bjGsaR7jaA%c>Cream*qfOAj+vmej*63fNhS2?h33 z02H1;0~_2d;u)@o74E;-I>+WPqjn1?jcwazV>GsH+jg2Kw(X>`ZQHhOHc5kqr|+3L zU*0)?V9&j0_LqCFwXOx4E!4+}O|6dwQ;=_A!(7?QN;jj1dTX;7+KqHyeC*jot4bzFuY<%aLh|m_^mIQGo10zk#~P1 z!vVRt)V(KWbVLgolsoOJIO zm3sJje}j#%Ekiwyg4dReg>`mvibhWEtk_7@0le)W?U_Twyt0!%RELZ3tC$r2y(LlTXXJ?nhVsb!e|>B|3U?_xD##qOD_0>3DQ|`c4ZF`4 zy<9+Vd~0$(4rFO*KeqKZG`qv|^Sb$zO`Y`xX1Lvxh9s=?i)necvMfQZ%7uJ^878Jo zZN0(c4>P-BzY41QrJpNV>OJvk=C8)D&RG?ryc_wL(*M@7bGFS7twH85l`K_Z6MH9I z6RRplQk_iRiza=(P(}lDFICso!pg17@OVH?^FkAAa#JVw5siL-PgGSR0f`*XKaqZkeqbk<n}g1kBe&CM@6s_(@b;Fby0Ly6*vheSwHd>+Y7dWcM-1g5jw%-_}Kgdo}|e$0o6kh%k8{d}N+y}VHP)KJg+{Y?&&q=@tiUfADw%_SSE zWOr1Ohy`p5It&sneg*)#BU{I(*o%LuOK)yfsAXkq$G+y15o`b_t%k!n z@m25ylN!VokIR$ACoH~}IdaoSj|yep|F)fwReqD`yk&7(P6-ciSN5%)-S8&xNg-CS7@TK;8#cVj+7Fo z05`x4KYaUv&hME%N^>SC&`vt*73!{22ZYUEQoL<`NZ%nWAr= z&X>zuOrtMbw|R(=ry!230#Q_D@H};Q@JqC|XiKrSwCpk1Xkx$xey@Vm5vN!^lT*1Y zC@dQ{;g@EcbGG%jEC{}T+nnYI>A$|6-?8ePV!! zBF}*|W8YJYJ#W4<1hNBr+_?8zf-SC>=rgqZRN7fk|TdY+CvG0fwD?> z>a?4eij4uLtb4%bNqeR1;-2+TDM+k#K{=!Pm&>2^)^aM8c8}`oW{wGR5mf(eI`V!_X8@s$IwB=RB{dG<&34o6{TCebY9u`%Bem|=sL{va9(Ih=B=j}sSRFpr2s3ncM26x<_RsWCfQM)_?aXTM~p-bSumx{e->aT-BO{3 zE#Y^#qeQ@n5e7Obk-y_#(j5{!s;=m-*N=>)S&uRT%5be=Q-bttA_nme;cd*ZR)>JBgeKmM>i;8b^iaOblRli!e5SnWdHmLJ=1hSQw z#~b(^l9eKJEvxGp#j3m*CC%}9r!YR8m=SLSRak);xOmx9RD*hTBhFg$1$@NDm&`WU zg-73=zuDkIf}$BExhdu=Mz}cDZP0iMDrK71t|4Kh(`b(8OTJG1g9w&sl)4!eb$HrJ zvx8sSsdEz+EB0|Kmc9EV1TY=R1B9L}WyRpfurK_!QM_`Wgu9*kQ$8}&g-4_H?dm`J z$*h4TjfC%66}%pgbzEs=-=Q_T_Ub&<^$V)0#W778DHmSiavReL$(bv zI{Te%yfupPV?Nf0O&RDkIVcymzScIP#KT!%{=LilUQMwPJL~-g%U38H@M`@Gt)y%L z*5ch87fjlIycVbLpw+6I)&qj*zOx$t#C-sowmC$7Nz)tbrzOgj2u(oU2%6s7RcLon z%P??zB3yU}+Yu0-{himv9JK`?VltjI=K6+n2of)_G_N{K^tWNV=iK!MWY63ClK+nS z1T~m5fEskZk)A;TBFlp^r7yU=c{CS8k)8sV1%kAH>9sjttDYD}-QQZ-$Vd>6-4JLj zC@4(^r(U904E}!fCL~@3cI`@#s2Q9TSrOVZp5~4mdAi4{292XV+v9CWRh8|he{e|C7p2D z;Z@tyLIp?8kV%j2oAb<$@3XgoUaNeS$tq|B>@9$@=;Kj-9rbZ-KjS8vl^<+V?@V~cJP$bIn^nSDd zF&p+2+LS$d6BmR+B6}*2p1{Nwt-IUlD6Hx9``W*c3VNHDs)Rf;XDugjWT4mGf)PPr z9gk}Cp zl|MO&r(aXe@IMWMv$*+W%oMUVM2aLab(ljJRr?2L9Ulsh^4A(^o_--I9gsn&rcDUc zVl$uo`z~XiPROUWZin9ir(*I8LBQ3)nfW}rpoGk^N9CKcS0j0#s-)O3)5ckfm)A0G zOo3CQRf73q#2z4E6#f@Gz{5Tk7J|DsLwZ@{3-jH7j}jdTE_dJn(HM(V)xdp%7v9qP@vo->UM&6Lzz{SSXsz2d zAk|xM@D(rBJ^#RRj_p+%bJJL8R8q(-!TjD#)6f;j)#e9kZfwzn|Fqg)4vz#n5Jayc zS3eq59>nNJST^No;{Kw#kwgI#_u1pHBjpR!Vc{9A!^<51I5_e0&bFRfxa4`r!@-W7AYE&<`l!x}xK$N2r@Ff>QPI3;JG)Uz6xT z#CkA*i-&~DkGS7qSfNIr2z-D?kZ`MxTign@in|b|u{k(N%oY1jjKh|epnA`L%g)vB z5=)Kxn-Td-wM6a5;1&M5dgkP6G=bAgJ{bqx*XkL7@UmlSaw{?l7VL#sZ0|(^HyePl z{bYIKKWwj3>HEkry%A5N44(egmAT-5%z^;X!x&*RhoNdrWQESni4K-j$6b*PBZH}_ zYjpUf|H^_{3Yt$jH|fVw#9LGE4>ZHX)KtAzP|>->vtM?$79-qP1!%X zlHPum4`&aB?02_R%-zsW{<|}NoR2{;B(YMK?$3j%G`7X>i!Z~SU;{NO@8I7tZz+vL zr#ur-IH>RK)Q%6T%s)4sg+x4F5i@!XgR>BTnY8Sn%S7zA13nU4%Z6-FXQ<0?3#2OI z>ni%roK6J9cG8aGm5dGnGBqYyY~s4}qx7nMbA!qkNq&TzY-Q`Z>!}NPj^YXHBt8%v zNOzpY-*r8@AKu`En>7OI;m{~6a9^QE7Z!e!#MXmZH#nkR&WKj<=tKw}I$Mh0h$d5k zxmojZvBY#DBkwyp%KZ^(S=KdF*?&m%0R>Q2Fw?GmqWb4Ryd6dxuCWX*G@a`1uM8|y zHCq3y?6Q91a>~R=@HeQ1tS099u7hD2EtG`1u9)HKY?kjcS$@darGJA8w(~ok-s2TU zx#*{uk{7?t(Qih7`#na#8r?Uu`g?T*s6C5Ci$bZ#ntbRD{$v$Y)XfS`%kNm~N2W7^ z2-Eea!UH3BQxna6`n$ip5wVgMw9jo75KFkPxY=QwP;WJeh0kTXpBy?Y92s6+HZk?5 z^m74Bh324nEZ((QJXPfF?TGXDt@eo;iKHz?v101l(WI9?#d(MpNC5FVIH(d1V8~CE zW}-@f6FPz6wXp?){wkUoE6Z+H1SV5sQzabsEM73$*=tDNFi9eXjtsj5QA{8Ed@CF{&S&s}neQ329`V7j_>dXI`?nq*ZZCE6-(!C53Othb> ztU}XcTP41^M0u7$er_ebSry${B)dr=zOfVAs?6$zw(016VmC-@`mT~bZEm)a7!ol% ziNm3@WR0C5&1K*yB~@74RA}~RJlv*P56|UGpGzV6HP_rdTD6V@z)^#^n1GI5=t9@L25^i5D_p7GtW#)o8kju3SnR^}iI5lTTz-07`Vf0A9)KReR zYtEsVUnYq?RdKTMs=v*n3WN|-+6u}P72VM6S-KWKep0FycK!tpH5h+`ESl%O+6o7d*1Na$Ofwe0dav@mQ&8L9`F5{vg~nH zW)x6REuwkbD%$Ak`iWaus$CYKA*qLoTh5G;Y@>|VYc2k%G>c2)QOsbGV&D#o;;>eF zWbz)j+Gf$t?1~O=9E8vNg-~_$_MSPwMl*?qw}J(Bwfy&wIBKW6asyAEdFPH!TlXkD zoDYVAWuz>?RcO*1qyNZ?!*tS*PtV;f7il#t=+V)zc6NW)&`{@UY~c!jT*40nQ62at zB`^W*sWFJ}-SII~iGpQx%hn?| zF6oalanrqPU7*%uLzUzT%a@wyc)d|Zg86|3!W1O1eY=#PbOW`}t@@UX4HhF-hYn}n zh!)-NAHfWm$CY=g&M>EIoG=_U5LNu1F6Y9b8RPEVE!$h5QB|+VqY~qui=p5N?)uX% z{RO75e+2;ty$u|ILG9ilSp-zm;sUAF;74TSMK9IMvlNwq13J)d91OGP*X(=>i@2ij@* zxf{%a3fK>5v#MGgUK|bDLg(A}1iyHU?anDEXeJ68d4f9YK^FN*-?XPvJKyCZ z25IqAjJma3iRQimt6|pTXP0q|t_6&Hg~i{EQZ05azlrpeW#axh9eUnc z)rS$S|KkGJu^C{-{&`zFCMX&5cMmdaiQCD|&_H)St)E9)ws%D^GgQ~qQO%9z&vPp>g{D~oKUWE~-dN&vh8S09}&op{Tk21TGv%_g0Dv3m5| zR6I5-@;W#hcJKQ3KeAXQIH`2P?Dgw?^2`#KgQ%GV550sRnG0%}hjOErzku=xLqIKx zz0zuJ#7-TJ89BC}0CQ*8lsmlicRRs!fTgEj>wKoaQS*h1a5$-MR9y&WQiCHv*276HS7Zdd42Z|Wer1fAv4T?y~ zg~0@_PS&P=W&LYIte;R@Nbg+^j#d>dtTt3YXC&ASIh8J0$HgNDYgsP=xTNNchey|+ z9?)tHyMHS8e?S`bDFfFXu+ZA&{1kF108|egt(;;$gR-93h(1(h%j$-nOMc?l&Uza5 zX8i*x0qW3t#=4i!3C_c$*JtvxcSfFa!W*bqlvKhG%t?i- zvyWAg-)MERl1K7ix>MVoZ&82wamn5tkNsW`Cykw1x;vasZD0u1=ozVQjvb_*?;{|; zRzz=ne`L`mOmZKx&p-Zokd>-w3||Ic)EfRso-U97k;a}9Ie>Zh_(w*JY$nxC`bJGG zEL5H1^sbSK=Hjx2ZN~D+H-Y@>5)jE^Hu94kj1gI`7%`d<=7}m8DUVjMj*A>6M^scb z4T#nX#e^f^V)`b9%K~qkAIrhEtt#*{2ZQ{Kp&T5I)DjgTECrLdP5m13bm_Hx+ z`AAxpwW+PbLj5>37=|<<3^^}%L1Q7x%c_HS{M1ek(@R$st^DTz2Bl>APYXlk)G{g34EA$1jUIw=2}Bf^1e z$rrM|ES_V*5`00w=8P_mLU5Vb+@BBB%Yv2kMGOyLAOq#^8}&OB0Sw>VVOr0LlO+Bn z&cyk)ygk;<)yR5ldI?&lX~c1cIZBeIU~?V~mLW4U{$bhQQYNo&vjBMSet%{Wt)JV> zN>$^{gm?nQykXtO?{ES)!KUx&*3nCYnsn!dBm-^D3s2@U!LXm&03Re3edOcPKEdKd4UNU5A20WQ?p+&*{XZpiOKb8 z=Oe6*gU8}S=QA1z+Cqi@&ZvfIowfv_zdFA$gqpx?fWuEU)~#@pWezO0NM75-UOlp& z#|uK@Z%nw%hKL($({KJ&zBwCH)kO(2T@@<3!3th1`Bb9ij(|LTzq&s2##yy4pBp2u zfUh|yiM5v-i;CSmfg%kx`#BW7+%J(VM@#koCkKA1Na%mgmFsvt-hl_wNon_HrE3={ ziIQeZ+;JybxLmicxqj<;8`HP5+xu<*RYt)clftT2{t8mNX%9hcoTM?|YI^!CtRA3X z%pAYsRxt*&KP|?KM94B;{Tup|nauOfEu9A5wvhmdo6V zC=*XueL`#~q8k_7IO*MWc_OJ+rxPN4gvaq|_Ic{cn_yyERO@KQfU?Of6_SkjGH?8B z)9R77_wdO3e1C9}K2M69?HV-Aq2!+KdUk=0+XcVl0}9qZS5_z;_+4@>cBC4HdMTzU zorD@8bnAOD#D#K`aEq#YTcLdi$YpNq0@Y zs=TAEfF{S~S&5eNTwT7)0^Q0vxw&(~i)L)paObuCa0 zpir`sP!q_>tl|UUL2XKV4TN8H{U)uN);15FAw%x>G!twE|DhQ~-Rd-|G(XS8^!OYV z0jNeY;9T4+D1WjJ4}4?AFD8Vr8)E!eu$k%Tnn(CF8*=n0#29Y@`tuamEDm2KY;2PM z!1kb$t34v;W5dgDn>4)d+Lj0(Iqed4z=-7`tw~YP-I1V%^LEuyk!l}(?w0o9JIwy+#3m+DZSq> zPNwr(uX=Giitq&dD<8i4gEs8`X+=MZ6zxF`0XVZ#7q ziw3p7$1K||`)r&wOp($%(ISr3D=fq}qvR2qm#38VMv2;D@dM#9lac_QgS8*e(mJ8zt4sz2itCHjpOedu5Qrzj zct?vyIp>IW%mFtoFl;227Pu51&7Md7{dIwTz>U~Nay?c{^dU6v=pLO=eDX9C8dL?( zse}Eokqd%>L`0LLIAI3pan0EIdv<`Zq;gt#es*BXgABE~B#~r+i%xgs5pUK{)#}_4 zFY%fV2HX9J)rY+5+Jbx;$i?^|-($dd2gYGk{>JC`tG8r5vBTI+krA@|5`@%7_I)Cs zxi45D@bAyIDy2K_zY2eKgZJ09q z1o3n0u%A>{L;)fPOagC#zx!A}9057`TH>l^|L;=T#mcRuE!QE|o_?>hC6R$7nfpXZ zyPd_fePZO)kS7y`>(Sf;mXf-2^`Cxwqmw_3sqt-c`tlgaL0rBqq3<~d{%ye zMD>Wkv~0|}C2oQC&>j7po%(i#g^#QwQ#o4EI5fp(Lx1>*tzePVs4^9;j~{4qA4VZZ&+41)l1D0^-zRw#KK!JtJ<1*+-~46oS=YVXL&$Ia|aOK0kL z=VarvzainK&LHtkjuP>{9RBE+Fq}Aq@_kH79$^&5mOKT2+LY0fc1hZ%e-^?@lEXCq zgKS72@RBMNO22{n9?1O7PYr4Vb=xjg#0vZz{Sn~0}Q^{Es#nPRYZ1=D$R4en8SSq4}0yzb7AG^h4D;ZFjkycF`loj zCTX(uH5`vK&Il1c`NXZt@^(uK>I`C( z-%tw4dzUp*R!E|d9|)m1a_nQ?xKn7i)5%BVW*A1Z?>3y#?Dge@HlYMBUO~6ibzTLz;#@!INW8Pe^Z?^I~=J54oY*8!5dsi?7(3akx7;&RG_Je@IAe+1(mut zQEw`(M55-4vwV+93GUY3}xdOAb-}_04%QBN0vM*d;Lv3v8 zl-*hW+)=Q)I`iCFgcYiMS+XuC-4QzIV|SK@#g0oLq38eTD2oaCCCjsCT_imPn9~)) zsv8^&+@hNzOhZmXA?9taa>ybfW&U;5h-z5ew1m`P-v~xbUxYNNVIyV~@xBO5)g#-H zR~G&`&MWJ4Ec`PK*{nV6%N7wXD{;vH#S%Kv1A3Csp!HxPJ3p;xA@3j=Q+KQTB%9Q3 z3DIMB*)?>5mDEm-6RUnfaF|dMs8lQqI^0+63cL&aQxjxqf)L&OEPli|$B+N7@BYXc zde3me16R>gWQ)YeL<^^AACYS^xmD`JE#83rLiU~biJqgBJui!ToI`Lt4C%c_CRWo# zjm@n3&$4IeFDXQ@Y_Og0cY%NTrV5D-g!rk5L5g#qgBG!>(QV4CzLPJ{14++r7b1n0 zb*7$WQGqR)D)ufCV_wu@53~p6V0~<-^JMFnb~MZvdb#R`n%vk%Q>l2gMHhwK)2HO` z+XcGaVM-2rA8funzM(mmcchb*^ZO21i}U+yd0itgsREfjxdD^R1iH0jzwIDE#?_X; zu!A*x$JJJGj0UdnUg4nNfwf=-UXj#G z(TY);9*+456+dgO+WaQ-=`567eUS$rP}QN=}-Lc^B!AQ)iJ;CwVC6>QNS|WXq;^pnBevnRDC= z6{H{`VF0ps_~-juYN;5bP+cC$-W|m30Qk@x&=43bBV{j2=%2h|5Vc2JVTB4Mn%Xs_ zplT+BbBY6s^ZT1_Sd>;C7fo_@G&G$U#04i(C3rH)K?Jx07!FtSL3o=FeK7S5vFrKt za0~!=x%vGRxVs%`O`5!<`??za_XBs~g{N-~Lrs@}lC^Q<&#VP&Aeca`h9&p2>AUzc zAYOdDkI{sv0TQ)5<5hF7d>V#4tomNzwKmrGG`3@z!R_^PS<^I_QhHT$L{gK)uw^2E zumgUTclJloPvM+^?}k$)lsP~CGWY3~iO4vtDT=_YQFf}jofjsufU4+2y1=jccwX@L z+!Cyia0qI!dXRFmm2tv$l|%mA0Qmo20N$=D3FhGtpiMZCB`0rhrVc60pV)t9z*FTG zLpGJz2#V3F86lg?FCuRexhfOq+dV#$qc3)|yqjB@{-zw8PF}6Yytn3_X{k=Bdvam^){N zobX{~ryU0`4vxA_9-_3IwoY}SkJ~yg+$B+rx}3wNICA%;#u;)SjAGW?M*xRiKojV< zCP5`4Z*+4Hb6ZMOns`<1$5oHBp*l6iSXbmU(ns^B=`7B-L4~)s59?(%z=liqqXlZq zv9tJ2+)w|@SAqb68Kq{@>7!=%`Yj=J`C{4PqK?SVD(rESy+s6y*ZaeB+ja+dxn7+< zna;Y^sD+Joecf0Dj{jQcpEJAKU_)~C=vN=Rn8j2`jyOVx$MS2B7_wvr&7Gfv88}+*xQ0zX1^MF@XLp>asXSm4yT)&e{Y@_1(QL(wI2wXuv~#DPQ^BB z?3g21(I8)e8-Cii*jc^IEtg++-f`Q)W2j9Dy&*>{t!&^o?%^5$=#(|Pp&xT{|*rW zb#@*j55yzsYm5%%GA9!s^sAz}80i?~m*G<-E+B$;K42)GeNS+L)g7j%Wf*3;+-p<+ z7M8|}e(n4UQ;8%Ckc`5vt7~MVb3mHvH9m~zC;$R~Qbz84FEIN{re>fC$RRKv5f%VQTv+ki$E0*|YQM~~Cf~`WQ<%tB97~+O_QZ^=l%pzniTKIE< zF_^gI`5ISOARzA=zS@{313@EpVYIoGSAY{;%)31R=`-mVxisbY)sdAR0L8c`^z2R4-$$EveNZK5UKmDa3ZCA#WoZL=tE zKyo@fo&6LM=&KewOtWqSj&lduB!Wy^1gm@^m~!#mOu zesG^#-a6l_!H=Ak#Ykfh-|+`k^rtmN>xh9YTFEFc5xQU`;}0no8A=HTic}|!c0@k_ z?Bh8vvQC@j( z*l8oI#o3SO$J@=7@256>WWRrWc&a*#SfiEEpD=#>n4W2&z!J;`YCSQs6s!qpYxY3tV$@~U!?~-)>7pTBG33sGe%p(0et)XAxT;OZ2BGK+4j^AU z!N+h~dSL~8?VX(=gY1+NC4p19mS|=KHAz#AGo`~ArqX4Q51zfq6y8}|`^?7^)956H z>$@12-0Mmiv*F!{|02hi3o%9_Ve6zNUNKD840I39(F6PelIi8|%N$Z8ON^hIjK074 zTO5^%c^FZ4R!@fZ+&7rfF6x93fh4sbD$XHB)b*K80Y=eS!}Gj(3FX{Hmefv4EF4Wd zd}lUpo^=t+vrC%O$6aYUJMkPW_-b0qlQC-1j+F_C>MG_4u>tzdm`CQ;)R=06Hzwh7 zV#ed-;{?kVX8Qh!^}5Og7Kq&mtD1C>Bj5~X2-{;Pfs^#~xfvq%mZW`EKy(P-RGFBA zX(&t}0|PakCc$9iGL{KmHRJ}r@M-KU`*Kxc-YF4WeTk_l4VdXA$0S zhcou?VD989!%nXcOpW7L(b~G(Vj{w-Wzhn29U4V zPzVbG<53m+GAC#y?K%Xak8|MD<$JSDmlZqVy`f>9;Mu3melD3Ba;%E&ji8(ZmnV2t zG9e(h7(n$^{6{muBz>;0E4)(tYp5|5t>>kb4i2p{z?+GaoFU#_wj@1gkE{dHL(crwSK)XVayb1K}c+h5<$go5Z&w;DJ2p*vWQqxhC-;3tq?rd#kKyQ0MlZU8 z&^aot-X{q29UMA9%Zh)X@HHBj$vx!S0KGMRKe~5-OB`}A3f&AABrSKCL4+7l_j}U)oC-HtLC@u56 zQz^GT+^e5vLQ>HS(<|G6`6xM9Cf`WvFd8jRSKoku;CUFJ3bZ8BVC)w4BTn$2Hy%1w z>>;g{f~VhN=ji_abnzsQFt#6GsLlPT44{m-1kgo)P$N;N(>d-IKY)V5k)$2YV|3jUb#Whek1%GN8*uTb<)?wH zPcH!3m^!(m3&LlGVX!wzj^Z5jR9YQEJkHEK3N-A|5Euycq?rrZ{-mB!JLrMsAa_df z3+b+ShQDb};L}AZ2huI{uNL)*>eV2iHXx}oLTG0D>ehll`X~~s_(s?d9Adn~B?Fm@ z{YDn#3^Xm%rTFTYDZexQ%SW=BDz*JmKdA%2MWlvg9XU3|NM3(F_#fkYq*7RXVbJ%f; zFa@#gVF7iI;sO86x5SB}mwB1ockI?jA|@uB9{B5D{9rVo^6ufvw`K7-t~i7?-qQ~( z_XCK?d{V`9|4zrR`|~qh*j34#u|UF2Epb8X#Z&0et7R{Zk3Yb3zqq^=<;Q|9%u5T? zVq^;g+c{T@Q7N=@7v0Sv)tA|$$OQKHNn51 z{*GLbgc6bh^pV^`&J)%}Wyf)kHx8FfJz5I4OU-_#PNp-~aSpxafbv=WS*LD`I$p3E61x?w1oD||9!IQ zuF~D8 zNqdT`kLqvT4yM(5{)U&d4yATYSxfEoR--ws7iH7g?(2o$noQ{5NeVpr=S|I`+#YlB zLlv@(+Rlb@zoTMF=3xWTVUTL~s-%wt&_X;O9y#5@j}d28e)?sMb!Jh?)!gm8aldxp zf|Ph>?_Nya&lbT4J#;Lw%;p+HJnHBtgRzLp&CT~r3gzn6yQjJf&$ zP_QmTy@|Z@R`77$EwNU>|Hb{s7dzqv2OJa;Jg*sIg<5Q4?r{u=S~c@tsFOH)_Q{HY zj*CC_U@SfRWF=*ZiW7Y`hz2!~hft7~yuzE2l7%x{b(L~fQ~VALmlp3mReUQ|W8d!I z>gZJV#Jteu;_LY*Yuv+z{Kz%{wxVk(4J5EmZzVVn$BFU;45B6S zypO8K!MtcnEak(%g))R8$v{b-@46%;!Axm-#1je|jgR|TOSw`i5WHF-a702kuSc?Z zH@s+Mn3YJ*-#~{B3tFiQ&Q>JnoL@|ajL}hMUR!@_=&oAw4(v;@&6iO@B;$X=e7+S0 z?ZSisBFBdz(f6ih7WfOsA0(>JxR-GobxS`+n{q`RF15L1%$W@AGyZUdH93$o@ZTd* zxypQ`jIN2c`8!i7iQQ^-Fzad+k%IeFDvWSL=KX15qMhhXup4IquJ zQV<2FJVsu5na-eicz`@lZFgf||I-&q6(hI-x~vqh?bHTS^j^>}!KaWvK|S#11;yH7 zzCPwmrsc}*Zaj4Ma~P~SAx^(RcPzt4;^oxEonZo>s#Gxcm#>vSKsu$$FD?|eB!nYZ z`iR*t2${!0gw3}S|GJ|zkc~t+QsT*wLk5#II2nwz6Nu@j8OR+B8fjIRy_OWjOgNPT zHB9ytH7DHV-T3p9IZ4%a5Sy(IMZzMFv~qY9BWBqrJ#NqJ7t!88g>3^pMF&E9067Hf zDCrb^Or@vYxkB@lBi!j{x|Ub$*J>AHOgf^gasX+o-`ERu4s+rMJWmcH_d z64#8xKYrVm*Q$5K(M-}~W%QB)&>-72f5pGExtCpk%j1g72jRKFlC&{W5T0;+=on%_ zeq&4HDN-c#kW!VyLH9Ar#g9_&D?nS*Skzy%83#$8>(xo`gL1$VDHYcaAH4s=Y?e3|isN-lpY^!poNis_TsczViu6iUMYOvhwZ?}7cuf~ckvbVK zrJJN}W{)ari8g6T2161n-mv@oIJg;OqaRsNd&*uNQ&LO@Cvj6FcT%ZE?CN+G@5b#; zanZ+exP&#{l2L#|NAwURSEA60fW>An6i$W>r=hTm<-AbHD?c3w)X?%+tGXQV%9ac? zWk>o*P{Yxs?p%#Fz7}%eo05P?^x8O8W?JRt>@J2-Y#ydiEMN{^FLPOrGL%jFTWC1+ z`T?H0n0?t$cS%P+AJt?&QXaiRiqxFKPIME-UOisi=Fi8T&mCE#O&-My9UrFGOCfu6 z>!%w0(hS|-!tcAK_o*#==HbP-8O65cB!R}3;E#r0HQQ$^%SOAwyGWmKt#5G{ zzrLlh5Q5<$Ftf3>)G;f_$+1hbMx{N+{*#LBVuWA^n32VvT_Wjq&sbr#%Sg|}JqYs^ z6J0kDo08&X1yOG8G0?%k?(cp$bRzu(v7AGoEcnM-IVwuk>1$eyLS2ha4|FQKP$9It zLHw+Etf1xyZ)>`|NHli)Lm!H50wYL|y}7zCGfM>Sp|~jA0>-^;?k=3G7Iu&5Qfdyj zBs&i%J1o@x@u&L#W|WD;(EG2kK_f7D(^_Xx|NA;~VHnOpP9p!mpe}Og^Lr#bB7-OZ?Uo;WOW8Kf~&FJ6fP>RF4YoeH+1sH!~hU zH#zS61B|(*botk*_3gAUv<&E0mxKw&-Zp1cSbTlQFL(7xb7F!jst8P5!PnfFgIIkW zbY+&xPJAnXXk911Qi1HB#a|)Bc`rWJrlsHdcmcuiDLd8xBZ(r3k(dZckjJ6??)(qT z9m#^zDcO}{0Ns&6VyP#n+T8!n?!V99`>&b>mbOe%rx4Sc_7`a&F-vaq93_R-hv54y zHGBH?|c z?WH|)AEBlX@!>@?g6uT$F%eeNuuf7z&(86PrVq#| z+c>@X-RI)NW4e*$LmtpG#zhO2TjCvO8$0w@oqR_1u-)Yygcbz?AEaZ`gx)=5A3&~E^4 zO0SG*Ubf_iXi;jjo-049?06A19(0z;OU&$F4m*vaFb;{w^VbEfmQa2%lmDH(pvzZu zpQHR>{fayYjM+pe6e40`Xo4@D@$C#ilXX;0o_C33eW zm1Txke|a&udgJAt)zu1wp9_57x&ZR(MveXW5~+WLaqp6=!a+yvQ4oPhhAFZlNT@`k zBV3w+YZV=Hcch!f$$}6yW#Sk7fC)u)xEF#4MOo&*Wu*-Bv|YIFh5svx{QoP8HD6Jz z4vP3Mik!j5YP&u7F zvgPXm`8j{nd2xhh#OtP`9RE_0 zwsFPxO~Y6kD?_`lcp2@ye#J|HGe;}p-+qU(?~gZx69ur#AM%v5qGJD)aC_jdo%WU) z9e@s`?GLG949mR;9CDOVAGCB#31;nf0G9mt&`0qEe)T46?MXq8 z#T#u-0mB;T|0`u}MqntkDn()Vg8|9$@`_+A$RU^S7$(1sIOD-HsEsht3DGb6#DkuKIHY?rqW=L0J3Upo_3LOQH5b)NTs=4x~A-Pt<`VlPpy z&AlWKJ4Gy;3z<5on?(+e1X_N-%z2|aGxjffB)nRz$DzgrK_xcXHCTq(%6u$Hmu*#*0SL9 zkiMnF9vL{sO=GcLR*`)a`OdJ^XIDg^@Y;1DvJ@4fFk;Ahtc<6wJ*|f2$2wK-I*Ow+ zf?tfvLy9EO6&^=!SFbGOya+@gmd=G7F-yZu?(phu8Pa{vAY=T8&oQb~=}8ciRHlLJV4Cw)`c_)vOuwqH|GX z$8wsr1kd?Q z-2X;ExEzsur5spo_hRwj3Mk<_+&EkJ4g}M$+uktpt}}D9H3PVe8V&e#!q3 zr?Fw+s93}kkkiXv<6UOLOdUV?PO874@z-wKE_*Z5XW7eQHetnb-4IzZNH_rw!-OWM z_rnm-)nhR;jjEf43?**2m>cD-%+mm6npYbO#Gm{^4h)D>8ipj)A=l;VX` zrnwu8bn;As$B0v|V=l~&xlN8~EriGWxGzwKPM?l^t`N`Y8Hg;vWk8fvcMPI+@Y@^d zHjd=a6^AWN6HKZL$rttdunK8RJz%;>y6yu6Ko`~ZzGf5Xe$%yWNEqZQY}Iuv3y43c zA!w_c3Rc0Di$-AVtr-j&2n5*gd#9(Mq~(_Fr2;3B)J}7JF1R0guHT**sWm4dV) zeBm$5Fuk-KGx+YjUqeH4S%!SJez5^jn9^@Y@QKv&#`rFWS%a-F8vLtCxKiV2BSOoZB{eviY{oDjxIFdgq8{+M8^Lfrzo5p-43VwfR zKV;bV__!^|Fh91$xNZbw8!r6G!$P)Y!b8_ryt)C|)02V_{|%6Ax#^+9J=|BTNR(?M zptGe1_DC3}cwYe>hG)NgtlEy!dJ`bkGp(PNMh!gu_r`wPezAhSX*6c4VO?R5vIW5; zEpqgOp2Sl|TqPL9;+IHBbXKQjye>fx`2=4L^*05jl)!|9!bvYMbEFrw%6(F}85~5c zcmK|8Ny%XL!ua-hy$^~N9LcU@%}ZjVQ8r=|O(yoTZ_Y{+Y`7Yv+rP*n_9cPxFpgO8 ziCPv|i!dfPaD|s2_G1XoK}R7Ib%=*FTqZLzT@^qLNaXLDR(+zM8cf1bPrW~a{~Enl zUYIE#ht;u(1Yc!m>v9<#k|$*>2eC((V-5ydq^&6l>(?!wc{MQX_wIiJ8kbl+tq%;RYD$;jlC#hR!GuzV3>?xVtR3;#MEkyjPtQA6;mnDs; z9VEI9*F=hStujv=6|@Wa%n{2(l2F4c8=ShA8!&i!ibnYdpY$ixuL)pBtHlH56m$xq zh)R`{dTf}kZ|kE9FVjE+RuAASaDT2y#?AjsjgQMD582>>4Gco?giwpeI)|12U{u`jRnhp}xf6y1 z!^tPb@K2q^E#fUwhY+rNg|mfya`eZl*O&VcYBigx_H?K1Xk4jG%T0At@&j7+|FOXa zjnaf{fZzOC1B4yg=j`QyF45N|8i}i6H@P;1j}{3j%SSDJ`cWw*+;6#6_Nts=C(=&tC)>BHff@Ih|J8dDfbD#)nB338FoJu%}D&oaXJc(WPm4w z^#;|&2CyMX)=%J8q;OhNh3Yqd2ICG}dFr^{T!G-9A|CEo!UtAXKJPiVxA|yJtDnBE z4rF$&u0!~S3~gCozfOAUMkIfEn(w1Q`;yL>7(Eff4Q0;`#Mta`^C|~HeF5vG&4lv9 zlci2bOVC*mliK@gspRd4Rqikla7|qn;wUD!gEoGg||+P%WKE6pPMf8yn z@Zmm(tU8ZEw2)y)XBf5SZe?wqavWg)hg()r03&bo+6=R1UZ-~_NNGy5J}xth^*x8N zLQ~P3U5t3rgYQmS3V{H}txR;stmT`=R&_FTVuTk@6fBkk|71`os}x=dq63A$hs76; zw1{cLYGmN9E3bp&xzktHrJy3dV!i$bNQu`Uq=5nFGw&kcx+v^B4Eq-JkxL(?Scnl4 zjVN7z#@K@}*>Dzt=Td@BXD@sB9*S!wDb%5+9gxfz;P7^^1J7c&)Y|l?WucPXpp!M# z|3Q2D#$ZxX*5TB%Db<39*yYE4!;irEBR@E^?c!vjrDfYiwlC}4Rhsfdp>?zq0PUu} z+K~6oGK6eBF$-`r6EjfNID{m3eff#%so^5k|NA;kvg8YBRq&ny!qxf z1@ZfzPP}bo>GbOO!NF@~Wo+1!{cHbcyX!_ zzg3N+wb^@Jb|LP8tD=$60|q!0i7no(AdkQA)w^MFzbC=lZZyfVbkWe$(=EJ% zzMpL;-nO-nI|nOfZi}1O8Svw=dOux6Gc(;1uUX6v=c^fz;WsOmO|bI9{zJmqK&tH{R1D`h@%IKl^*| zb*ETx7gQ4z#|RYK0F+sj;Jufn)U`Hu$x;h^CqJxFE@I(AVGZp0#xJ&r1t)(|t9F8g zq|TH$2Fbkg8#wM>T(tH*GUlDH8@(m+3*HK1kI##_mJd$pxj}SP1&j54?UZ12yxy0x z6+PtgmToFo3hq_}Ds$o3(7!G~E^P}QmSQLo$1*XYNEGJ-cr30aQlnaLA076Z_S(wg zk@%DyvHpo4}iky*Jgq^1KVob-75y4NDcb%&8a799>-`@Nc*HZB~{xW8W zg3yUs@|{{aMpHBiyJAx{AaCnqWuex!(*Qwfh`DhKBf3)k`3Ot8$po{N{^wnOsf61CEa6-R5IE8^8@Nqth8viif>C`$yF6cUULuINsl7IhGNd~6%&&sT}Ix^yGf0gsBS zg^kynzT_sQA!_mzm4R|m5Gmz)nGc}q*4xqTJj2N; zl|uZlbYg-`Uk-^E86)r|$I&ON2JgLc6gIC9OKFAg^-6LP2ZmQ1@W*Q!L}`uUcwX8V z$PnirI@ii>le05hlbD152KgKr-0z}_lr!u3X&(Z}y6B!e&>jmg4_lnc2})mmAOLyz z3m>kqA_{k51k0n&DQtEWNPLnRA8)q&{i~hTJ`!@)xo&II*+Jr|IC^mL`Ffa;xz0+5 zGhZO20UOTjbN6S1>2kuFxeXt&7UkO5|IhZ4xb+LuXY%r+1Ndm-jHEU7 z8&t&GcP<%YED8Y}g`3$!4FUgmh1izT?vIMy<+1NR-}@kzszVC&8V_e;r}vAdg6YSt zOZ+Tb@>F1~AlQDVa9+5cC^11Ewh#0UqbR3T?Q;Z(6Vd-6r@JF7I6+FdE6e6k$WPsr zh>3xEfU8zh`eOAVV}6lt+kTawC)7;B>PcsW*caL&1vkv5hAAVF0GsRvhdIMe5^6Cp z!K;s?nOK!fUuRf2yE-8X|cVNuFX!B!(YpIyoiH zl9)yvHYY;0k*|1KY)&!-cY_elwUEYuTB^gz;qX_zF06cb3EUN1)#wYA-mH9$As7p8 zCp2S@f?rVbW7`Rt`+W)KHZP6LT2Gy zTL)H}MN4!=9;&Ku`ZV+i#C~$wriAeV31?{JZhNq~EWjvJz`k*u%q;t3OR0^wt(P&| z#7dPJmmsE@z&ufjXWZJOe*v6?owI(1vpuR-0qIV&6@%Xj@Jq$l5lP$sXQ__BQKbaF zlInE5M7|ijA4l(0an$CbOK4;-i?5aA(>PA-qw)DUwi_3!6(U+FoA>Ez3|5=>vFLEt zq7-|)v{^Au-}Xk}zoCaTW`*7;XBQFbz;v|H3vb=sL%LuaF@v>9x&u00K$jJ&92Q*W;z;6Qmy2oHG)z4Boz_lc08mqy2(> z+F&rGJXG%###3ZYxd*PAjuvATeQwIPb^>D(4Qp{1mB&`R`@I?OT1ioQ8d zeiNF_zV_SSUA$8S^hZui}iENz@AM!XBwI zq@&+jG+ud0pSVrdL+=(o=>J;%`fIfL{dDNo$;P$XLeXo$kP72-@42ILC>Vt~k!1e< z!IB>`zGdu_LbFF=c$cVFCzP*Da0bu<7xK*EF{nggITCM~bn@v<&;v#;(hDoue{5#F z3iC2vb2T>gjr(5i7@i@k)%6@QzuHKL8f^p>Bt@pu#nslhB+c_Jq5RcIK4FOw$cfsd zkiX(HDBm7eDStkX3>RD*uRgwHxzyIydbt*~O3jyb4oUBOV|RVXEl0ZEqXRuip&pNq z1}sB`f3WTvv|dRz^9~&eg3(n%deAxsuQlhp`s7is$e^*-)WP2(=fs8h;+Rvf`xVQF zrKCXNfISR<&N~c`b8vnmmg^O!Ge#IaKRBE;kioy`sU?Esn%?$ZzPsNXBHY5k8z|w2 zeG&WvO2{Y6|CGLxE3C|i>;lHqZ4JAFE@V?way@wD711+??1<0`ZP$$rgm9|?aI}#y z&wZrkpo0gHY5#tuC&v;E_05Ef?H%pZuYHG1DMnLKVi5Lf+R*tc=f;Y+Ts3%Tsb`_Sg##RW8 zrfJB|R(z-LfTW)u!e$tlQF+B)qmAu>Z_-+Mw@9BIKGKL#dfPwI02z&rj^c*u)}I_l z+Nc1fy%fI9Gf=p4FN&6KMIg$lR_|lTh4e>2K&Wv!Up0sul#I|u<7(53_Evvu3hh^z z&Mod9^^Tt8*#&r595!IOn-f7}lbSP!`EXBj+8=V?KAV;q{anM+ihJv2QloLZw|tJ9 zUwt)JgO`e6xD{%O=yPbS<~M<2wf9Omrd(mbNyX%N40=w;j+DTQmB33i>T*H0unt$= zin_|>>Lz0GQcQX}VW;jTQ z2h3dGCN1Bq{v6{Cep-&FK6cv|uBGD@D+F5rFvs4jF_?;?W=bxF2`OT$-8Vh0V}n$@ zhSG{z)PNw{)zq^D=@tr|>k%VXhWQV$WaaJuZa@D=gM279(<8z!Qf)$UAsq~Yt)3N} z4+^$8_b`|afI3$$kNvz&#jQZPk<{#ulG5|)zp>eRS;C=J;t4n92wK#2eg3=F|LFet z`7koF7Uat6I+)*fO{Nl|bE$e37QG=|Rkm)A9u2`GhA+*W5-+V%22GQ!teA|xed5EM z+;?bY6DpX_h&?CzG)v};8If`aMs~V{@ zx%=S_NUUrw?oGbAKXhhI0tybb{_>HZT-{5wo}!PWf-XNPG%M}2T$*kB=0pi5olF9B zw>s2>yTD&aF7w1iW{OYjO^SZs?G~h2Q8mz9x>5GQs5By7cM+We@IGDmS5B#@ z6Flzf<}_Fv0dhZHrvKW!3c3QSPRv<<8nq~~0S*{P{7G6&PxR`aHr|r(D>P}qx0XR~ zZASF=uD8?=5TTI)v`(~IO-3JbD|p?*5zyOqV&g?+(=9c36Gs6^m1);CmyOn3+=VfA zGXp2m48v-WEcG^rCQvRD&k<=N95N$wp~qmZ9jPPi33Z#oYs7TwWjA0Ee;F&B8?Q`% zZwH>Mg^GdP(BM_r%8#fqMfirlcYnqO5}!b7{36;|UkNPhynA}mDYjz*aud3*!iE()=;0H?pWvRtqd^HFn4VYBDVFIT0>*f8ICLO$D1G( z(P|N!WokA%fYEL51&1DR++Wnl*EQ>8Ry*pOLWxyRBZyMFtO11FdvQ+7;nn?{Rr}dh z=P_~TTa*Iy6Xs~JzausO41AULBpq@n(C>=Bm8pr@ZPUb|+GI=$~s>Y=PGU@SrEhLSv?Yz16>fv@!Mc3}r zwkrA_OHWD1Av1NB7L^z3>3w{DZ=SPtP!^RD3|Q1Tby<@_{k-Tk{_7c7MzJz_zG)ZF zr3J>7j~NIzveiysD-#Clih^1iO5i;8e!k3K*lO;nQhp0XW+lPjdvScc9JSSBF!5#J z+agf9CBjKPD*$n;#QeVF>vw;`)cf(`A#;2>yS>owEZ}rjyyo~1Zn&hSRzLkAt;0b< z0RVG2ZC&}Yhw>X%%j{50T<3gEIQ^Zuu$gLjS}BaxbG%|b?^+J(nxZ${>kQ_D&v`D< zey)PpgyPx5*2~J^iOj!|NFLEpyRPD`Sf0v8)+!M;^ykZM5_I=@PkE3u$GWvu z`=aIw4oiQ?l(v~#HA==3ZMu#s=!f0Ty9-u$0 zH(4{N)5<H~}dajXODP?k&W*I11> z)!CU`!*_PN@wD(PP8-ja{i4=Z_BX=BcAh(sc-$TgJ%td{c9><{hJOF;> zT+;&qskXwA#V5SRV(Rh`Q7AwS_+u}zt^2_H-S2@nGv$I!h7ZOTTn}!Ry=2X5<`7XrH4kJ-*@KnIA1iy_Wv%+D8*i(2%``i;@pjWked30Pv7puInBkCT$dM9%}^i6Iv=@Xwc(Z9rFBq_5NNW7nF(o0JA#Wk%a`k9DVse-`C|nyO0|WX7YZ;4H6Yb*OmK zEUJ^#3mGe~+vpsjE)PiS0;R}GE56u2;&rqlX%7IgvVwns;T82Xbyc^CQdLerJdb`X z|BdLXfkv_6O)InK_?38bkBYO2kBBZ|3L87Vw2n>KlONqr7anbt9b>ApTKB4%&YY*F zIYJc=px#9c619{RO-tD^c@ZA66UgoN;pLiL-mXl*(72+E3r6MU0I;B{j55X}&~4Hk z2~&}>H`@r%=OY5&+)#AByzH!OHxstG3^!5@Q1UmQ?HVKNS)K7Cvkr)AF@+bbprlRb zhD4>rBpg*n_rYEK_#hjMv{CU!?(h8c~TV3R52HBm7+y92-lVix(sb_spnlT9@?9%T)2I&*7bk3 zmwBgzkYO}ELm)4^3dJW=28OoCcdq<4Im+1Ly`wS}IdkpMfVXe^d3Uo;y=BWW*TtMK zUwCXid+7x?Og3x!NUSe!oUf{C9F3mXK^*J)hsUVD398q@oBX|=3r}&4gdWyL=815Q z{VQ5AdzO;(3!2nv(nR{t(LyUN(-OfS1#G?EW0R6Qvo(lo`8wb={Pf3y6wCvl>NOyA z{S7b`!v>XD00z7Wae77?$I=fu%SYzc08`Ark1S%~eV8zQY|q6n9ooO8yX_GK?=zT- zF$!9mrUTUtY+CGzL93B;sX4 zYXuhsHmo;a+~7VC1RqZclzG>XJ8SIZD$TrnRG)dkfc@R`$*H*05MlnssrcDiKBFT{+kW4|B?BPvokjyh> ztis~?HriftlTdH^rF;YV>0#;+MPI~DYG)X_V$ygD`Q$1`52?>D={$WZvvx0WDzbeg zZa*jjI&s9*0H-8 zz;GTRj*_ltP;5}n6lgCLC{1pQwKO&?DO)Y#uw2(;O4X0pzZa_OG07DCgPVW+?3Y%R z<+<}qsp^Taw8IyU#uPdj?hRz*Iujn^F<`iIq$p^~=g*oHB*3A`9?&hK>UlE#Wm_v$$Go(UI zIiOp$F46Y>tf1->DMFhb!wf^!mJ99IbvsUIZ6Q(wK@u-TH2fG03-hbctv9HkHQYGGI&{~e!wVXpi2MyiV zRU|m)`PMI3a~nt#>OsAk=!RUAIHf)f%Y%ocU)+~tY5KDWk7VXX!- zmd%AqnXV@swNMPxEJ*#+9_qBX0?gpbc7>D4^Xb#friZPKk8G_+PsU1-MmZGR4=L3Q zWzL%pIF`3eC&KT@VIdiBmatYQ%G;pLc;&AN6Pq@&vgtvG7JjjU4i&jE3`vpN&L-v1 z0Rq{nYR%g3THF8+ogkB4n53h#JZYzGZSzw}$#mvS2(faKc^2;5ezwLQK&XWI10E_j ze=_PuyI4vk&A0eWlqhLEOU24LVY`o;Vk)4PaC3;>OpHYtCvg_FZ0_idgkD!r#;#5H zfLs1sQt@}NDmuG-R}b!0jh=NL^(X9KWsAP0ilujz1*FF6%Lvnz-XiT=k>kTI^X9`Y zi38qm^&j88qh3L^J9!ssfYVQ1l#H9gFiWs#PonlZ={w7pv+N?cMx}0X+rn@24o5pI zGM6&VBC z|EcdqAB#cdcKi}*CDhW?OVfNws_qu}4z|ojlo0s0T;$*7IbPFjAW(%zy95fi6qg8S zi+)xtAK!iCBiPsSo?l%4o?I8)lYhC)pl;W`?tU^U^U3Y!tGk@0>2?|wlBD8VD@ zY{~y2LVLUOpy~1_pUMJTQb?_V&{_p-55^Bo?ms=^zBe-&24U$reHM+UPgD;w(w`UIWG3 zjCh}xbAi%NM?IzBY+qhnyo5`2F$#1@Qv_{)%qLGc&A04i5C*6QNV_&RcfPIy#mR4m zWcW5aHzs*yhf37GeEU-V3>Q~tR$vyVb6NtKQ)gxes8;@T)Idq{lq|g#+M-h7Y`*l- z=MFa{FXK#3=mp`ZS~#v>pJOG0Hs`%R0iz3{yk47Y#iU!GszT?s?`10;{i{|1^xg!_(N0MY zA@1TYugYaB6r(D)mNR6dGV^%xPkIw&>`Gblz(F8dcYmhQ(pEFofm5yD0yzu0m}MH) zdX?{L+8@>UMHqiOckprVG?QK7cac6W($qGxbG!gjYi=Z|qF3Tsw$taIrQZn6=o!o# zP8GkWnstBmVZ<^0#nQVu=FLtGwz=8M$gh|IH%=O8ZtVLchPY-(QhOXyW(l6R_=jEw z3R?gOsF%Gl5=IR$TW@llS6P%#&5CRoM@w4WErU8Ha*HoyLCC)Y4V)B6Sr|_#jVOX* z+WC$qGms3&W*x&u>Kg}rURrhs$~8Ye8+%|S5CVFywlB=w70ZYQ9GS7jpdu-8kBtQv zsc%h+B$;@nu!e;__84RKL;N+~-Ynnj3HAZS)1C5YTSS2}Zq7r_i=2-#hBYiS0_x|I zx(`Ku{aGVeX)u5M*U6CR*6R`u|mRAbSdAs zo71*F8|kT2FZ#?LJYM^857CCD^WgF5x4juX=d2|WuLSX;Yz94FtHx4^nS18V>tHT(0i@G0RXrf;exkq#iL4|K`enya?l|Z2xP)G-zVnX&wlKoLKc( zvIweT0MWh37K-{j6l_~0j4B>-`U$n&@2q$!y;sjrufyX6fnDE#?^dQuE0IhDAS-S`C z8nGd|W!51&@xMvw{|rij1{P^3m9zlFWmQ#lzZ>E0s;Da2<-Uw0@=g4?OM7jb&LNqU z(87%a|Ht0Wh2GHwcn}5U3RYC5HqWtnvz{Kb-1*}_w(jCI{090VwXf`$zC>G*>}Xle z1Olu*1r5%~(oJzLAO#3Yuq}*N5P96`raA>Q_*SILs?(hc@v|oY>magG)QM%!MjiAJ zckI`EDx;~Db`?+Kn#j?Me6f8$B%9d{7hj#)Te^Z|(b?88)Kn~bLk`WqywmaM)ov-j zF8}*{eLa21R$errSOuYX=vs;`G|3(uDaC4QGUXKkge6$KK?aCMZt|aG0>e9&7Pu3! z2!efAr~!)}3cMDeBl(o?JJP`}TY=VUDi&>{ON|NAqK09ivu8nw1>_b7`$eKHLjBi< z1^A+B@;b*-PQe-r0mV#XpSuT&(n1uNo<~>qIW8Cci+=u}J_TrZ6<@4D!GK-WzeM?_ zJ4Nsx#pu9iJS*rUC3+snHvgFXf9v;SPPhVRN9Bq*V z1QaAji$)8HDJrR-xr=$2Vbeof-_-gcJ}yE~Zu6MlkC6B$WdD0I$p3mWZCI}{r~L*b zKrTOoYiTYbkU+epr?DxxWq)b#g>M=-N|v~8Gw1wwJW_16WY;M^xY!o|c#h|}CLzefHDJE$hIXPM|{!z8L4WiN5MJ@xFz*R6& z_E!n{MN0RZ)|im7lrB=X(ka&q20Kv(nm0om=<*c5iN~b>q}VI!X%g4_=yr$h-zciM zMI9ct@cs7>DbQIB@)xmtd_sG!H5wt3CggBW%|4dJ)p9??zA!~esiN8df3RQ&1%TD5kBN{0i)}wK zH+_ySkQ1FGgslqBsW=0y)uA;D?D-h1muaJjA-_H()rD*ui|?JqCxM%gM%+Qa9T7RZ zw?Je0YS_Zpq@2Qg?bH#V`K8iiXm+zq@dvaz$x(ytdjr_t-~8CK1O%ezTnYO@+@Fg1qLM4+Rq! zoU(j{+;Zt;+Hp@WP={BOl8O>8@SZ#djM-p5(F#Q5pj;63r4bI)u~OeCLHNWrPs%xt zQhvMSoH3Vw+5s?un`EXwm?5bkNA2F;9{wVoOzD~z&StlJkYM%>c6Xh?jRGgdZx*W! z5QQ{;pS^5176I&`>Zb9A%$T(2-}<7wG3yWWy8A&B=ac8Z)&&vqp+5W9f~Q zcEPz&_nmF(E1dX-cfAL74z+f_~brrwXPufdD53s^jQ{a@R4x!uidW6Y%z+%oLLE!FJBSlpX; zm9pTWsMM|sh?i}|afI#!>o+=)NWafl>2bwX9%L?UN6Z^t$kkQ?tB_XEgUOQDrQa@o z)~;x_Xd4k|LD*sA!6sH_Ecn+C6386xr}cy9K=l7#N7oX9fg0UyZ_OT@Rl3(Y@0le% z3G@2G?{cnGkreh!300?TxzJl>yS0sDGXGQoMB8Oy;9n^=hhMBPs}7B~kbYKhPlTyO z#f}_1X?tH|16yiA&Xam7%UtY76ZrphDw)y!4>j-)$`du0A5vpvQR!d|c8+(iUzbZm zQ&H$pt$|n8c-ImfR9>+AEp5m8dXGcst)L@2S!|k>dV(neD^DAw2TKR|CV@Z5sNeXZ zT!na4xH_gTjvXjXsTaS1mX@`)`Afy^}us2F_&% zA`WSPiLJ|$d{MZkjfSj!rSJ-k}$P3DXhJczA!^VA8Qh^YIl3@B#2};@OtMmfd;%yDi zf`2s36xOh&`TMl;)CwDMXwVnQZxJF&1ErBW(A~@50jQv?MzH!TzJt=+O)3ENzcKGR z7qahT?Fu9#IcUqTEl`hOJzmR5x|%)`>Vz)wU&69fcJ{GF0>VF0S!pb^}vJV~qr6ne-)D z-~Ba9L0^W- zn~)d{-BTtDCg0b>@q=UM4L;YgWT>m)vZuyv!VM|D7$=Z*S;+2#9PR zOSKtVt;r`u_OQBoJ*=GoI?a9+ljZLqB|j=onbSw+(f{2($Gh8HW~*(nl-B=>RV0;u zw*>(fiIO zxI9{KeUL|t1T{}lJxTrKqv#As)#%+T;fv>`hsW=+zo79@&v-Ya8P9H3s_nh5>tou zH?-I_`fc;_sM$wG2}faYyM@_bo}qe|63@~S7%1ZdF!L_#Zc`~AV2k+nFbyKSLi1XP z@WvBA23=9ng>p=Zi?CBQ$k0o^17NK|Fvq*b;112nF(RFS?iib?>tAHPLs+ckOiD!( zgnLES$EGBPv1Ds!8_Z#gI^x?H+^Hl3Mw>4G6>;QME z&m2FiT9v{r^>4VwbNN+$Z&4 z`ex!I@#%I*axKp9hMXj;Vd8HdbgGA}%(0xEejOAbE#S$f@e2p4P#UkIz8sSscCBJf z&Ul5x{gQ-}EDffS%26e6&{70KhWTf`H(kdku>XJ^>RbR?Kd+?d}n9V8z6&G51Vp&pG~=>4MZ3Rq9XOLG-!yUyxOqmMr-&q^tmK zFm%e+)?k|CxZxJshW+Gy5TDWahtmrjX|}?6Tj3X`)!6#KZsCapv%&K(^{-v@LDv>2FU)d z%$akg`Vl5@`^5U7;y=RVC0AL`Y0Kpv7}RPdQBUu`&;B|pyx$J$E<2aj$u_K*1(LZC zymmH@)zZJ!g|hkPm2~9DK73XC`sUU6&STL3H3Y67n=iGsgN8G7u={8M!||W9!q;nv zGicc54^yy7134IQ-LUTQ$R7wR22fB^->*z=z&m$xBHtvbSE<2BLzKpzl5>l)|M95o z>RaHQ((8WvwWW)^Ya@K+dO|h5H6;-J*7iL}uuOv?_?MlTrV08vF`v*SyS`&~nT@&x zxE%^|e^EOg)rQvKuc?yV>#h3R>;1k}Ufiq}C#ik}4CFvOSoRf&vHss^z5&B=eP6hvDkt?vPF>omUCnzR3tYA z;>@Hp9Xs%|Kl}Hn&GQ_|1hcS}i_7nwQRc&KiVM;diVLhcGg9k%j(T%4L78Ax{F8YI z-(orRNYGCpTDh1 zlS#f!1o0V6ra5yx1W@m4wN{ia)kiY#9ydUYlvJ5Q6Q&kE=2N~vnhiW|&SXv%#MiKz zp8w;^p}_54!8#AOCdqAxo}82i&%uXPQ&^HRD?bRM967W9`)PKT!2x&@JJl8HFU;0(2JWnt5KOs;eG`9CT-|# z$Zs!*SF7L6sz=R?zj0q;X<%ieSooead;vj=5@XNCY4kMNG5)K^>{RnPtm#hF?UIR1 zEF<&EaN}HGOrx6eR}3H)44^)^8Rb>W?nfBFNiC^VsXUdb`CZ>Sf=R8TNo%D_PpnrB z$Qi1I_cNL)(WpGK#2=fs(L=p)uqsT^Yf`h)6&LnTbo02+AA=%M9ivwxbwm?+fAU;p zUl80Ot+KV;ds8qV&nMzWjlyc*fpGML@F&!}lHV^989}X$`r&?a80%Jqrg%maYuujl; z$}>Xp8~LXd*&jKrM2D8v!{zt}*wQ!|WkDeMn(E^IIjZe&I@3SAAqMtf$6#WeEJrvo znmwMS(it2nd$M(6C%DnsQ_)<4IEUxHdVgWsfq|fif^VvY3PB>Ws~T7&F(cd|vvL0% zSIEn8FD`QLJQQLTJ{%$6_qL>wdW@mWJoysW8+1{oH!Eo#0%1$zNK$mCk#)=zF{DyC z7Y{O(82>-k-UF!*-`+@aP%&3!(*FiE_we3d5I^7E1i^6l{*r7-c|90$8vgWSy0 z>?2PgW`i|=?SnFh&x_d)rp&{OXH9-iFVM0{J_8_5e{nH*{RWnjl%_p*n#>l)NtM8Z zaYY%}p5H|IDuU{OM(5%4kb8omPbN!(c>DT0ymj(b2KD#Y&z*8bP|`g8+929S=bPu7x^MHxw{SZ_6><9RF zo#6m0g0A9bxxPM6b5+8l)PM~*D$zFhMwV;WPQl`y=>p3qhzPSb5z|%8Dl{jbo%qgsAes=Q5?BaIh_zjDs>AVkd z8IcHO5{)tspGFyTG@b1aRjKP~5xq9!tDMoHp3z6ip|v(k_UES6uFMiCxTg?BBpM#T zPm$#@VuW0woBhhurcaljCMkO#20SoCeP_DbhkJMpIiOp7Ucw%E-g89C+gi-RsD|UB zmPZLrz-V!9SXD{?7v`>|+tT$^58pdRbaxHZwp?AWz#xuXp&q|RFfkuW+XhoU+>4lZ z7H2UiG*7boy~A+*GPJTsoiXQHn#5E4Cg1~k&6_V{cXUm^;WwANUvrq$UHEyo>VRz- zYZtDx{v|v6(F);dqUpLrXVLnFT<-+5h)c!mnNKuTrcYmU@+0Ryv8!_B-i%pAZMpq0 zIH+~4l6sT0b5+-?d&TPMgR2SPQlWMyDoaSa7EwS^ z=qM&vyBs%K>SVh6ZE#H!$!Z++0D4RRYEZ#~v)uCp};1jdcv6W&8RmZZtC@*Oo(&BGM!p3#`c9 zR3up5(fh3KoR<6me-f)fZ_GlMCyN@SdM!oI%h^X2KbKI(hcOC}q>t2;k(WvPtLw%c zsQShuwo1ankIS*4+uwGjv^aHC52L?-;xF)>84u+iB}Un?9T0?8?;O7>nETLbP1(E=k zPFN`ab;jOby>HKTWaaBD2bSLqp zu6>YX_~P!IlFowxGifh>$W9sueJ{#(|Cm!B+Z9c^h`1?u zVKX|vM*SB>W}TEk%+R=img;#03gaIwLai8~_|8GK_oI5}F1@~6$&$xi3kbR!DJ6KP zUAHiIPZyUJQ+6%$DOt`5Trtbzoa5I|c+BVW$Xwo#HUklzT8+26_pbgXYM3iAfX-lV zEGNs5ME&xvDeDVyRR0u;)K!lvwsx1xq55LzVxm`LI?M$!HZz7+wU3@8)JopALw{ zrH1jn{vfpfI2+2BA>Kc+=lkh_HN&G8CEPc*^D0RYaB=p%TPQ|kB_XM+aL=ZnD&C;mS`$uO zZ7iFmv1}c&nLpeLr=QUVzB0%AIj`_gJs1D%SQCZzgol)TWi0ttVIVvr*e_Qz8X}Y| zC5(0HkF0aJTz}8mtTdRfKYi&T`?DAEqXs{toT)sma>}Ai*I=B8imE@+Rx9_7xDHmL zm3|a+(Lro1cSXXc6UlXM{1Vj_q9FUrFQ~Kg9pAvCY-tJpR$dxxe0neS4TjPs zm#se7J~)WHrs`r4!2~=u=xXjTs=098zVGG0YkXRZjm_uEu~Ura*hK@S?@JuBuNd|X zLy~Pzdlm?$T$}kwA-ER&b9aSgQ@+zH3>zl-ocu<3ZqurN?^l6Oz2PZw9-Fr@eNR{J z3{3WDY)-ILzqTrjpN}Uqe)hEHh2}+p^gR9#A84h+uKI_^ml~=74&AyE32m3?6uWO) zau20y*|2W?^;o};jY9o`K~qg*vyxrpoWawir0;^rYln07pRFf&f?5xhsU-DW7W``y zMXuMjKgORD;>_AjmpeS9+GaY8PA5~^+8+j*M)JAYteYK zH9@&WH>7L*RCp%WfDR{)EFu2F!mH6%63?d2e^`kyE%%(hq^!EVD66!WA4V@3#!x$R zRh_dHy_BSpoKv|tcHq-xZ(w}y_4czQ`xBCyqa(953gk2y)-jw6`6B03w-q9@W#eFDngBZZ)0R?=CHkzl{UKVdDCTkblvMFL%g4oH8ehu)T&s(vmH)z7{Zqn1R(x!K}sk;rDsn5}J#wgU6T~#a2Qw%vV_=VudXSp5&%i zH$dMLc(cr;pVYImD@*EXdX$*`l0ZDgUVXwtPle3Uq+oJt4$S`1Ykb3H;&aox&X4uV z7ss<7El%A_EqVB2HCgb_?hoJ`;wS%&hyT;fxOx2*;JnGL3v(nOx>1?FD(g1?P1%Nv zcihXx<4<**o;qL0&eY=<7JON-FKTDP&2Qgmk8B;*9ozmkf?+*QvG0|$CNnV*fTw=l zeL%>?&5WMYTn?@o8T;t8G}*<{xNmH)emmIHB@%I&l8;( z2!P*ETs8ae&bpuXx3^}0_O78U!FP*%z?Z~dt}M`XcpmJT-2Q9C2^JQKm=Fhwd)svT z*q=C4ari43GFh(4B_Dh74FuRbwWSQVNduAX4D$s%y0hBn5Nv#Y-%h#PGOef5;37_K zZ=MKHJic9bGp+9<2d?-wyhS5=3FCeZW)mBKp1hP+cOhFqi%ph+;?MqLdxgjO*XwKA zsO>{tD`c+;9NSE>l!Qxei5Q$CjYo)SnOC&3&C$_K#9kNKGg^C0_Ao6AdV8Kj&4#Cp81t$~NWjO@6JSCOZSZkt%dja6DD0Nk92}`_@WE ztwsOeix&nc57?4UigSU!9nM`#bsxtVmmBGuM%3MT3BD4-crAAMpMNhdNDfvybN;Te zUU>Y;)$-z?^emuZ?%sJbgf8eM4{D7wFOg=-Bag*73J58C7mBepMYj4xNcdHT=S!j%4Owd_tn7x)mhmUR*s4wF~RRzc2!%?O9v{w#ORf4 z`BhWoPj~(rdsh{C+nTnb;-$g6UBOAWw(ZdNl8Pkt7ejqwP1K~UJW#p8)wo%AtY$>$mW0zGe{0_Oe4+`DlV?WDQAew51sVpaYC z#U8JprSQGB*C^=UtW;Ks&=sP-3)sh5x`zGdY61+C@T}^}$GrAoN`d;X*Il6MAM7sL z&?SDe1K*SFZfWf2acx0vLyfIgYvT5u7mw}2D9=x58`qr#m9@zAW8Fqy$J?`g>?+Ht z#t(j6VzoyWXiSulE_3u;SkirQkNce`o;f5{6^Eq1O>g92>QK9^p#)r=4cjOWwVz|! z1>#-w?ix|vHSgrNF*jj@+1v4TTHJ8_fV}ah;x&s0y})F^Lca-&L-paZMbW6lf#Ayw zGQZmt1<$2+qa9*1Wc!*ZDL?a-S9rgf54WR3?;;grc;eDuMp{`&KQ=aieDEOu_(SN& zUc}hyS0FG%D)u?~)Bq>lh4d(YmpBKaU+*!2;aKXu{PF;GZ7dJ9&v{_VW$Gcgc-t-= zEzL`E4VN*Rh(hfj6u;BIOX&twd@dQ$Yl2l5uX8-=2;MLIc-Ko`K!{%X(o#AKI7!vX z>@{S39L?Ya$>8{GZhemw2O&u@#pvkpMjm+iC#b%!g)0SE)7Kf%qg+iFl{IColM+Q< zP}(y^v0O%B(Gz3oog`p&oig3B{g95Qee*@iH~kC09^f1GoU5GiWNCp((%JOwYR^&J zWY10Wo4kYgyy9WbtQV)A-%xx(5OI!74ir2mmNk9L%I8+e7$!JWchoB>8y#Zh)3w{d z;8ZQiXce&A#IRfmq08&=3~zjIlI88pcDs{1P|rC`sd!^8M!vMtV6(WDMkUsOg>^+@`OYBTHXT5L2*k=FO9t&U%sMn-!7qJRH%J%SuI!PvY+gjpb9`{n68n?JmC|* zPZ9_i^L;H@!{zq4Tg4<}!JkbsHhDVP&GczQUJZD|IDe=pWr?QtU+?dtMc+mt4DGbl z?-``Xgg!h^vC~|!8goqhvmS?J7+go`&xHIunt^U}upcl3Eu_s_#o&#m4fw%KT=1XG z2<$PQ1RN3zu%zz=SmNY9u+!UeTx@;VQx5D5sI_dxSRXA1Zosz&s1->Ifg8ZV4_@|@ z0|tcy6!q+BA2#4Lj9T#^Z(s3%X%s7EdMY%El`}nM8$Av9asn?=lrcR$H+t&(Z{Ways^dSYC0mU`LWPIi0g3)ta3Cu_O7J0N`cH zHbJa7!6xqNXe%8r#C{qw@Y8bFn*l_twS2I{j7697nGC44PqD4)^(K?2|C_CYkC(Vwr*(c-eR;+_1gm-0;z^DFr#QS<3%kjyuGwCS$jI5m%< z+t_%C9cJZUshw7ovMwF0YESY$4(*XyIC#|)@LP(QdY@hP za`7r~}CwT@J?`%sIJP7K7^Z&*CkV*$t$e|)W{h;#}Rey@ji#Lv1BK+;! z`Iiy$O*EQ_XH7J8fMAWv4Q8eajwq^_@%WB6s%|OwRmt=hB*lVNwwRe(I1Z^|u#p}4 zMy=P$B)@!%x%Kruxktx0t@2dPmiL>?USG95Q#kuR83uR!lBjo!7V9!vxqfMUxbR)K zuZw{y%d=d0eHK%F=2@aqT6X#YLd z+?3P2n3aL+NmTd&<`YCAW1x*4S%87=#5il>+W4T9$1##~s)A~0^DtB2PF5mq9->%q zP{_2A!a22R2Cqwrq<=yx1PDs58TPQdHjlieX)AT<|(iN8$jbGQHOft2)eQoMc_iJebb+y%@He$RV zr7O^BFV?l)*0ocHqxxnjDy1vxWPeSg#)nhx$Iv#kPz*^`?C7MlL@j!@)yY=8)=Bvu z)fYIvbM3H)>p^V zU&j+%{qGAec5-N5F!ML`XzE?2LM0Xc42$}t2#z)9P@906gHbzUxxL5!{rT#jYdsFg zi_ml`QSGo)%ZYb0VwKWz^KL1>%*M?{(|tv?qf-}MzH6sXtfqT;{9KK7SZzpkH*`dD zib5vdSxQwF%)b-0cSudI;B~y{_no)n=M5>_2=wX?>G^FN2Y+ORHSW8wFQR8qxFuHlkfl*_QQy7k3y2^7NkCageC6h{PdR87^|t=QvIdgc?)BAf8>gcU>tPG%L66u z$c7t*Y`14FO5%PZRlNK}YlBloQP6owM}P&ZEE4M(vxKxf8Fey3W>E$z7(Q7B*U#$o zum4x8;(qZ_-!k)e)hqw8o~t`S(oiql!V#TZXIkAUv@kS6yxWM?bKenh@wr zTl=}gaJ4_YZzlTC+~kDcccpF1SOlQ#weFm?1nniy=ZD4PKhEr9!FvGxQ2=PCw1h8~ z+aQG9_>BPJPav)yhHG_32Yw6J+6Z6ozfp@}HT`|F{t{$b+1NeG94{m2MQNGJ?UC(T z(IL1)Nd8Kmd6RM_{%?#TANu-P$mGvN#r<{4^=CI#16sv|-mQhori2ez+>fE0ueCc# zR(1Pv;a;f&Wbb&esLBo@Tq*?F+Z{}(x8uDdESL`2TOS02{QJ zdi#sMC92#@(5|6ogwBnCg%h0O$`phrS81I=cRil=Dg(- zrmj;ICDM$eqhqdn6K6i?sA`|XG|So=)VDN!TX*>u2kJ6#m?dpTcM6kAF2!0Hk;f`4Ob-s=U^&CjvZ-27smLb;EeU^=hNmO zMkQ?0x)L_g7qg#di@!7&fuYWe%VO7Pc@un(bdeYZJ5Mr@2@mE$DJde*+1Ba|7~#Al z%%wr@9Rb2Qv7?P%vIWlq-k9SxDdz=G-elDi-)njATTon}g{v0Zs@+y)I-Jt@2ucP3 z$bOtL3y|=gk!H|AUy}=vC39XV_t#`Pbj<)qNHZVowuYLYS?@uM>a5M^%t{0;960(C z`I)2#N>&M=ggBGvL8*=_=2$!(?7Uqhwly}L1hxf&mS{Xk#y-9mX<70)>!?D!zN6*5 zP~i_DQotZYKA1U?amSs2!>|$uq!p+<;X^E5ID-gz2k!0x2QX=^)h z@c69u7ngT<7%_N!%;g-^Yzx3EVuI={ofmr7G`OsV;#Hfc#X-Xo0TM9w-kA>|5d=x~ z$M?cx$)Mp58a|-Gpvh%jCIEJZpSisUTh2zANd(E6B7ZiN${9&e1)8UA&)Auh{uw!Q zf`X*!%n1S#R&_4x^(3*a#bN8IZiGJI&SkAls*VYg-W`+pS8*NuRu*SLXQB71K!X7j zY3+gYPVEY$5&%c(qWbtg^L2lm@C`ZPL_udc_C(Ramux(adBEl|1Pz(@M_7FLzAadH zF9v7&g)0VUoD;;|t)C;u-K~>ze5V_d(|D&_Ehp$sw@QxO9sKDdQf3yGb~iBeM0*%Z zL6p{0P-6nXG1k4T6t?2TRX&=bvJYYHNto+39s^ARaA z%lgJHB=ki6#xOetkwZ`MPKu`nM@+E#Ff|3Dw2OuV(bz?E<9`bP{|VoLP>2EX6CEe4j}IRV4mz4K(HFr&>4>7f+RsuehCFaqqvfqr#CvUIT>JIFb`{g`44j z*4}!u2Oa&Mx`b^$*lVr*7ir{DdGo<|fE+!*HX( z=I&<$5@fjL{{evi@ENr}y!$@x=|2F!z0sqV2^cGAF4)f_+#{ zL%B+N#Fv=u&3SFhr6s<&155fuZY)zfccfNZ~Kx<`)6VX*+_9* zIrIz<760tEtwe^DGc27x++{M62P7PK($gs88<86iH*Nk1-vDj?=I7-%B&k>}#W=sghGz5S4wl=JOdca)Tmn7s$!W!|2kvhPORb=#Z!b- zJrf1b#2U%T3ogwlZRN`H@`uwx2d&}0HGuPbLFnlHhXhzdK8 z370U4j{`ufJwXj4({q_kWkX#t#6xpx){T~M!nNc25jcv&l)qnBRBlnNKfO$1`iJ`e zTJ$P!BA`NNlN6|$c$>&s)jd_L6IEN(#`2gkk{|n(u^$vJ!68IB7=)>v*9Btzx|vQ8 zPEpSVXOsai?lVc>gQEBH(lH3SWg2k=+cJ1j$!M`&DDOH-SBMx+ALXjjiK~^&+cAz4 z+dUpp2-`E>Kc8kS`dB2eF@^8T$owiYXGcDGEj4FQUfwi7-ApIqtNfi$T|Xq0xb~&S z^YOL?7P31P`mwCUQNG%1@Z}Hs5zVlkJQa4CMNIYv(i?|y)Rumho-9j6R@opH zTBU-RZ}lF;k5*BhEKHBfze%ohR}p_Ug^pftCUb2XoRz{13#`VpBZmc6V%jZ+1(svl zS%w7&G40dPfOTnDzLkb2%Ot>Y^YqrS?Pgr7;{RX7=&9`;@zLo>>a0!y2`lRw*qvz^ zs#31GQHiS9Aag1w*3BBU(u#Vm&p9)-uRtjTBlE@GGGz3+doe{R*Tkq$RqQkKb5*ex z=3tg~OxA-UxB|GDT4CuFM&^NgI7WukeNAOWy~+p7%B<2OjLeps{Oi%{jw>Qnv0~;r z**>o)0vA)lV6h;7hh}ynUC2#TUg2dxv6v}V;CHfj z6Qg7MGKEz(DO-iBsLHqP-YW&Ol_H|GQboPB&> ze$N#{jDVTP%TV|?k(IJbtYt=wQm5_-E3ZU6KdLDpIk$SUD|V)jXWrcZSevy0@l;Wa zS0E$l)}JG(6YuT(-~8eS{*Qn1%Ouu#TA%vu@^`g0jL^yyG;M!CC;Md#OzfedGLDVg zq1x@T!UW_l`Wy;6>;D+n=*KC;&^VL}9gvMhb8mFOgaUE#X!;G7fr(ADDUxO~5hFpnP-@{bD^QA4oF*@+zJl#8-cSk!+dra1muo{SCLCMZa>)^`#gfMkD)LYnkAnpe0(rn!mT((~s#(x3DDihZ< z^tzJaN9$3mpLV~jpbQ$2ebQ>RikgC5YZ}UiE}-Jkt{X5y<0e`G)gkML|2SI*;oU~d z%hdU;5Aii_ztYCaQWHMR`ag1K}oUYqOlhg)Es4zB7QwyQhAMoY9&airTAx*X4DN94A#jHa%Ir5)YT-is&=`L_ z>LN;QRvLn<@Iwy({=ZS%u#5oQd6eU9ojR`0PiVmZ7pfO#6@a^fBHLKS_%EV(P~Eb| zgq_ApNSD83oc1c}6&n232Bf#pJ~HJ^Q1GoV8`B>XU*f>~binT|)o&8!y)m7y|FS@( zU;`DeO+X2uLj2-;n2D_Th7ndUEujc7E!WS|5`drOd0cl9OimG+QqIo@Jd`;p_Ath`L%tQYJ&n7n2FVDkEa z$y@geH3sttz)_*hXEpFujfsQKfxNk3UP_~4{jSJl`w?OFvwUD$UTOgL*U5Hu_FLu8 zqRIrLV#9z@shkBB2?iB+78LDSP#?~M$_0b!KMRWTET{x9C>BiJZ7GjsSP?K!P>u z4^lMc&g!1R*fVk}H^2jF{iWkuwB?2y@aL^HeB<=o-(`|RpN@@fz9k9Y*TP@U$|Mkz z*#)?@aO``zpIoVqK983=s53{yt}RY@Enw##s9rR z=!CH7sfe7GeL{O#Kns8~jag+OemUU4Xy$ zF<&!&fx z+)Y^y{lsg)pe3JIZ@0`iVz1w2VEm-|({g@>zJLDtB36CYB1?Uak&I7~MbC>MJ|lFl zdqb}IR!=P@>GfyCl62S7H9w{0KPWQK_vU|r*8>jouM{PJQW(CkpcgizOU5NJp7TI8 z%$+~?fs~p(%SDF5lC3a^fze7UJ%91~x7;rQ*L**fgoa&A8gY9O(^Dk&Rmt9maaG^c z@?vda$qlm1o-+6gb$eskXVlj`(rw15*3)bCHW~A7(T+K+e ziMTo`oiAg3h4@PA{$;J}M_I1R^dk`cN3!Zqt}opdeH;`v8^x57vz%UZJ^!)W%QrpB z;&Kps5vGiSFLPHBa!oXle-ur~^zTTB+5At16LlLEE{Qu89Fe;%Vqt0>lH!lJmfY?9 znMFLgdZ!}4sa|x0rS>e1ImPwx^he(M?rXz!GuSSiT|Aq|mp}Jr61`u?)3_{?<@ZSe zASoHfM8_z#?sX{oqCwdno*8Kx}Eoe6fK@{`mHy~*+o}*h{D(s# z&@*26ZeqdKbxUMJYd-c9UcQru-8?bwhm+g8?~PsW^ugUDVPkt;KzsQ0{`;sIHJ;U# zsNLPyV!Yoy+m5*+|w$_ppDIoipits8tnK!_}V|^ zAF|s$t(4fPJlnPkv5E>YjtbBNpor3XH~#t^&vz$fRa#pyj@&4bwD^IJTUy;tHt%@s z#6(nSAEe#NrMNWEad80jlZfPD5vFAprZoy4su_MS#6zDRN@nurK zc<;kVhFNt%$=-00ynS_n(HbE(_;q+ty@0=)x6Yt8T=Nze@~?KWHj4XC050=^{70f~ z6izQp?-V=|eeg|Rb;W(+doGFu_Z<$y9Z721FPW*Q7`(1QZZp+)#q)p}t843G2)rkB6V(}=XoC0dsw3BuZ z%Qh*0TNrd0fL_^ZdI$BFIibHWd8C@IO+~q5ieMvQ&jf5nciLwCtW@s zN&XlT+-}kDMr`^e-qM;`0bs{Z58l+GC4D0T+CM$04IE_N+nY{1r0Nx^waTdI7In>F z91^WuM|tUFnhNxpoTgR}zQaAJ^jEMwbd+FqkgH7HMZeoGP8IKc0_ccJtT;9X_LZl#?s*qT{JGS9@6emu!Gg*`!qA~{GIfUDq0vlExbJ1PsOvk%u{Wt&NJf2yN{Q4; zVF%x?6zC)xEUqDbuBT20=y-7>P5t^FuXfXI6pOA@OHVmF>>o%sI6LUOp*_I0Ir=8F zMBieNV9Cf14N@ST6N>(js`XxaZd%=B3&en|tp#2qUm(kLqO9ztD50-AONvnNl5ub-;&Ra-vE)`Nbwy`8V1JLeQ`zGFl z#UHr?Z=IjA8=JPEl!O$JPJVS5$SUnP)+dO9E&|=_Xl_8+nXGa_=dR$nGg>WlNUI5X z_~XKdOeQXWR*t6CP zA5`5yFlr^I)n#e{!R-9aCl8&)7(k#fxqmLN!V&~-;BU_W&@=hfndkmLJuN>!JFnRQ zxtH>4Fu+`uAj3qpA@saZl~73_a&W8p*>6qA za%hzKD|Xu2zeIRE&Z$0`K5vlnm0aq+7o|Z)lzG-DD2&*jzv-)J_8G@;hTyYn+d|`G zlnt_iVA*Ku6~pRGxV>AehvoRi#%F-gD!Ll1&ct97MUxEUO%{jRR)lo6o`Zi99>aI|?xUdJ1sT=jKkx&bcb# zHI>?r=9mbuic*{Bse*mqOa?b53hl@f0>Sdih6=j|yOLsj@Lwcdunxlo&RWIgv8yP) z4+d*2M7|wSf+MDDunXf(kO0dz!aGO-<3}m)e+Fc&09ch#zOOHyVX>5f$df->-D!kda_A9(ubDJx? zS-8e3yqUSWD!iGvYAd`M$&5=_zIWS~Om}uaFF|(Vdrj)12iPMjyy3l8!2?(!luqYJYO>+%^ZX9r4YOx^2 zTfZvP2y-ku=@wSXe18#ZK}xU=Q4pY-tt0a8XYEeMhgp{ecURQn0zA4~MU8=l@hPah z_x`F{W*$e&C)2Y~Hg>)6}Zw23turU4>y0$3&a%nk^CD5y^ zN$OIjMI&@IuWs)*OQWXuX1}P@TBr2E;&KGbktx{BtFx;X+f>clEdOUM%9A=M@b?rM zHsfILTkXF{BV^?n2Rq*ic-iFt;Q3nQO-?M<1?chR$|S?AmMgo)A;g=@<>z+=|4qvYzXcHD`HM&y zbT&i~1F`$&B7stkeQ~203I9-8?!9-o?^6f2@UTWqbuI4s5aAZu*y>8sL7xP`z9HeL z?38iha3oRH9Pt@`37oj4WCbgz>zdezU=H{N(o^C&u<#jyso3GdKO&02_(>OJ5`(Xx zAc|q5^Q(IzFpilwqms{SsIW`-rU2-u)qy(`akgg`;sp1y-=ME*uKU8EBKeUPUp&aS zBTl$VtQUJJ=Apiy<*CVl(Ks4}J2fv&ifJoh6BMZ6w=Ki=^8Dd~1X1vmG6kMez&Ymk z;2g6tQ4Tz%P=covVP$p=>(Joaa7JSG4n1CN&-&bZ;-mDh6IrtpOlX8NP$kSJ;CnNKf z88%E}4Ro#>Fz?ZdtRgfm@I0#D;qj*B)C$=DO7T8Jcjh{$V0JynBN^oZ>i2JSt5jgX z-TDK}bJ}Pg1-tRrIT6(K64_3_R9Tw?RHs&FI8WKr}yaAjGg zgK2AEq&NRR8-y*Jg8tbsG}2pm)=;xAxbUfX4Nf@)F{TzU?>0WeajW_OlL_V7`pLc8 z&rPu17hl5N^NJDr<~5d$Gmb4yGxmz)P285??E?yArNUP?GQ*p1!`X-lJCE@KgNST- zg3M0Y&NY}y05vfUygV`wj%DxXc`9QQJd{_ax{fY^Nh|0C-v~UF0JF6LLtNfJf#V|v zsk7K2ilM>akwY|@+}f-`?4xP;5S0Bi^k+bMk=mezY zq#f`e8uzDBaUA4>>BvN^L(GG*L_}ae&0Pf0Ps0H8yp3x-_%Haq7FH`ftMKyZ-;+#D zUQ8YAqM((+%j%9|ax1}2mk?t}quJbucMHuqtJ2;%8WmPufMd}KuGio6#Mb!EX&zpEQEQ6Pn zt(sUO%gUlV>9U!y=StyNh1D!D%B~D5th=(Ky;DS6r>4fz+{`%8^Mvlq2ftwpD(osN z5I&!sqY8z_PgBi1HKp(hwui3PC4O1e!lNS8Q-0xtb6JA?9mcC~OoH`M$n$B)VEC?_J=)zx~Z1{@)v z+YuCNsY>AkDktEu@;k1e9dS4_+KkTz&V--aHG7Y#w{t;{_8?EN+vm7yuwTkPxSXInT@F$ch{QJD$X%Kt2`*;CuNSk(+$dLCT8`@FE0x>OkD zpW^5ams*|)9Yvt!e%99P>GenaO&e+y|I69&@HAE7 z=aFgfrFtGXLjZ)xw!=s`0#Cha7{@&Jhnb1Nm*H06%_!vH9XSknnGdZKg0H|$!JAR& z3AXrp81rP}((bzYB}~I0EBHf{rk;aU$vSCW**Y=!9Ndz~y+cJX#z=y9?Z62a>iOJ( z?RA{2?A7juTzwduEKUr?i~cm=zl;===|@7}pevv?genv?84bPwIhR%nCx?oK3SlTv z(D&L4NC>PR4NldEL&2$f0#XKLjNZg|peM2ur5q93%SdohGAI+w8)uO>1VxL>fNnzs zS=>eBj6S+f16KvzBMc*lfRB}nNOKrk3ik?K0}YaKL=ELY(b5qxv@A{nMTxFc!R0{L z2kRD)fiNLioB~P!EuEw9hbEKBfU>m>bm=H!Cn)i&vf!jL0U3`L#65ytLk{K5%i_pU z$xy37X&72SrWQpzR5y=&jkd(yPa^6rqY?+|Hjqg4duYHF7@Jf<);2~OE`w+tqR~TO zhQQx)ti#})zw*O0R|Ildai7rvh9~Ifs9gCo3O_;Xp~__B9j2iK$TBZ9L8qs`3BPDO`zTP^V3Cyk17Z35BWggltB>lKv+L|5D0&C& z1tn|wxe0!7Eg<8jr!Pz6xIk~`p?<#&zN7{}&mrkGq%6z@T?cXOmEOlAz0h!(DR4JH zN2RIpNGLcjjYDlmqRDY``OpDr9lQnLjn2WMWpJWTPx}adGM;1+iUzI6P zaqO2aa%q>ojw(hI2J4oP46rg;95;#?4c310p*Au{Xbrp=DsixG5%~@s0`-<@h6cdQ z(0q6nnM;2CD^XAVSzDg`S#R+}Les!qI*gx&qHm&((9$ZnZs{p-hXS~Js0g&Q8ty$5 zJ>?QUsDf}Af}T1E#6%|PqpEV8zLN;42xWWkD4 z4H%REI#K|39*hZwoFAY-RjTlMqlHjq&^>(P3+*s81We=Y9_cYK!Rxenz0g!(C?119 zkd->Te%gs(D7APxv~<>x-!)=^4*=@{!zw_vpGX%;gJ~>?n?A%dwCuH>l08oCEpo5D z{`hEL0GYh@dXC$f5Kg74GuX|3@a9>B%3uZ5%e$54cw=B`IHeexa#bVmX;QMmB(7mY zk+d=tLEUnEussDRkPhN%g-=cp{fehMhnXa7i!_7g4^L0elY*VK2x_3EnY6!M-V(IA zC}|Bpona3?IM}Q`wM6u{q?_<4i(cm-JQ%oQ!&ws*%yoafv~pu?0|+*5XJoRv8+yZO z(6wl+%K12<)F&)Q_YzgdqkA3s`x5ED+8orgg19)_a&Gi!Z77>paAUbmrbt2C`B76W+GbDcOZ2^!%R=rwBs9~Yv|4~C6jRpYp)dk zvQVb;>7D6)zdaurm|vuSWXL@9DBFp->WPG=Y0dB#xzL&b#-gAEK-}bbrEjm$@7zR? zc#&XQp!g>Dh+Xlg37#_Y0g7!>zGDjlW(bwy)){p_R#p!`<3$ z_BAC>Pzq9-dYHf84fQb^s!IFrIP}JW?dQ+)={Ltso=v%pd`}$^hGtiOV{;nRJc5V) zd>OZ=1mpgg7$1v+TL71tCCVtD-&8Y^2>wGJCrd%EOWS5(w{b0qam#+L_nFSs94V=k z@6X0>T}{jcm(ldq#|_-E6OCWc_>*rV5jD{<&$rLB-uE2aK1?OTJD-188>;f+=Dp@z zpCOIF7c(_)jU6qcgY>bdY7aNM?M$f@n6(o{CN zOKYH%BD9e=yJ2Ly*zNv@`~5w!^;8ocikyQ+ zFFrq^rLVv;{8H`+e>V1^_ZqfRbl$Z0$2q!ioBJQaA4Gm&r&luVZv64+0kzEs-kT1p zCL7)p8Pb~FZN?tVMsz^9GM#4SDXlF9CZyW6bNNzCP2E>E`uf#tfIsg#WK3dg{Ap@j~g8KE0vwAa_~ zd|{ZV%Kf>kS|9ipOgyD8_TptLMx89#>u=W~C<*r|9+uf_>XJZcop=?p)lcGqs+xqk z?eF6hwvO*Cz1}(PdxhuQLX*POx``5qg6Xs$$}*LVDM=$bvKuojQlj?HixD#s9*H9} zQSCK0r;&V*_W8t=AWk7Q@kzH%e%s2pFn;s1x+vM`RK8SFb?H%eGEaZ=pD_8Qh&2&KFW7JFLimD4to4Z{ zGD2HSNv_S_(B*3=8F|3O;dLR8#9XFAN-7MU*_*w;OfTxh9C$R%xb@}E!lk(bd|S^f_IUqGP0gh$vcjlgoNwALZrGv0o6 zI3f!b&=N9-n;l85+2!2Ye`3S51=<0$vA)gCZV$L47}u>1InzZ%LA;yX-7OP%eyXx} zSN3A(9kmU)V8W}-(-u7diEgL)w95EJI0wORY<(qw4GB3^#_ux*H1&H;$w)H%2D-!IM+%Of2`_R%LA=^J+-%jG(r+A z51fJoX;cMi@TfUTQ;tqr#uq#@r<7Mji19`)yUGOdK>VY*pa08cy@5TFUP(QvTZ^O~_kQ>b= znl7<9kK|F}fAkJLvI**`;`qhksQADRR4$LSXrKFpTpnownN5e>q<(oRO{ACc{Fn{~{tB)T){q_Cte;@z!^*5it{Px5D0nJ(8Z`(Ey ze)nI&6-I3XMv(3?SrKeQ9tN~nuy)wPG6=LpSwv}4pr|B9)Bk;>sNW<-Dh`tP$DdOYQ^;9UjCzv8Bcz4Th={qUHRDdO>38Kp6xt|ad>zI&j3vz2g)z5y7 zN8XY!HVuxz#MAPCIF984ERN3H5422LtD*+W1I&9gnYABQBtu^oS}Hfo&;qZS_QYw^ ze|lP+QnF)AHh>I0$XnGG53YxqvCEVzUz^IAkBFw7#IQi_9;l?2eNz?qK|(ww!2=&= zaCP}=FTwehix8)fO!Rj{W$lgOvC5$<5Yu2VTmtF7gnLT{8c7GPxib%3Yo}fB#U%f= zp%fNm1Npi+#oP!+`XM`1YDSP?Ss4}pe`5_Ze_l+6iLU-aQx|6_{x&s64CJ;U;trA$ znFoqlgp&y#`O#Kq?3e@z6i>}VBLznv9ER4Wd)x+T z#vRj6C$;6!HPTI{Y+!VL<>hJ)h~zR8ZYwgg^nS@vQ4+=dzyEbb)vd6Qs#o~%e@JzL zs0l{jpBTGL^3=iNVy!kek^2CkIkPkreZK>m!DAs{{B1Dhp@g93=woTB$HjABG@in) zJ$Es6uKN?6_FbQd+WEY&1taXShQ-VkE(F$2ThBXe=z_k^~r`~^<}jk6q_p=TP~{oB1@W!=0a}by~sk& zJzuu8Pv_`Zd#xSLD?IFK6!`Tz1%8oMf$jO#g0RT-QNKeeH7&h{z4 zw|j<459geZY+G=^4=4fem(o&uadvj5u8YtXYN84H&enXC)4p4$K6)5WES1KSxqIIe zl(Klj=C@9N)!f7)Q#OYleg#{>zg z4#xJ;c7pbKlSfqK1lxLVBXYH*_VN3}WXRL+&pR*9R^@u?uA)K@0?|T6MHoy_qN394 zbwt|hWxH7tlwzvXKK~Z|{_B_P=%X+FmW@SQaK5>0I5d`j`tu*xaxL0|4-?D3 z{_*wGwSBo3ZNbmg0rIz$aSwI^f6ZD^Z`(Eye)q573Zu3G!?V+%G2(f}x&~-~b~Tc2 zdzc1-mMDv;OsW)>#2EVDM@g|H+aygHkYYb1lI|n#`;N!c$?`Anq9E#YC|ct>k&i!WXGrDmWEZq1c44u!*Sa3pDe*ncGnstZ| z`p)9x0{(S#cYpik4|w|l{5l8IvHT3ce;pt{9E*fg`R{u|xby@++}=S5Mxgiek})CY zL(z0Rl5Bgk&W~)$pBz7-t{{v;{6u0A_j~IlbQyiXTtG}+=1C5{!PdnV4HT$;H)lKq zUBQa0^33OKU7UkCph*}Gf3oTFNh{o)y5T7fuCGs~G?-;Hei&ooVF|`?P1ZQT$irM6 zlmpn7LNkK(?rbz=0XVMd09FaB8z3HI@w}>OoH?SgkNN(BOB2P9A)#1ac;#@1zLJ*+ z^)O9GkDG~)1$7nYl5D}TPLj}MjX81UC?Yn-JlR23xUEqn|F*Jce`+zQlU5c{P$7>+ zL0$>)lRR;J6vnvGys;yeGqMWJwm`Wk#^$P;BB~xGowhQr+sPhP$6SNB! zvPa!UDeW(?rh3oz+L98dxfVvL)M6u2&Bx5$>r7M^v&DLtJYTB%;|*GCZ?K2&Z}r0qjQRU{qpBVwZA3y z8J7>E*?G~|f3z72J8CCHgu8)0BB}9ABVtMIl!y>xNXQw-#FE=dK`|AMbE_Opln!IJ zcV_Kg1O1+SW|9x zF}tm?NaAL69&>8F?+7(-yUbn`j)r$!#aXRaCl81dvu~8}cWqQx~#WtfDBl zI@8!4J;-vg>fGzpsZ)owvp2pOKYHXlBOHyLK1TMJ$>=J%ejeO5n`@h!pJR+$R~y}n zf4k8z@b9{3fp<4_J+FIm6~&?JUvEEpG<1E(Lw(PQA|#q0ee(#B&p5gA+&=0DeiWmV z=k~$=McY0+J$`|9QTOc2GyCY}rG2vhqixnZbl*5JMp5j-{1Zcet-MhIr#n^{r)}u{ulcFNBaG5^!rcn{rA88 zEB*7=zs~iR2!DV3Pk8zhe@Q~pL&%;><3Uo^b20%o)-AM4;lp)-Oh zazzYoW1VnKI$^@B1Ww0{gjh#+kphWO1%HU+B=l1XsK6Bp6u@6zo*XZ?UtS(v9nmx-8-z;etg;L1d)la%$f>kopr(Q>o(q-Eu$dS4`mIR$FPlTlI4f4MNd3Z_os ztgaq?@$&S%O(twXqxV`Rj6-~Fhj{D}4{i4y&%1K^uWQz7r`x^VY%G)S!^7UQOXav?H@?fvbqRF|Ap79-s)y_s+s-a5?WYUS(ztYBh@F)2d_aSahTwYi!Sw$ z_FKn$O;p)W!pMC?WCY`vG9qdV^Ka@Xi7@#Qye{*{x@p6mEvSq@mi=bY*}o|TUhv2g zUlfVSZ0{xQa&^J5Jw?r1IWUv-!VJnDxg&goodFKP5h+5?*#_+@f7@YeiOZs}=2tWg z<^b~~5TW&Ni2iWQ7e^3j==jkPhsv@^o_Ma0hZqmC7UF`)j4>>%#C+WAYaH4*41=&{ zX=$h>*O8cve_PPD_{`t$JmtRzr09S&a zjBx1o*;MC3Ajn3}e|Ra2*h+#0XlITzLJJG%F$510@l6g@Ds7?xMR4_stn{L-JHqiz zFtG70i3KBy*0Sw5xS-LdRkT-)__Pev5xyN0L=hT0A&G*CaiAuO19SypU&!1x@h4rf zz{huEx&(rILDyvZ`bJ?YRtZd-9PVQ&eBzR%k<(^_e9$u%e<%O2eSUFt`qDmnK_!%R z)UVpg6umcSAXS&D2%P$X8H#mn=3=$9s{~z;Y79%A{HKIqd`XK8vNOPxEL#)s;k+|R zaq+leR~B|*{ZuARo>OKaGyoVmasP&_`l|_B(~QB#5BLJf(N7n5=%Z#irok2C8A*D5 z>gKvbFCa$Kf9(f}A4|r*iq`AsGqj!!9R?v;!CW$?u}%CW+Myum&&6Hnt&qn-b>`h3 zbddf>i>TGxHuWY?n-fMTc4dsk7Cx!%Z8bitbsEc0dyDmVxBR!WJltO<(OJD-+k3oF z-`;~TEnDyD#7FZZw&(+wnL{nINeFNwvfk9@N1ax$e@>Rw8d=ZkB>cj+WFCskpIE#pEdf=ko~hRtrk?134ej<#q=s-z1dp5udr0vEJd&7dU5Xf0&byWtI|_KvJG9EAA2-ujXKA3_zc# zv67rc2*N>)f>nmI(Bc&jEna1z#jB}8i&xsv;#FyAG2cHO4%rTz3|v&kwLm)grZq)O zs;X8pU$#eNHzrd*9@eadPi{Z`(Kgs6M+i|CX$9Yt(m?#qwxtJ8Xoy`u!KgJ|h$E8) zf08eC#FYXZ25=Tp4udphZVk>*m@SToyPuph_}&G7Y%Z8)8pUDW<`sCV}_oWSY>+kmp@JH24gH5&#rP)LCApka~!R70>{<4Gh8OK2@|q(yWug z4`jGA-KA5T>JZ2PRYF<_Fql=E!U9=tqC{jh{4;BwsX2q8O4X4thga z8K5j2oclDIOS33auBw{Ri6X2TEyt>4XV9_S#spApV&JVy0%{~{`tR+j4LA`uuT>?9sg9Vs6%NPbBcBpI5f5CJjI<~N0i*-Qr z3_ffMvr6LP6wc(BOVZ>D?N%+OcjG<*J|fa1R8$Qz;d4b;eO(-2bV#{}TBjs9(q^5i zF;S?;);7wq3CJEGC^~@M$WjLjL|?x%Pvz%_j{d*G`MRneZy@Otl%S!bInBgO;a?5rcTK z;l{ZmPy0}}>tj6d;CLet$5V}uURD!$&`)^QDR)N0n)C-Xf9#nq0VB06{ z%P&t~*fyj{worRCj_=6a_uP>iQ*TpV1R#rHVtC6tHcBiiGpGp?eUPR7pD?ut*&0oi zawZ?|^3pdkKwaE2VW?rV3}ylpx$6#Um=3bRY}0;=(t)|~DctJKnPnjf`LtamxHBm= zN-RhIXigP$f8xkQqT3u)BPpe))m;C4eRFMN{c|&Ix|M34SOTq&U}Z$yos?8&?Md^? zY(|+4ZWYxb4bV`Ua|Mhlpm8r1;v#F9?i~imcSmE-jp=@p?MdnXHX-Rcz9BMbPF+Dy z0Tm1UxFarB`DG6ZrGBDZY}$IijIRe2`Ehz?T9VeJf0I!WEuZCbYk|4d@V@xG3NC&H7uj!ot#FJZMUmE zcQC(nCMEW3RhOpXh9-ofSZlSWY*qQW)y9)2Yfs>eGG_%k*GJ&25I`&6&>{Dlop0X0 zmUj{De_5IkNrBRmq13wazuyqHeqYDWj(;RYey ze@S*Vd`RbojG;X#^n_Xx?pcf*2nt6R&GRR_s4O7>!5{6Hch%*PbI{cQeByd*aFwY( zGxoJR9o|pJaOT}7a4gn2Gw)_Bl4336oAw2do!I~<&1lgNa;WZXHqp90Qr_QYD2i2Z z`o$~Ds2D&BQmX<~7laR#mvGgcVG4B2f2S37qZOpec7!s$Ms+nRuuS~cMREm1)BrRm zXbF{U1FNKA=u2#~VD~$4v|Pj`nV5<9s1&VKOi@WUO}%<#5~kV9PdqpNK&p0Urt9~S znr2><__6P3R{TGAtRKX)&e*Ye?umT zlbF5j`oo|$4=E@}7dW106(j{+U@vSHk{A}0O&jW3=$$+9*bb6-oW!)JHFhF;p_lZ) zPH*P`8Y0&3C$$*g#;fm~G1=bs9iVsh7H-w)b{B6)UL9ZnBX&S6)(e~g9oA4=X`vN^ z=m#k6OS+95*R%P3C9FgGLJ57uf36hC^C@nN5`C_M!JV;(>v{Ov;W$$2L~onkjN(zo z*vQl25Qd5BGFh-$#hE3at;=2HqfEFoO|!`oVj#Oj=s_!W_*s39h@P^vf<^E*QHXv_ zHN0RVCadQ)<)OFf8}YFm!S9f zlu#mM7+sKEe_?H{!^6WDoU*iWjW+gZ4U$ErvxJr*=pj1A#r3sD+tJPr zYQko;xL3{JD^@KxV)GY$%R;Ae+wM*PRFNFMB-}VY857bsk7Mk|e4iU9ri-r9j@8hx zD>o{=aY-|7x0~!x6NaEqNzisnwc02q-Kh(jt7MI0Wb&Nu9rkQMBm}Cl#8IOkBlt8<{4y7vb z$MgoGgU{eJQOc8%f9Pu+SlNRYzM4I<#5T5-Nlt8$y=z~bE2;CP*p?CNyh9Ck5pZ`} zJ=(miIO>W*`3DeHHXf&qyVM11?1uIBfI;3lpk@lxS+Rs66l|j#(^dKKy#s^j2*`Q3@ludY(pKNs&0`0@tShuIT7)3dD1hQmwX=>X1_es7a zwsRSUofl)B+o$t=Iv+n5%OVH_$w3!{0!prJKDXId`FL^h?(*^j0KA>Qk8jI7m7_|`mMO@Hf#4}X&I-jI+#foXVwM-CL{lUz=TYCud{yVVyB|1Y0m7ZfMp zq7{HQ<8!19F^o3eYe7} z8aDz`o@x!VO0Ap$7Y0SHA$fM1)g1f^zztuW_5aE5Q#|K;-Q(olaZ7 z@y4MaBU~W0Ce1j0W+KPN2g3PF`mfa4AXYR!dYG&$^ij^;awyBcY`HWnif6l%p*|+G5&Z@tO}q{{~B@4K_qq*5q<7 z$eL79Xa#5DHjKM^wjg(Xm$Z3Pve%F@jZvv#9Y;I zwg=3Wlg_5DY#4rIvI+>RE?a!kmXPP zO^yJ{)oY3f*Q$k(&0^KYLB zajhdSCX@DW8gB!%N75nNn8*k9RCidpRhH7*?SNh1{P}w@71O)NGLFQnce(KIG9FU@ z|DZSgilgv{XnQacyi{Ep1gAc@SMpxCRkUlOc3AWfbPVm$y9<7o%PJB<2l)NV)8o@O zx5P^lE&_j*Rc&wDFcAKpU*Sop2@)`MleVg?>(Y)1q-l+yU#hx_9CJXl#L4W0QceBu zJLjb&A!&z^2+p0Kd+zQz^W`$$#D*bQ2r6bAh&xR~FI`_nyVKK;XJ?-Q;Dh&xE}p_w zB%bJX6vmMNkx)lQ9F!Ux1`n80#M#7?9q=?^UI2eTe6a*+{51%!So&L*0O>))KF-al z7x=|NBZtwemu!4B{MrN}7ev4#p~sS^O~zI~=k(mlY=u6S z7exW-`GN=O5^T9v7@Q(oXma&e#Oh&nwS5fS}-V)G(^p*Qd>2@s*X-hiuadg zo&7Klq9s_k9ptvYZeyi+x}tQiDvd#X;W8=NQ*Kz;&TK&LO}_SrEBrzvlLzHvs6cK( z>K&%E^w#FXppbL3CuP+t(MCC`wn}uV-?49}9oql)zsh)}e@AtzljFpmyG5GqT#tXH zT4zvaQRmZ!toapmaKy3d)|)d=DV`f4PB!=Naf69;bhJ~cZYr4;r6hMxp3s+zOsCZmUyP{1AC44uWh^w_+cV z)~EwUs~K=np#=3ap&l$m1e4vN2%xbQj=X?&KWKBk*REZ6A=7J~C8K|yvpIVLu*KI^@$Vh@<{?bm51==v{I;n?Q^F z#jD!_{urMI!@C0~pS<^jkdN*h>_KoE`S$_%FqrVs?(7cfx<89D0D4=1d%={;f48HI zCu3m0TY>r<1z+j6TS56Ph(_^cDgg~cKTVlz!Gg&&u-Mp%6=9n?@mt>e(GoZ z(VveP{7h#jVK8LFI7&0t+uvtzSeqwF`<3~2_wymXjc1wd?ZM%@!_NnU{ocE+-?3T3 z{n1rTL-=q1u(yRx)67pYKB{Sa*xUcKz4K1Chp9}0$ai+x+TMX?tM+$d7HjsGf&kAf zFAujr^`PruZ@+)HxqYy`OCvnBwcG||2RpkvgP%U`ZhqL_`PnfXwfoTP?F|6#9%1DK zckp4Gnn9ZvwQa-%evoaF-~y<`7KeTqp7_J_%HuOX-IQ;jQ-AD-DX;d7Clfy!g+XMz zxg1rV@aUrQB#5T7tn!Q&8#iR-IdEKE#m{8mbQvU?RpNnj1HZqZdhp@Bk1DZq<2&Oz7 zudnNS%OLSEp*`-0f_l5-amq7||1B+hNfIXw&9m&vHM?ncRiz3^R-$M-olU0+OliPz zl(fy>yk&p=ESd2xYoAlb7^o;tA*l#`OL!b=%XGoho( zuaK~?Z5Gquba)noqta$lFOhpTfqQ5^tB7WM_d~nO*67!tc?6;~WHdc^`i!1_3R0Bf z@tD!%*vTvivmi>b+q3Vf30MyCIq;0!Q*7u*QJjA<{*@1B8K;K-poW{W0lpo_vuH%` zeo#Qr{9uS9^Q93&?|-ECK(y1@6dycal^sA_CbKCudM>8$KQrtMBaCK~Dd3aP=+DJW zw!;tD4 zM6-W&*7AqcD2o})j*mgQjMx-9z*tDa37^Et6^l>)mk+ZR!4M>TAI7jm(J2#SbEVS( zB!Z4W6u{6)key99E@eDLtp^PR5JBD#0j!UgD-lqWU~&ZTTX@x>W`fe6Q=Tj<D9{7qzbCbvw{WYh(wkEc@@!HW~r zCP3X8AAKkKelOWkJY~UHwWha%ng{Si@-Qw;#=%ZWUiiPf}(#v zflNM4kkfM%{R|-qkTH>mjgxr7P%L?^AZihYFIVU5f2jyBx`S+%{l&U`PEKJ#*~rg) z=3jutjKUq(g{ATEoYT&EDOP23?=wSX#KK?_2nOUML64VWSx|Dp4F7Fy^1{Bf2ob4| zmMS285hU3R8sKB#Z2iv!;7x;skBEPvU%pg(ZXAR(RlNzd5R0@Ihhk3rLW+x*it;0t zUZrRtw2w=|a6F4>@3X;xj0_Y7o}VQCRc;j^^CUPd8Ur9JqgfbsnQQ~rK>kP=0{NdC zW8B8MR)^=`uXbo`VUywy@S3;KS4a#%+@)FSwQ+d|u&bkXr=zvTpX@A&FIj)YFIoT2 zyfCkQc*b*kT3FW!4+BsQK$#qwF1tJnhG#5DNoJQ==qjEiZ10ynlBYms+OSXComV;X z-}2uUv$G&weybT>KpREvS4J};dSQquvLTV#n>TOtc1ugl>r0X3wg1)I*%}=Fg7(DV z;NAOPyTktai?#dxHudXoZG(T6d$_y*`{1Cr_iq0k{A!T9ghc<8XYiY6omGz00^$UG z;Q$48NH4nZs6`ERpxHLElP9>UW6%W}NUP9OEM_(`Y>e938{Madb@a_*N#uJ7Mb`n^ z1)IgvdimzL|J|gFCF}$nM4;y#9d~K4F2PrTyUa#4380xd6Q0eIsA7NEesf5cG~|5h zRk77}GRUV-JUfd==%{Gha#Pq&plAayhd|i2*;9bG`SIPs!C=s-ANA5d=d;@kJBw~M zFqo7=j@qQ1$Eb>qTk;u%QRr?k=I_|J_kfoV@VDc`# z9ERZRj>(;(^`#$V^{7o|X|{iD^w4XwtnjLkfRaQ2wTkEcs5E;dQ1F{o(CS30BaR^U zHAv&;`XB`=M9$5_wE}N->j|HbTdQPN6%ut(2IJ@|^(k0>3 z5Iprg{O2qVM;w+>Z)r%zvb|Ps3(keX6j1OJE(6rk@cD>_Pdy$qM>7BIrW%;CxzIR} zVVqt>;W$%x=x7v$&y};Gd!m zMSbY+>LAlB{Vh(;ec=V8X^M`CHyFI%{@5D~V7iGzssZ`;{2v**nZ4o*f*=?LiC6d?52Y#k=@jhp zY%H8Xu&^$I5r*-=N^d<*TWl0_;rEe~`n26;I*v7(q%g|=+0T8+>h91OxNEoake^e1 z&~8EQ{IN7EE7Fyy3JMK;)FJoE&>u_@f2Pfg(V2hH2h-w=`80?`qh1-?1Lg8;sXW&p z1-W^8Fa5=;6 z^gw@{l%iPwyt8?@y}Oh9oO82DIUJMQkQS-FU}f+#|AOxWl@Df94B4COtYKpKly}Pu z)nMHo#^*8+(m8c8C z8sb3hU7)Gi5bnuBe4DddPl>y)<;CTcBf09AH?38_}o z*ySp?7JI?Sh^&_dw9fh)bs9)GF6(4u*2Z#a;gIf>9D}OKF1$yvP%EBn)@9Z3<-FnJ zkw~Hv(yZ=CtU0UXIY!+kY5CP=xVr`N2uNid31G zdO^Bd0@WZw%H!4n28rZYld1#oUZoYfY&k7hh~D;(X{iYJM&yGZCN*w#RgNIAK44%ZQo7AF8XT6No%gsR2|u67!uX|1asg8H!@ zxmiHncq^6$vp)5R{+jKMPJsYRXr`dgnk|5Ge7-_ z^GKa0xHoV)s(+YYGI4F>@|x8jv^11E4_(H%&UJmU~t)hN_8lTn%{D1&896I6$T z4v}VFkcAnRCs+?(TdUhBo)RAVb@gi?L%c?#+<^*g9*%zwJdMjEYpY$RodP)XQ$&YP zn8a+$W@b++x3(2i&V7KNA2h7Jv_yRH$4HOBAIFIck-?orfYRKY!|U=qE&0FAv$4%c z(RO2<{pgsRD-W`HK+VIh$z`+RiT7F>x|yD7mF@5py_+8*&Eq^8w^VLn)%l~5w1L%a zDoWk9@27w3B;`AtMOeOrUhl)8x3g7hd@0V3%dG`2O)%$*kvV6ty8-u20^uLbO9E=$ zNCMGnZime&Nk5h*Mi~O)rkq`mSjo{agr3vxP*2+R1uS*shjJ?Lt6Xq&FmivP!Q=y~lK|?n<}T>m7oExzY(S`T z&MWaM62-~~Zx3_qyR9GTYDPXf?8Q;0Jlg~hLLCa%A6;r|d0%ut%&t7lBx6{R#761* zTp^;mnQ47*-q+#pY=S`oc>(>&uKlLAWQKc52)=eAowtg6BD50lE2H5Q+M;x+-wL(6~G#3Y2c{cU3XB{ zR<7VW;X%9`SWDUbwxJVigi+jRFz^(&9Hf5|9tUB@6HnZX&w+T0Al}UMMv2IRrtU>M zjn-xuRgAY-x+QrHiij!*4cfM-z~xqLdNlYjw$!m*iysUo> zE$%u$d|I`uWTYo;=lg)YzJB=oUT^SoZ>P8a?&D4J{PYW~+S6Jhm)(;8SCrn~`q-O4 zbC#loxx;7k_wdhz3P=T5LMXh_iUs*&P&m0*qkT13=)F==(JAM5#T8Jc z0o9Grzft0w7Uu!as05ZOD0mlXmLq>rTwph^QKB
  • MOnrQ5zHvw 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