diff --git a/.circleci/config.yml b/.circleci/config.yml index edb010e442f..076772c419e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -68,9 +68,10 @@ jobs: - run: name: Analyse PHPUnit command: bin/test-with-real-projects.sh phpunit - - run: - name: Analyse Psl - command: bin/test-with-real-projects.sh psl + # PSL requires its own plugin that is not yet compatible with Psalm 5 + # - run: + # name: Analyse Psl + # command: bin/test-with-real-projects.sh psl - run: name: Analyse Collections command: bin/test-with-real-projects.sh collections diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8ae030d4f46..b9b7d2edcfc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ jobs: - name: Set up PHP uses: shivammathur/setup-php@v2 with: - php-version: '7.1' + php-version: '7.4' tools: composer:v2 coverage: none diff --git a/.github/workflows/pr-labels.yml b/.github/workflows/pr-labels.yml index 1366a7ef1a9..30761023d08 100644 --- a/.github/workflows/pr-labels.yml +++ b/.github/workflows/pr-labels.yml @@ -10,4 +10,4 @@ jobs: with: mode: minimum count: 1 - labels: "release:typo, release:fix, release:feature, release:deprecation, release:internal, release:docs" + labels: "release:typo, release:fix, release:feature, release:deprecation, release:internal, release:docs, release:removed" diff --git a/UPGRADING.md b/UPGRADING.md new file mode 100644 index 00000000000..a389e609676 --- /dev/null +++ b/UPGRADING.md @@ -0,0 +1,164 @@ +# Upgrading from Psalm 4 to Psalm 5 +## Changed + - [BC] The parameter `$php_version` of `Psalm\Type\Atomic::create()` renamed + to `$analysis_php_version_id` and changed from `array|null` to `int|null`. + Previously it accepted PHP version as `array{major_version, minor_version}` + while now it accepts version ID, similar to how [`PHP_VERSION_ID` is + calculated](https://www.php.net/manual/en/reserved.constants.php#constant.php-version-id). + + - [BC] The parameter `$php_version` of `Psalm\Type::parseString()` renamed to + `$analysis_php_version_id` and changed from `array|null` to `int|null`. + Previously it accepted PHP version as `array{major_version, minor_version}` + while now it accepts version ID. + + - [BC] Parameter 0 of `canBeFullyExpressedInPhp()` of the classes listed below + changed name from `php_major_version` to `analysis_php_version_id`. + Previously it accepted major PHP version as int (e.g. `7`), while now it + accepts version ID. Classes affected: + - `Psalm\Type\Atomic` + - `Psalm\Type\Atomic\Scalar` + - `Psalm\Type\Atomic\TArray` + - `Psalm\Type\Atomic\TArrayKey` + - `Psalm\Type\Atomic\TAssertionFalsy` + - `Psalm\Type\Atomic\TCallable` + - `Psalm\Type\Atomic\TCallableObject` + - `Psalm\Type\Atomic\TCallableString` + - `Psalm\Type\Atomic\TClassConstant` + - `Psalm\Type\Atomic\TClassString` + - `Psalm\Type\Atomic\TClassStringMap` + - `Psalm\Type\Atomic\TClosedResource` + - `Psalm\Type\Atomic\TClosure` + - `Psalm\Type\Atomic\TConditional` + - `Psalm\Type\Atomic\TDependentGetClass` + - `Psalm\Type\Atomic\TDependentGetDebugType` + - `Psalm\Type\Atomic\TDependentGetType` + - `Psalm\Type\Atomic\TDependentListKey` + - `Psalm\Type\Atomic\TEnumCase` + - `Psalm\Type\Atomic\TFalse` + - `Psalm\Type\Atomic\TGenericObject` + - `Psalm\Type\Atomic\TIntMask` + - `Psalm\Type\Atomic\TIntMaskOf` + - `Psalm\Type\Atomic\TIntRange` + - `Psalm\Type\Atomic\TIterable` + - `Psalm\Type\Atomic\TKeyedArray` + - `Psalm\Type\Atomic\TKeyOfClassConstant` + - `Psalm\Type\Atomic\TList` + - `Psalm\Type\Atomic\TLiteralClassString` + - `Psalm\Type\Atomic\TLowercaseString` + - `Psalm\Type\Atomic\TMixed` + - `Psalm\Type\Atomic\TNamedObject` + - `Psalm\Type\Atomic\TNever` + - `Psalm\Type\Atomic\TNonEmptyLowercaseString` + - `Psalm\Type\Atomic\TNonspecificLiteralInt` + - `Psalm\Type\Atomic\TNonspecificLiteralString` + - `Psalm\Type\Atomic\TNull` + - `Psalm\Type\Atomic\TNumeric` + - `Psalm\Type\Atomic\TNumericString` + - `Psalm\Type\Atomic\TObject` + - `Psalm\Type\Atomic\TObjectWithProperties` + - `Psalm\Type\Atomic\TPositiveInt` + - `Psalm\Type\Atomic\TResource` + - `Psalm\Type\Atomic\TScalar` + - `Psalm\Type\Atomic\TTemplateIndexedAccess` + - `Psalm\Type\Atomic\TTemplateParam` + - `Psalm\Type\Atomic\TTraitString` + - `Psalm\Type\Atomic\TTrue` + - `Psalm\Type\Atomic\TTypeAlias` + - `Psalm\Type\Atomic\TValueOfClassConstant` + - `Psalm\Type\Atomic\TVoid` + - `Psalm\Type\Union` + + - [BC] Parameter 3 of `toPhpString()` of methods listed below changed name + from `php_major_version` to `analysis_php_version_id`. Previously it + accepted major PHP version as int (e.g. `7`), while now it accepts version + ID. Classes affected: + - `Psalm\Type\Atomic` + - `Psalm\Type\Atomic\CallableTrait` + - `Psalm\Type\Atomic\TAnonymousClassInstance` + - `Psalm\Type\Atomic\TArray` + - `Psalm\Type\Atomic\TArrayKey` + - `Psalm\Type\Atomic\TAssertionFalsy` + - `Psalm\Type\Atomic\TBool` + - `Psalm\Type\Atomic\TCallable` + - `Psalm\Type\Atomic\TCallableObject` + - `Psalm\Type\Atomic\TClassConstant` + - `Psalm\Type\Atomic\TClassString` + - `Psalm\Type\Atomic\TClassStringMap` + - `Psalm\Type\Atomic\TClosedResource` + - `Psalm\Type\Atomic\TConditional` + - `Psalm\Type\Atomic\TEmpty` + - `Psalm\Type\Atomic\TEnumCase` + - `Psalm\Type\Atomic\TFloat` + - `Psalm\Type\Atomic\TGenericObject` + - `Psalm\Type\Atomic\TInt` + - `Psalm\Type\Atomic\TIterable` + - `Psalm\Type\Atomic\TKeyedArray` + - `Psalm\Type\Atomic\TKeyOfClassConstant` + - `Psalm\Type\Atomic\TList` + - `Psalm\Type\Atomic\TLiteralClassString` + - `Psalm\Type\Atomic\TMixed` + - `Psalm\Type\Atomic\TNamedObject` + - `Psalm\Type\Atomic\TNever` + - `Psalm\Type\Atomic\TNull` + - `Psalm\Type\Atomic\TNumeric` + - `Psalm\Type\Atomic\TObject` + - `Psalm\Type\Atomic\TObjectWithProperties` + - `Psalm\Type\Atomic\TResource` + - `Psalm\Type\Atomic\TScalar` + - `Psalm\Type\Atomic\TString` + - `Psalm\Type\Atomic\TTemplateIndexedAccess` + - `Psalm\Type\Atomic\TTemplateParam` + - `Psalm\Type\Atomic\TTraitString` + - `Psalm\Type\Atomic\TTypeAlias` + - `Psalm\Type\Atomic\TValueOfClassConstant` + - `Psalm\Type\Atomic\TVoid` + - `Psalm\Type\Union` + - While not a BC break per se, all classes / interfaces / traits / enums under + `Psalm\Internal` namespace are now marked `@internal`. + - [BC] Parameter 1 of `Psalm\Type\Atomic\TNamedObject::__construct()` changed name from `was_static` to `is_static` + - [BC] Parameter 1 of `Psalm\Type\Atomic\TAnonymousClassInstance::__construct()` changed name from `was_static` to `is_static` + - [BC] Parameter 5 of `Psalm\Type::getStringFromFQCLN()` changed name from `was_static` to `is_static` + - [BC] Property `Psalm\Type\Atomic\TNamedObject::$was_static` was renamed to `$is_static` + - [BC] Method `Psalm\Type\Union::isFormerStaticObject()` was renamed to `isStaticObject()` + - [BC] Method `Psalm\Type\Union::hasFormerStaticObject()` was renamed to `hasStaticObject()` + +## Removed + - [BC] Property `Psalm\Codebase::$php_major_version` was removed, use + `Psalm\Codebase::$analysis_php_version_id`. + - [BC] Property `Psalm\Codebase::$php_minor_version` was removed, use + `Psalm\Codebase::$analysis_php_version_id`. + - [BC] Class `Psalm\Type\Atomic\TEmpty` was removed + - [BC] Method `Psalm\Type\Union::isEmpty()` was removed + - [BC] Property `Psalm\Config::$allow_phpstorm_generics` was removed + - [BC] Property `Psalm\Config::$exit_functions` was removed + - [BC] Method `Psalm\Type::getEmpty()` was removed + - [BC] Legacy hook interfaces have been removed: + - `Psalm\Plugin\Hook\MethodReturnTypeProviderInterface` + - `Psalm\Plugin\Hook\BeforeFileAnalysisInterface` + - `Psalm\Plugin\Hook\AfterFileAnalysisInterface` + - `Psalm\Plugin\Hook\AfterMethodCallAnalysisInterface` + - `Psalm\Plugin\Hook\AfterClassLikeVisitInterface` + - `Psalm\Plugin\Hook\StringInterpreterInterface` + - `Psalm\Plugin\Hook\AfterExpressionAnalysisInterface` + - `Psalm\Plugin\Hook\AfterEveryFunctionCallAnalysisInterface` + - `Psalm\Plugin\Hook\PropertyExistenceProviderInterface` + - `Psalm\Plugin\Hook\AfterFunctionLikeAnalysisInterface` + - `Psalm\Plugin\Hook\FunctionParamsProviderInterface` + - `Psalm\Plugin\Hook\FunctionReturnTypeProviderInterface` + - `Psalm\Plugin\Hook\FunctionExistenceProviderInterface` + - `Psalm\Plugin\Hook\AfterAnalysisInterface` + - `Psalm\Plugin\Hook\MethodVisibilityProviderInterface` + - `Psalm\Plugin\Hook\MethodParamsProviderInterface` + - `Psalm\Plugin\Hook\AfterClassLikeExistenceCheckInterface` + - `Psalm\Plugin\Hook\PropertyTypeProviderInterface` + - `Psalm\Plugin\Hook\AfterFunctionCallAnalysisInterface` + - `Psalm\Plugin\Hook\MethodExistenceProviderInterface` + - `Psalm\Plugin\Hook\AfterCodebasePopulatedInterface` + - `Psalm\Plugin\Hook\AfterClassLikeAnalysisInterface` + - `Psalm\Plugin\Hook\PropertyVisibilityProviderInterface` + - `Psalm\Plugin\Hook\AfterStatementAnalysisInterface` + - [BC] Method `Psalm\Issue\CodeIssue::getLocation()` was removed + - [BC] Method `Psalm\Issue\CodeIssue::getFileName()` was removed + - [BC] Method `Psalm\Issue\CodeIssue::getMessage()` was removed + - [BC] Method `Psalm\DocComment::parse()` was removed + - [BC] Class `Psalm\Type\Atomic\THtmlEscapedString` has been removed diff --git a/composer.json b/composer.json index ef2f427692b..f068724805a 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,7 @@ } ], "require": { - "php": "^7.1|^8", + "php": "^7.4 || ~8.0.0 || ~8.1.0", "ext-SimpleXML": "*", "ext-ctype": "*", "ext-dom": "*", @@ -28,30 +28,30 @@ "composer/semver": "^1.4 || ^2.0 || ^3.0", "composer/xdebug-handler": "^1.1 || ^2.0 || ^3.0", "dnoegel/php-xdg-base-dir": "^0.1.1", - "felixfbecker/advanced-json-rpc": "^3.0.3", + "felixfbecker/advanced-json-rpc": "^3.1", "felixfbecker/language-server-protocol": "^1.5", "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", "nikic/php-parser": "^4.13", "openlss/lib-array2xml": "^1.0", - "sebastian/diff": "^3.0 || ^4.0", + "sebastian/diff": "^4.0", "symfony/console": "^3.4.17 || ^4.1.6 || ^5.0 || ^6.0", - "webmozart/path-util": "^2.3" + "symfony/filesystem": "^5.4 || ^6.0" }, "provide": { "psalm/psalm": "self.version" }, "require-dev": { "ext-curl": "*", - "bamarni/composer-bin-plugin": "^1.2", + "bamarni/composer-bin-plugin": "^1.4", "brianium/paratest": "^4.0||^6.0", "php-parallel-lint/php-parallel-lint": "^1.2", "phpdocumentor/reflection-docblock": "^5", "phpmyadmin/sql-parser": "5.1.0||dev-master", - "phpspec/prophecy": ">=1.9.0", + "phpspec/prophecy": ">=1.10.2", "phpunit/phpunit": "^9.0", "psalm/plugin-phpunit": "^0.16", "slevomat/coding-standard": "^7.0", - "squizlabs/php_codesniffer": "^3.5", + "squizlabs/php_codesniffer": "^3.6", "symfony/process": "^4.3 || ^5.0 || ^6.0", "weirdan/prophecy-shim": "^1.0 || ^2.0" }, @@ -80,11 +80,7 @@ "autoload": { "psr-4": { "Psalm\\": "src/Psalm/" - }, - "files": [ - "src/functions.php", - "src/spl_object_id.php" - ] + } }, "autoload-dev": { "psr-4": { diff --git a/config.xsd b/config.xsd index 9f8676ce823..28ba0edfbc4 100644 --- a/config.xsd +++ b/config.xsd @@ -16,14 +16,6 @@ - - - - - Deprecated. Replaced by documenting never as a return type. It is going to be removed in Psalm 5. - - - @@ -40,25 +32,7 @@ - - - - - - Deprecated. Has no effect since Psalm 3 and is going to be removed in Psalm 5. - - - - - - - - - Deprecated. PHPStorm now supports generics for the most part and @psalm- annotations can be used - - - @@ -144,6 +118,7 @@ + diff --git a/docs/annotating_code/type_syntax/atomic_types.md b/docs/annotating_code/type_syntax/atomic_types.md index 5fa957be454..28d31d82918 100644 --- a/docs/annotating_code/type_syntax/atomic_types.md +++ b/docs/annotating_code/type_syntax/atomic_types.md @@ -52,19 +52,20 @@ Atomic types are the basic building block of all type information used in Psalm. - `value-of` - `T[K]` -## Top types, bottom types and empty +## Top types, bottom types ### `mixed` This is the _top type_ in PHP's type system, and represents a lack of type information. Psalm warns about `mixed` types when the `totallyTyped` flag is turned on, or when you're on level 1. ### `never` +It can be aliased to `no-return` or `never-return` in docblocks. Note: it replaced the old `empty` type that used to exist in Psalm -This is the _bottom type_ in PHP's type system, and usually represents a return type for a function that can never actually return, such as `die()`, `exit()`, or a function that always throws an exception. It may also be written in docblocks as `no-return` or `never-return`. - -### `empty` - -A type that's equivalent to a "coming soon" sign. Psalm uses this type when it’s awaiting more information — a good example is the type of the empty array `[]`, which Psalm types as `array`. Psalm treats `empty` in a somewhat similar fashion to `never` when combining types together — `empty|int` becomes `int`, just as `never|string` becomes `string`. +This is the _bottom type_ in PHP's type system. It's used to describe a type that has no possible value. It can happen in multiple cases: +- the actual `never` type from PHP 8.1 (can be used in docblocks for older versions). This type can be used as a return type for functions that will never return, either because they always throw exceptions or always exit() +- an union type that have been stripped for all its possible types. (For example, if a variable is `string|int` and we perform a is_bool() check in a condition, the type of the variable in the condition will be `never` as the condition will never be entered) +- it can represent a placeholder for types yet to come — a good example is the type of the empty array `[]`, which Psalm types as `array`, the content of the array is void so it can accept any content +- it can also happen in the same context as the line above for templates that have yet to be defined ## Other diff --git a/docs/annotating_code/type_syntax/scalar_types.md b/docs/annotating_code/type_syntax/scalar_types.md index ff99acc7e2a..c326001db8c 100644 --- a/docs/annotating_code/type_syntax/scalar_types.md +++ b/docs/annotating_code/type_syntax/scalar_types.md @@ -73,9 +73,3 @@ Strings that don't pass this type check: A non empty string, lowercased or both at once. `empty` here is defined as all strings except the empty string `''`. Another type `non-falsy-string` is effectively a subtype of `non-empty-string`, and also precludes the string value `'0'`. - -### html-escaped-string (deprecated) - -A string which can safely be used in a html context. - -_This type will be removed in Psalm 5.x._ diff --git a/docs/running_psalm/configuration.md b/docs/running_psalm/configuration.md index 6a84f20742e..03fcd77f44d 100644 --- a/docs/running_psalm/configuration.md +++ b/docs/running_psalm/configuration.md @@ -134,17 +134,6 @@ If true we force strict typing on numerical and string operations (see https://g ``` Setting this to `false` means that any function calls will cause Psalm to forget anything it knew about object properties within the scope of the function it's currently analysing. This duplicates functionality that Hack has. Defaults to `true`. -#### allowPhpStormGenerics - -```xml - -``` -Allows you to specify whether or not to use the typed iterator docblock format supported by PHP Storm e.g. `ArrayIterator|string[]`, which Psalm transforms to `ArrayIterator`. Defaults to `false`. - -This flag is deprecated and will be removed in Psalm 5 - #### allowStringToStandInForClass ```xml diff --git a/docs/running_psalm/issues/InvalidScalarArgument.md b/docs/running_psalm/issues/InvalidScalarArgument.md index bc0f97200f9..5a8713736cf 100644 --- a/docs/running_psalm/issues/InvalidScalarArgument.md +++ b/docs/running_psalm/issues/InvalidScalarArgument.md @@ -1,6 +1,10 @@ # InvalidScalarArgument -Emitted when a scalar value is passed to a method that expected another scalar type +Emitted when a scalar value is passed to a method that expected another scalar type. + +This is only emitted in situations where Psalm can be sure that PHP tries to coerce one scalar type to another. + +In all other cases `InvalidArgument` is emitted. ```php `. `TMixed` - denotes the `mixed` type, used when you don’t know the type of an expression. @@ -35,8 +35,6 @@ The classes are as follows: `TEmptyMixed` - as above, but empty. Generated for `$x` inside the `if` statement `if (!$x) {...}` when `$x` is `mixed` outside. -`TEmpty` - denotes the `empty` type, used to describe a type corresponding to no value whatsoever. Empty arrays `[]` have the type `array`. - `TIterable` - denotes the [`iterable` type](https://www.php.net/manual/en/language.types.iterable.php) (which can also result from an `is_iterable` check). `TResource` - denotes the `resource` type (e.g. a file handle). @@ -141,7 +139,7 @@ if (true === $first) { `TCallableString` - denotes the `callable-string` type, used to represent an unknown string that is also `callable`. -`THtmlEscapedString`, `TSqlSelectString` - these are special types, specifically for consumption by plugins. +`TSqlSelectString` - this is a special type, specifically for consumption by plugins. `TLowercaseString` - denotes a string where every character is lowercased. (which can also result from a `strtolower` call). diff --git a/examples/TemplateScanner.php b/examples/TemplateScanner.php index f713dfcc8ac..2ed2caae02f 100644 --- a/examples/TemplateScanner.php +++ b/examples/TemplateScanner.php @@ -25,7 +25,7 @@ public function scan( ): void { $stmts = $codebase->statements_provider->getStatementsForFile( $file_storage->file_path, - '7.4', + 7_04_00, $progress ); diff --git a/examples/plugins/ClassUnqualifier.php b/examples/plugins/ClassUnqualifier.php index be5555e82b5..bc84b0e21ac 100644 --- a/examples/plugins/ClassUnqualifier.php +++ b/examples/plugins/ClassUnqualifier.php @@ -41,9 +41,7 @@ public static function afterClassLikeExistenceCheck( $new_candidate_type = implode( '', array_map( - function ($f) { - return $f[0]; - }, + fn($f) => $f[0], $type_tokens ) ); diff --git a/examples/plugins/InternalChecker.php b/examples/plugins/InternalChecker.php new file mode 100644 index 00000000000..d5a701dcc6d --- /dev/null +++ b/examples/plugins/InternalChecker.php @@ -0,0 +1,58 @@ +getClasslikeStorage(); + if (!$storage->internal + && strpos($storage->name, 'Psalm\\Internal') === 0 + && $storage->location + ) { + IssueBuffer::maybeAdd( + new InternalClass( + "Class $storage->name must be marked @internal", + $storage->location, + $storage->name + ), + $event->getStatementsSource()->getSuppressedIssues(), + true + ); + + if (!$event->getCodebase()->alter_code) { + return null; + } + + $stmt = $event->getStmt(); + $docblock = $stmt->getDocComment(); + if ($docblock) { + $docblock_start = $docblock->getStartFilePos(); + $parsed_docblock = DocComment::parsePreservingLength($docblock); + } else { + $docblock_start = (int) $stmt->getAttribute('startFilePos'); + $parsed_docblock = new ParsedDocblock('', []); + } + $docblock_end = (int) $stmt->getAttribute('startFilePos'); + + $parsed_docblock->tags['internal'] = ['']; + $new_docblock_content = $parsed_docblock->render(''); + $event->setFileReplacements([ + new FileManipulation($docblock_start, $docblock_end, $new_docblock_content) + ]); + } + return null; + } +} diff --git a/examples/plugins/composer-based/echo-checker/EchoChecker.php b/examples/plugins/composer-based/echo-checker/EchoChecker.php index c75eac76a56..a7255e4fd4a 100644 --- a/examples/plugins/composer-based/echo-checker/EchoChecker.php +++ b/examples/plugins/composer-based/echo-checker/EchoChecker.php @@ -1,16 +1,15 @@ getStmt(); $statements_source = $event->getStatementsSource(); if ($stmt instanceof PhpParser\Node\Stmt\Echo_) { @@ -46,7 +46,6 @@ public static function afterStatementAnalysis(AfterStatementAnalysisEvent $event foreach ($types as $type) { if ($type instanceof TString && !$type instanceof TLiteralString - && !$type instanceof THtmlEscapedString ) { if (IssueBuffer::accepts( new ArgumentTypeCoercion( diff --git a/phpcs.xml b/phpcs.xml index 627943d181d..b1f79cf0c44 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -190,4 +190,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/psalm b/psalm index a633cb872ac..9d05b6082a4 100755 --- a/psalm +++ b/psalm @@ -1,6 +1,8 @@ #!/usr/bin/env php - - $this->php_major_version - $this->php_minor_version - $matches[0] $symbol_parts[1] - - $analysis_php_version_id - - - - - $codebase->php_major_version - $codebase->php_major_version - $codebase->php_major_version - $codebase->php_major_version - $codebase->php_minor_version - $codebase->php_minor_version - @@ -48,15 +31,6 @@ - - $codebase->php_major_version - $codebase->php_major_version - $codebase->php_major_version - $codebase->php_major_version - $codebase->php_minor_version - $codebase->php_minor_version - $codebase->php_minor_version - $comments[0] $stmt->props[0] @@ -68,44 +42,7 @@ $line_parts[1] - - - $codebase->php_major_version - $codebase->php_major_version - $codebase->php_major_version - $codebase->php_minor_version - $codebase->php_minor_version - - - - - $codebase->php_major_version - $project_analyzer->getCodebase()->php_major_version - $project_analyzer->getCodebase()->php_minor_version - - - - - $codebase->php_major_version - $codebase->php_major_version - $codebase->php_major_version - $codebase->php_major_version - $codebase->php_major_version - $codebase->php_major_version - $codebase->php_major_version - $codebase->php_minor_version - $codebase->php_minor_version - - - - $codebase->php_major_version - $codebase->php_minor_version - $this->codebase->php_major_version - $this->codebase->php_major_version - $this->codebase->php_minor_version - $this->codebase->php_minor_version - $destination_parts[1] $destination_parts[1] @@ -145,16 +82,6 @@ $catch_context->assigned_var_ids += $old_catch_assigned_var_ids - - - $codebase->php_major_version - $codebase->php_major_version - $codebase->php_major_version - $codebase->php_major_version - $codebase->php_minor_version - $codebase->php_minor_version - - $assertion->rule[0] @@ -193,30 +120,13 @@ $gettype_expr->getArgs()[0] - - - Type::getEmpty() - - - - Type::getEmpty() - Type::getEmpty() - Type::getEmpty() - Type::getEmpty() - $invalid_left_messages[0] $invalid_right_messages[0] - - $codebase->php_major_version - $codebase->php_major_version - $codebase->php_major_version - $codebase->php_major_version - $non_existent_method_ids[0] $parts[1] @@ -230,12 +140,6 @@ - - new TEmpty - new TEmpty - new TEmpty - new TEmpty - $args[0] $args[0] @@ -249,31 +153,6 @@ $stmt->getArgs()[0] - - - Type::getEmpty() - - - $codebase->php_major_version - $codebase->php_major_version - $codebase->php_minor_version - - - - - TEmpty::class - - - - - Type::getEmpty() - - - $codebase->php_major_version - $codebase->php_major_version - $codebase->php_minor_version - - $result->invalid_method_call_types[0] @@ -284,21 +163,6 @@ $result->non_existent_magic_method_ids[0] - - - Type::getEmpty() - - - - - Type::getEmpty() - - - $codebase->php_major_version - $codebase->php_major_version - $codebase->php_minor_version - - $assertion->rule[0] @@ -306,66 +170,16 @@ $callable_arg->items[1] - - - Type::getEmpty() - Type::getEmpty() - - - $statements_analyzer->getCodebase()->php_major_version - - - - - Type::getEmpty() - - - - - TMixed|TTemplateParam|TEmpty - new TEmpty - new TEmpty - - - Type::getEmpty() - Type::getEmpty() - - $invalid_fetch_types[0] - - Type::getEmpty() - $atomic_return_type->type_params[2] - - - $statements_analyzer->getCodebase()->php_major_version - $statements_analyzer->getCodebase()->php_minor_version - - - - - Type::getEmpty() - - - - - Type::getEmpty() - - - - - new TEmpty - new TEmpty - - $token_list[$iter] @@ -381,21 +195,7 @@ $stmt->expr->getArgs()[0] - - - new TEmpty() - new TEmpty() - - - Type::getEmpty() - Type::getEmpty() - - - - $codebase->php_major_version - $codebase->php_minor_version - $callables[0] $callables[0] @@ -461,10 +261,6 @@ - - $codebase->php_major_version - $codebase->php_minor_version - $doc_line_parts[1] $matches[0] @@ -473,10 +269,6 @@ - - $this->codebase->php_major_version - $this->codebase->php_minor_version - $imported_type_data[3] $l[4] @@ -484,12 +276,6 @@ $var_line_parts[0] - - - $codebase->php_major_version - $codebase->php_minor_version - - $node->getArgs()[0] @@ -511,83 +297,15 @@ - - $this->codebase->php_major_version - $this->codebase->php_major_version - $this->codebase->php_major_version - $this->codebase->php_major_version - $this->codebase->php_major_version - $this->codebase->php_major_version - $this->codebase->php_major_version - $this->codebase->php_minor_version - $this->codebase->php_minor_version - $this->codebase->php_minor_version - $this->codebase->php_minor_version - $stmt->stmts[0] - - - $this->codebase->php_major_version - - $cs[0] - - - $codebase->php_major_version - $codebase->php_minor_version - - - - - new TEmpty() - new TEmpty() - - - - - $codebase->php_major_version - - - - - new TEmpty() - new TEmpty() - - - Type::getEmpty() - Type::getEmpty() - Type::getEmpty() - Type::getEmpty() - Type::getEmpty() - Type::getEmpty() - Type::getEmpty() - Type::getEmpty() - Type::getEmpty() - Type::getEmpty() - Type::getEmpty() - Type::getEmpty() - Type::getEmpty() - - - - - new TEmpty - new TEmpty - - - Type::getEmpty() - Type::getEmpty() - Type::getEmpty() - Type::getEmpty() - - $combination->array_type_params[1] @@ -641,18 +359,7 @@ $rules[0] - - - new TEmpty - new TEmpty - new TEmpty() - - - - new TEmpty() - new THtmlEscapedString() - array_keys($template_type_map[$value])[0] @@ -663,9 +370,6 @@ - - new TEmpty - $type[0] diff --git a/psalm-language-server b/psalm-language-server index a1869f329ab..4bc838c8a1a 100755 --- a/psalm-language-server +++ b/psalm-language-server @@ -1,6 +1,8 @@ #!/usr/bin/env php + + + + + - @@ -46,6 +50,7 @@ + @@ -56,19 +61,13 @@ + - - - - - - - @@ -87,7 +86,6 @@ - diff --git a/psalter b/psalter index c36c93fec99..8fb9dd3e648 100755 --- a/psalter +++ b/psalter @@ -1,6 +1,8 @@ #!/usr/bin/env php populator = new Populator( - $config, $providers->classlike_storage_provider, $providers->file_storage_provider, $this->classlikes, @@ -534,7 +520,7 @@ public function getStatementsForFile(string $file_path): array { return $this->statements_provider->getStatementsForFile( $file_path, - $this->php_major_version . '.' . $this->php_minor_version, + $this->analysis_php_version_id, $this->progress ); } @@ -2010,4 +1996,19 @@ public function addTaintSink( $this->taint_flow_graph->addSink($sink); } + + public function getMinorAnalysisPhpVersion(): int + { + return self::transformPhpVersionId($this->analysis_php_version_id % 10_000, 100); + } + + public function getMajorAnalysisPhpVersion(): int + { + return self::transformPhpVersionId($this->analysis_php_version_id, 10_000); + } + + public static function transformPhpVersionId(int $php_version_id, int $div): int + { + return intdiv($php_version_id, $div); + } } diff --git a/src/Psalm/Config.php b/src/Psalm/Config.php index db8f8f06df1..76ae70f4ae3 100644 --- a/src/Psalm/Config.php +++ b/src/Psalm/Config.php @@ -5,9 +5,11 @@ use Composer\Autoload\ClassLoader; use Composer\Semver\Constraint\Constraint; use Composer\Semver\VersionParser; +use DOMAttr; use DOMDocument; use DomElement; use InvalidArgumentException; +use JsonException; use LogicException; use OutOfBoundsException; use Psalm\CodeLocation\Raw; @@ -37,9 +39,9 @@ use Psalm\Progress\VoidProgress; use SimpleXMLElement; use SimpleXMLIterator; +use Symfony\Component\Filesystem\Path; use Throwable; use UnexpectedValueException; -use Webmozart\PathUtil\Path; use XdgBaseDir\Xdg; use stdClass; @@ -101,6 +103,7 @@ use const DIRECTORY_SEPARATOR; use const E_USER_ERROR; use const GLOB_NOSORT; +use const JSON_THROW_ON_ERROR; use const LIBXML_ERR_ERROR; use const LIBXML_ERR_FATAL; use const LIBXML_NONET; @@ -316,11 +319,6 @@ class Config /** @var bool */ public $use_igbinary = false; - /** - * @var bool - */ - public $allow_phpstorm_generics = false; - /** * @var bool */ @@ -503,13 +501,6 @@ class Config /** @var ClassLoader|null */ private $composer_class_loader; - /** - * Custom functions that always exit - * - * @var array - */ - public $exit_functions = []; - /** * @var string */ @@ -534,7 +525,7 @@ class Config /** * @var int */ - public $max_string_length = 1000; + public $max_string_length = 1_000; /** @var ?IncludeCollector */ private $include_collector; @@ -775,6 +766,58 @@ private static function lineNumberToByteOffset(string $string, int $line_number) return $offset; } + private static function processDeprecatedAttribute( + DOMAttr $attribute, + string $file_contents, + self $config, + string $config_path + ): void { + $line = $attribute->getLineNo(); + assert($line > 0); // getLineNo() always returns non-zero for nodes loaded from file + + $offset = self::lineNumberToByteOffset($file_contents, $line); + $attribute_start = strrpos($file_contents, $attribute->name, $offset - strlen($file_contents)) ?: 0; + $attribute_end = $attribute_start + strlen($attribute->name) - 1; + + $config->config_issues[] = new ConfigIssue( + 'Attribute "' . $attribute->name . '" is deprecated ' + . 'and is going to be removed in the next major version', + new Raw( + $file_contents, + $config_path, + basename($config_path), + $attribute_start, + $attribute_end + ) + ); + } + + private static function processDeprecatedElement( + DomElement $deprecated_element_xml, + string $file_contents, + self $config, + string $config_path + ): void { + $line = $deprecated_element_xml->getLineNo(); + assert($line > 0); + + $offset = self::lineNumberToByteOffset($file_contents, $line); + $element_start = strpos($file_contents, $deprecated_element_xml->localName, $offset) ?: 0; + $element_end = $element_start + strlen($deprecated_element_xml->localName) - 1; + + $config->config_issues[] = new ConfigIssue( + 'Element "' . $deprecated_element_xml->localName . '" is deprecated ' + . 'and is going to be removed in the next major version', + new Raw( + $file_contents, + $config_path, + basename($config_path), + $element_start, + $element_end + ) + ); + } + private static function processConfigDeprecations( self $config, DOMDocument $dom_document, @@ -783,15 +826,11 @@ private static function processConfigDeprecations( ): void { $config->config_issues = []; - // Attributes to be removed in Psalm 5 - $deprecated_attributes = [ - 'allowCoercionFromStringToClassConst', - 'allowPhpStormGenerics', - ]; + // Attributes to be removed in Psalm 6 + $deprecated_attributes = []; - $deprecated_elements = [ - 'exitFunctions', - ]; + /** @var list */ + $deprecated_elements = []; $psalm_element_item = $dom_document->getElementsByTagName('psalm')->item(0); assert($psalm_element_item !== null); @@ -799,24 +838,7 @@ private static function processConfigDeprecations( foreach ($attributes as $attribute) { if (in_array($attribute->name, $deprecated_attributes, true)) { - $line = $attribute->getLineNo(); - assert($line > 0); // getLineNo() always returns non-zero for nodes loaded from file - - $offset = self::lineNumberToByteOffset($file_contents, $line); - $attribute_start = strrpos($file_contents, $attribute->name, $offset - strlen($file_contents)) ?: 0; - $attribute_end = $attribute_start + strlen($attribute->name) - 1; - - $config->config_issues[] = new ConfigIssue( - 'Attribute "' . $attribute->name . '" is deprecated ' - . 'and is going to be removed in the next major version', - new Raw( - $file_contents, - $config_path, - basename($config_path), - $attribute_start, - $attribute_end - ) - ); + self::processDeprecatedAttribute($attribute, $file_contents, $config, $config_path); } } @@ -827,25 +849,7 @@ private static function processConfigDeprecations( ); if ($deprecated_elements_xml->length) { $deprecated_element_xml = $deprecated_elements_xml->item(0); - assert($deprecated_element_xml !== null); - $line = $deprecated_element_xml->getLineNo(); - assert($line > 0); - - $offset = self::lineNumberToByteOffset($file_contents, $line); - $element_start = strpos($file_contents, $deprecated_element, $offset) ?: 0; - $element_end = $element_start + strlen($deprecated_element) - 1; - - $config->config_issues[] = new ConfigIssue( - 'Element "' . $deprecated_element . '" is deprecated ' - . 'and is going to be removed in the next major version', - new Raw( - $file_contents, - $config_path, - basename($config_path), - $element_start, - $element_end - ) - ); + self::processDeprecatedElement($deprecated_element_xml, $file_contents, $config, $config_path); } } } @@ -888,7 +892,6 @@ private static function fromXmlAndPaths( 'allowFileIncludes' => 'allow_includes', 'strictBinaryOperands' => 'strict_binary_operands', 'rememberPropertyAssignmentsAfterCall' => 'remember_property_assignments_after_call', - 'allowPhpStormGenerics' => 'allow_phpstorm_generics', 'allowStringToStandInForClass' => 'allow_string_standin_for_class', 'usePhpDocMethodsWithoutMagicCall' => 'use_phpdoc_method_without_magic_or_parent', 'usePhpDocPropertiesWithoutMagicCall' => 'use_phpdoc_property_without_magic_or_parent', @@ -1115,13 +1118,6 @@ private static function fromXmlAndPaths( } } - if (isset($config_xml->exitFunctions) && isset($config_xml->exitFunctions->function)) { - /** @var SimpleXMLElement $exit_function */ - foreach ($config_xml->exitFunctions->function as $exit_function) { - $config->exit_functions[strtolower((string) $exit_function['name'])] = true; - } - } - if (isset($config_xml->stubs) && isset($config_xml->stubs->file)) { /** @var SimpleXMLElement $stub_file */ foreach ($config_xml->stubs->file as $stub_file) { @@ -1872,7 +1868,7 @@ public function visitPreloadedStubFiles(Codebase $codebase, ?Progress $progress $core_generic_files = []; - if (PHP_VERSION_ID < 80000 && $codebase->php_major_version >= 8) { + if (PHP_VERSION_ID < 8_00_00 && $codebase->analysis_php_version_id >= 8_00_00) { $stringable_path = dirname(__DIR__, 2) . '/stubs/Php80.phpstub'; if (!file_exists($stringable_path)) { @@ -1882,7 +1878,7 @@ public function visitPreloadedStubFiles(Codebase $codebase, ?Progress $progress $core_generic_files[] = $stringable_path; } - if (PHP_VERSION_ID < 80100 && $codebase->php_major_version >= 8 && $codebase->php_minor_version >= 1) { + if (PHP_VERSION_ID < 8_01_00 && $codebase->analysis_php_version_id >= 8_01_00) { $stringable_path = dirname(__DIR__, 2) . '/stubs/Php81.phpstub'; if (!file_exists($stringable_path)) { @@ -1933,12 +1929,12 @@ public function visitStubFiles(Codebase $codebase, ?Progress $progress = null): $dir_lvl_2 . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'SPL.phpstub', ]; - if (PHP_VERSION_ID >= 80000 && $codebase->php_major_version >= 8) { + if (PHP_VERSION_ID >= 8_00_00 && $codebase->analysis_php_version_id >= 8_00_00) { $stringable_path = $dir_lvl_2 . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'Php80.phpstub'; $this->internal_stubs[] = $stringable_path; } - if (PHP_VERSION_ID >= 80100 && $codebase->php_major_version >= 8 && $codebase->php_minor_version >= 1) { + if (PHP_VERSION_ID >= 8_01_00 && $codebase->analysis_php_version_id >= 8_01_00) { $stringable_path = $dir_lvl_2 . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'Php81.phpstub'; $this->internal_stubs[] = $stringable_path; } @@ -2085,13 +2081,12 @@ public function visitComposerAutoloadFiles(ProjectAnalyzer $project_analyzer, ?P if (file_exists($vendor_autoload_files_path)) { $this->include_collector->runAndCollect( - function () use ($vendor_autoload_files_path) { + fn(): array => /** * @psalm-suppress UnresolvableInclude * @var string[] */ - return require $vendor_autoload_files_path; - } + require $vendor_autoload_files_path ); } @@ -2263,7 +2258,13 @@ public function getPHPVersionFromComposerJson(): ?string $composer_json_path = Composer::getJsonFilePath($this->base_dir); if (file_exists($composer_json_path)) { - if (!$composer_json = json_decode(file_get_contents($composer_json_path), true)) { + try { + $composer_json = json_decode(file_get_contents($composer_json_path), true, 512, JSON_THROW_ON_ERROR); + } catch (JsonException $e) { + $composer_json = null; + } + + if (!$composer_json) { throw new UnexpectedValueException('Invalid composer.json at ' . $composer_json_path); } $php_version = $composer_json['require']['php'] ?? null; diff --git a/src/Psalm/Config/Creator.php b/src/Psalm/Config/Creator.php index 36760726e1c..dca4c6e470e 100644 --- a/src/Psalm/Config/Creator.php +++ b/src/Psalm/Config/Creator.php @@ -2,6 +2,7 @@ namespace Psalm\Config; +use JsonException; use Psalm\Config; use Psalm\Exception\ConfigCreationException; use Psalm\Internal\Analyzer\IssueData; @@ -32,6 +33,7 @@ use const DIRECTORY_SEPARATOR; use const GLOB_NOSORT; +use const JSON_THROW_ON_ERROR; class Creator { @@ -134,9 +136,7 @@ public static function getLevel(array $issues, int $counted_types): int // remove any issues where < 0.1% of expressions are affected $filtered_issues = array_filter( $issues, - function ($amount): bool { - return $amount > 0.1; - } + fn($amount): bool => $amount > 0.1 ); if (array_sum($filtered_issues) > 0.5) { @@ -184,8 +184,19 @@ public static function getPaths(string $current_dir, ?string $suggested_dir): ar 'Problem during config autodiscovery - could not find composer.json during initialization.' ); } - - if (!$composer_json = json_decode(file_get_contents($composer_json_location), true)) { + try { + $composer_json = json_decode( + file_get_contents($composer_json_location), + true, + 512, + JSON_THROW_ON_ERROR + ); + } catch (JsonException $e) { + throw new ConfigCreationException( + 'Invalid composer.json at ' . $composer_json_location . ': ' . $e->getMessage() + ); + } + if (!$composer_json) { throw new ConfigCreationException('Invalid composer.json at ' . $composer_json_location); } diff --git a/src/Psalm/Config/FileFilter.php b/src/Psalm/Config/FileFilter.php index 1443594c7a7..2db9bdaaee8 100644 --- a/src/Psalm/Config/FileFilter.php +++ b/src/Psalm/Config/FileFilter.php @@ -2,6 +2,7 @@ namespace Psalm\Config; +use FilesystemIterator; use Psalm\Exception\ConfigException; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; @@ -112,6 +113,7 @@ public static function loadFromArray( foreach ($config['directory'] as $directory) { $directory_path = (string) ($directory['name'] ?? ''); $ignore_type_stats = (bool) ($directory['ignoreTypeStats'] ?? false); + $resolve_symlinks = (bool) ($directory['resolveSymlinks'] ?? false); $declare_strict_types = (bool) ($directory['useStrictTypes'] ?? false); if ($directory_path[0] === '/' && DIRECTORY_SEPARATOR === '/') { @@ -182,27 +184,33 @@ public static function loadFromArray( ); } - /** @var RecursiveDirectoryIterator */ - $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($directory_path)); - $iterator->rewind(); + if ($resolve_symlinks) { + /** @var RecursiveDirectoryIterator */ + $iterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($directory_path, FilesystemIterator::SKIP_DOTS) + ); + $iterator->rewind(); - while ($iterator->valid()) { - if (!$iterator->isDot() && $iterator->isLink()) { - $linked_path = readlink($iterator->getPathname()); + while ($iterator->valid()) { + if ($iterator->isLink()) { + $linked_path = readlink($iterator->getPathname()); - if (stripos($linked_path, $directory_path) !== 0) { - if ($ignore_type_stats && $filter instanceof ProjectFileFilter) { - $filter->ignore_type_stats[$directory_path] = true; - } + if (stripos($linked_path, $directory_path) !== 0) { + if ($ignore_type_stats && $filter instanceof ProjectFileFilter) { + $filter->ignore_type_stats[$directory_path] = true; + } - if ($declare_strict_types && $filter instanceof ProjectFileFilter) { - $filter->declare_strict_types[$directory_path] = true; - } + if ($declare_strict_types && $filter instanceof ProjectFileFilter) { + $filter->declare_strict_types[$directory_path] = true; + } - if (is_dir($linked_path)) { - $filter->addDirectory($linked_path); + if (is_dir($linked_path)) { + $filter->addDirectory($linked_path); + } } } + + $iterator->next(); } $iterator->next(); @@ -346,6 +354,7 @@ public static function loadFromXMLElement( $config['directory'][] = [ 'name' => (string) $directory['name'], 'ignoreTypeStats' => strtolower((string) ($directory['ignoreTypeStats'] ?? '')) === 'true', + 'resolveSymlinks' => strtolower((string) ($directory['resolveSymlinks'] ?? '')) === 'true', 'useStrictTypes' => strtolower((string) ($directory['useStrictTypes'] ?? '')) === 'true', ]; } @@ -406,9 +415,7 @@ public static function loadFromXMLElement( private static function isRegularExpression(string $string): bool { set_error_handler( - function (): bool { - return false; - }, + fn(): bool => false, E_WARNING ); $is_regexp = preg_match($string, '') !== false; diff --git a/src/Psalm/Config/IssueHandler.php b/src/Psalm/Config/IssueHandler.php index cdc2c288289..d3b6a3cdf0a 100644 --- a/src/Psalm/Config/IssueHandler.php +++ b/src/Psalm/Config/IssueHandler.php @@ -149,26 +149,22 @@ public static function getAllIssueTypes(): array { return array_filter( array_map( - function (string $file_name): string { - return substr($file_name, 0, -4); - }, + fn(string $file_name): string => substr($file_name, 0, -4), scandir(dirname(__DIR__) . '/Issue', SCANDIR_SORT_NONE) ), - function (string $issue_name): bool { - return $issue_name !== '' - && $issue_name !== 'MethodIssue' - && $issue_name !== 'PropertyIssue' - && $issue_name !== 'FunctionIssue' - && $issue_name !== 'ArgumentIssue' - && $issue_name !== 'VariableIssue' - && $issue_name !== 'ClassIssue' - && $issue_name !== 'CodeIssue' - && $issue_name !== 'PsalmInternalError' - && $issue_name !== 'ParseError' - && $issue_name !== 'PluginIssue' - && $issue_name !== 'MixedIssue' - && $issue_name !== 'MixedIssueTrait'; - } + fn(string $issue_name): bool => $issue_name !== '' + && $issue_name !== 'MethodIssue' + && $issue_name !== 'PropertyIssue' + && $issue_name !== 'FunctionIssue' + && $issue_name !== 'ArgumentIssue' + && $issue_name !== 'VariableIssue' + && $issue_name !== 'ClassIssue' + && $issue_name !== 'CodeIssue' + && $issue_name !== 'PsalmInternalError' + && $issue_name !== 'ParseError' + && $issue_name !== 'PluginIssue' + && $issue_name !== 'MixedIssue' + && $issue_name !== 'MixedIssueTrait' ); } } diff --git a/src/Psalm/Context.php b/src/Psalm/Context.php index 3b9470d6f64..78b116ba034 100644 --- a/src/Psalm/Context.php +++ b/src/Psalm/Context.php @@ -27,6 +27,8 @@ use function strpos; use function strtolower; +use const JSON_THROW_ON_ERROR; + class Context { /** @@ -770,7 +772,7 @@ public function getScopeSummary(): string $summary[$k] = $v->getId(); } - return json_encode($summary); + return json_encode($summary, JSON_THROW_ON_ERROR); } public function defineGlobals(): void diff --git a/src/Psalm/DocComment.php b/src/Psalm/DocComment.php index ae3cd6ca32a..d1c0bc51dd0 100644 --- a/src/Psalm/DocComment.php +++ b/src/Psalm/DocComment.php @@ -7,25 +7,15 @@ use Psalm\Internal\Scanner\DocblockParser; use Psalm\Internal\Scanner\ParsedDocblock; -use function array_filter; use function explode; -use function implode; use function in_array; -use function min; use function preg_match; -use function preg_match_all; -use function preg_replace; -use function rtrim; -use function str_repeat; -use function str_replace; use function strlen; use function strpos; use function strspn; use function substr; use function trim; -use const PREG_SET_ORDER; - class DocComment { public const PSALM_ANNOTATIONS = [ @@ -45,140 +35,6 @@ class DocComment 'consistent-templates', 'if-this-is', 'this-out' ]; - /** - * Parse a docblock comment into its parts. - * - * Taken from advanced api docmaker, which was taken from - * https://github.com/facebook/libphutil/blob/master/src/parser/docblock/PhutilDocblockParser.php - * - * @return array Array of the main comment and specials - * - * @psalm-return array{description:string, specials:array>} - * @psalm-suppress PossiblyUnusedMethod - * - * @deprecated use parsePreservingLength instead, going to be removed in Psalm 5 - * - * @psalm-pure - */ - public static function parse(string $docblock, ?int $line_number = null, bool $preserve_format = false): array - { - // Strip off comments. - $docblock = trim($docblock); - $docblock = preg_replace('@^/\*\*@', '', $docblock); - $docblock = preg_replace('@\*/$@', '', $docblock); - $docblock = preg_replace('@^[ \t]*\*@m', '', $docblock); - - // Normalize multi-line @specials. - $lines = explode("\n", $docblock); - - $line_map = []; - - $last = false; - foreach ($lines as $k => $line) { - if (preg_match('/^\s?@\w/i', $line)) { - $last = $k; - } elseif (preg_match('/^\s*$/', $line)) { - $last = false; - } elseif ($last !== false) { - $old_last_line = $lines[$last]; - $lines[$last] = rtrim($old_last_line) - . ($preserve_format || trim($old_last_line) === '@return' ? "\n" . $line : ' ' . trim($line)); - - if ($line_number) { - $old_line_number = $line_map[$old_last_line]; - unset($line_map[$old_last_line]); - $line_map[$lines[$last]] = $old_line_number; - } - - unset($lines[$k]); - } - - if ($line_number) { - $line_map[$line] = $line_number++; - } - } - - $special = []; - - if ($preserve_format) { - foreach ($lines as $m => $line) { - if (preg_match('/^\s?@([\w\-:]+)[\t ]*(.*)$/sm', $line, $matches)) { - [$full_match, $type, $data] = $matches; - - $docblock = str_replace($full_match, '', $docblock); - - if (empty($special[$type])) { - $special[$type] = []; - } - - $line_number = $line_map && isset($line_map[$full_match]) ? $line_map[$full_match] : $m; - - $special[$type][$line_number] = rtrim($data); - } - } - } else { - $docblock = implode("\n", $lines); - - // Parse @specials. - if (preg_match_all('/^\s?@([\w\-:]+)[\t ]*([^\n]*)/m', $docblock, $matches, PREG_SET_ORDER)) { - $docblock = preg_replace('/^\s?@([\w\-:]+)\s*([^\n]*)/m', '', $docblock); - foreach ($matches as $m => $match) { - [$_, $type, $data] = $match; - - if (empty($special[$type])) { - $special[$type] = []; - } - - $line_number = $line_map && isset($line_map[$_]) ? $line_map[$_] : $m; - - $special[$type][$line_number] = $data; - } - } - } - - $docblock = str_replace("\t", ' ', $docblock); - - // Smush the whole docblock to the left edge. - $min_indent = 80; - $indent = 0; - foreach (array_filter(explode("\n", $docblock)) as $line) { - for ($ii = 0, $iiMax = strlen($line); $ii < $iiMax; ++$ii) { - if ($line[$ii] !== ' ') { - break; - } - ++$indent; - } - - $min_indent = min($indent, $min_indent); - } - - $docblock = preg_replace('/^' . str_repeat(' ', $min_indent) . '/m', '', $docblock); - $docblock = rtrim($docblock); - - // Trim any empty lines off the front, but leave the indent level if there - // is one. - $docblock = preg_replace('/^\s*\n/', '', $docblock); - - foreach ($special as $special_key => $_) { - if (strpos($special_key, 'psalm-') === 0) { - $special_key = substr($special_key, 6); - - if (!in_array( - $special_key, - self::PSALM_ANNOTATIONS, - true - )) { - throw new DocblockParseException('Unrecognised annotation @psalm-' . $special_key); - } - } - } - - return [ - 'description' => $docblock, - 'specials' => $special, - ]; - } - /** * Parse a docblock comment into its parts. */ diff --git a/src/Psalm/ErrorBaseline.php b/src/Psalm/ErrorBaseline.php index 9759ab944c5..d0cc5ddd3e1 100644 --- a/src/Psalm/ErrorBaseline.php +++ b/src/Psalm/ErrorBaseline.php @@ -48,9 +48,7 @@ public static function countTotalIssues(array $existingIssues): int /** * @param array{o:int, s:array} $existingIssue */ - function (int $carry, array $existingIssue): int { - return $carry + $existingIssue['o']; - }, + fn(int $carry, array $existingIssue): int => $carry + $existingIssue['o'], 0 ); } @@ -260,9 +258,7 @@ private static function writeToFile( ('php:' . PHP_VERSION), ], array_map( - function (string $extension): string { - return $extension . ':' . phpversion($extension); - }, + fn(string $extension): string => $extension . ':' . phpversion($extension), $extensions ) ))); @@ -282,12 +278,7 @@ function (string $extension): string { foreach ($existingIssueType['s'] as $selection) { $codeNode = $baselineDoc->createElement('code'); - - /** @todo in major version release (e.g. Psalm 5) replace $selection with trim($selection) - * This will be a minor BC break as baselines generated will then not be compatible with Psalm - * versions from before PR https://github.com/vimeo/psalm/pull/6000 - */ - $codeNode->textContent = $selection; + $codeNode->textContent = trim($selection); $issueNode->appendChild($codeNode); } $fileNode->appendChild($issueNode); @@ -304,21 +295,18 @@ function (string $extension): string { /** * @param string[] $matches */ - function (array $matches): string { - return - ' 'saveXML() ); diff --git a/src/Psalm/Internal/Algebra.php b/src/Psalm/Internal/Algebra.php index f60f76446b1..d6ff1b7bde1 100644 --- a/src/Psalm/Internal/Algebra.php +++ b/src/Psalm/Internal/Algebra.php @@ -18,6 +18,9 @@ use function mt_rand; use function substr; +/** + * @internal + */ class Algebra { /** @@ -96,7 +99,7 @@ public static function simplifyCNF(array $clauses): array //65536 seems to be a significant threshold, when put at 65537, the code https://psalm.dev/r/216f362ea6 goes //from seconds in analysis to many minutes - if ($clause_count > 65536) { + if ($clause_count > 65_536) { return []; } @@ -197,9 +200,7 @@ public static function simplifyCNF(array $clauses): array $clause_var_possibilities = array_values( array_filter( $clause_b->possibilities[$clause_var], - function (string $possible_type) use ($negated_clause_type): bool { - return $possible_type !== $negated_clause_type; - } + fn(string $possible_type): bool => $possible_type !== $negated_clause_type ) ); @@ -304,9 +305,7 @@ public static function getTruthsFromFormula( // if there's only one active clause, return all the non-negation clause members ORed together $things_that_can_be_said = array_filter( $possible_types, - function (string $possible_type): bool { - return $possible_type[0] !== '!'; - } + fn(string $possible_type): bool => $possible_type[0] !== '!' ); if ($things_that_can_be_said && count($things_that_can_be_said) === count($possible_types)) { @@ -440,7 +439,7 @@ public static function groupImpossibilities(array $clauses): array ++$complexity; - if ($complexity > 20000) { + if ($complexity > 20_000) { throw new ComplicatedExpressionException(); } } @@ -466,7 +465,7 @@ public static function combineOredClauses( array $right_clauses, int $conditional_object_id ): array { - if (count($left_clauses) > 60000 || count($right_clauses) > 60000) { + if (count($left_clauses) > 60_000 || count($right_clauses) > 60_000) { return []; } @@ -592,13 +591,11 @@ public static function negateFormula(array $clauses): array { $clauses = array_filter( $clauses, - function ($clause) { - return $clause->reconcilable; - } + fn($clause) => $clause->reconcilable ); if (!$clauses) { - $cond_id = mt_rand(0, 100000000); + $cond_id = mt_rand(0, 100_000_000); return [new Clause([], $cond_id, $cond_id, true)]; } @@ -613,14 +610,14 @@ function ($clause) { $impossible_clauses = self::groupImpossibilities($clauses_with_impossibilities); if (!$impossible_clauses) { - $cond_id = mt_rand(0, 100000000); + $cond_id = mt_rand(0, 100_000_000); return [new Clause([], $cond_id, $cond_id, true)]; } $negated = self::simplifyCNF($impossible_clauses); if (!$negated) { - $cond_id = mt_rand(0, 100000000); + $cond_id = mt_rand(0, 100_000_000); return [new Clause([], $cond_id, $cond_id, true)]; } diff --git a/src/Psalm/Internal/Algebra/FormulaGenerator.php b/src/Psalm/Internal/Algebra/FormulaGenerator.php index b6504af7082..66ae5f7fb63 100644 --- a/src/Psalm/Internal/Algebra/FormulaGenerator.php +++ b/src/Psalm/Internal/Algebra/FormulaGenerator.php @@ -19,6 +19,9 @@ use function strlen; use function substr; +/** + * @internal + */ class FormulaGenerator { /** diff --git a/src/Psalm/Internal/Analyzer/AttributeAnalyzer.php b/src/Psalm/Internal/Analyzer/AttributeAnalyzer.php index 3e18d03c1e5..66f1f109163 100644 --- a/src/Psalm/Internal/Analyzer/AttributeAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/AttributeAnalyzer.php @@ -21,6 +21,9 @@ use function reset; +/** + * @internal + */ class AttributeAnalyzer { /** diff --git a/src/Psalm/Internal/Analyzer/ClassAnalyzer.php b/src/Psalm/Internal/Analyzer/ClassAnalyzer.php index 2e21a870e59..e4b95bed867 100644 --- a/src/Psalm/Internal/Analyzer/ClassAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/ClassAnalyzer.php @@ -298,7 +298,7 @@ public function analyze( $union = new Union($mixins); $static_self = new TNamedObject($storage->name); - $static_self->was_static = true; + $static_self->is_static = true; $union = TypeExpander::expandUnion( $codebase, @@ -898,11 +898,9 @@ public static function addContextProperties( if ($property_type_location && !$fleshed_out_type->isMixed()) { $stmt = array_filter( $stmts, - function ($stmt) use ($property_name): bool { - return $stmt instanceof PhpParser\Node\Stmt\Property - && isset($stmt->props[0]->name->name) - && $stmt->props[0]->name->name === $property_name; - } + fn($stmt): bool => $stmt instanceof PhpParser\Node\Stmt\Property + && isset($stmt->props[0]->name->name) + && $stmt->props[0]->name->name === $property_name ); $suppressed = []; @@ -1229,7 +1227,7 @@ function (FunctionLikeParameter $param): PhpParser\Node\Arg { $method_context->self = $fq_class_name; $this_atomic_object_type = new TNamedObject($fq_class_name); - $this_atomic_object_type->was_static = !$storage->final; + $this_atomic_object_type->is_static = !$storage->final; $method_context->vars_in_scope['$this'] = new Union([$this_atomic_object_type]); $method_context->vars_possibly_in_scope['$this'] = true; @@ -1553,7 +1551,7 @@ private function checkForMissingPropertyType( if ($suggested_type && !$suggested_type->isNull()) { $message .= ' - consider ' . str_replace( - ['', ''], + ['', ''], '', (string)$suggested_type ); @@ -1608,8 +1606,7 @@ private static function addOrUpdatePropertyType( $codebase = $project_analyzer->getCodebase(); $allow_native_type = !$docblock_only - && $codebase->php_major_version >= 7 - && ($codebase->php_major_version > 7 || $codebase->php_minor_version >= 4) + && $codebase->analysis_php_version_id >= 7_04_00 && $codebase->allow_backwards_incompatible_changes; $manipulator->setType( @@ -1618,8 +1615,7 @@ private static function addOrUpdatePropertyType( $source->getNamespace(), $source->getAliasedClassesFlipped(), $source->getFQCLN(), - $codebase->php_major_version, - $codebase->php_minor_version + $codebase->analysis_php_version_id ) : null, $inferred_type->toNamespacedString( $source->getNamespace(), @@ -1633,7 +1629,7 @@ private static function addOrUpdatePropertyType( $source->getFQCLN(), true ), - $inferred_type->canBeFullyExpressedInPhp($codebase->php_major_version, $codebase->php_minor_version) + $inferred_type->canBeFullyExpressedInPhp($codebase->analysis_php_version_id) ); } @@ -1950,9 +1946,7 @@ public static function analyzeClassMethodReturnType( } $overridden_method_ids = array_map( - function ($method_id) { - return $method_id->__toString(); - }, + fn($method_id) => $method_id->__toString(), $overridden_method_ids ); diff --git a/src/Psalm/Internal/Analyzer/ClassLikeAnalyzer.php b/src/Psalm/Internal/Analyzer/ClassLikeAnalyzer.php index 0b4934e8de0..c7d608722a8 100644 --- a/src/Psalm/Internal/Analyzer/ClassLikeAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/ClassLikeAnalyzer.php @@ -50,7 +50,7 @@ abstract class ClassLikeAnalyzer extends SourceAnalyzer 'bool' => 'bool', 'false' => 'false', 'object' => 'object', - 'empty' => 'empty', + 'never' => 'never', 'callable' => 'callable', 'array' => 'array', 'iterable' => 'iterable', diff --git a/src/Psalm/Internal/Analyzer/ClassLikeNameOptions.php b/src/Psalm/Internal/Analyzer/ClassLikeNameOptions.php index dff50564c9b..5e5a2cd1af7 100644 --- a/src/Psalm/Internal/Analyzer/ClassLikeNameOptions.php +++ b/src/Psalm/Internal/Analyzer/ClassLikeNameOptions.php @@ -2,6 +2,9 @@ namespace Psalm\Internal\Analyzer; +/** + * @internal + */ class ClassLikeNameOptions { /** @var bool */ diff --git a/src/Psalm/Internal/Analyzer/ClosureAnalyzer.php b/src/Psalm/Internal/Analyzer/ClosureAnalyzer.php index 1b36c9becf3..1f3d50de1a6 100644 --- a/src/Psalm/Internal/Analyzer/ClosureAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/ClosureAnalyzer.php @@ -95,7 +95,7 @@ public static function analyzeExpression( $use_context->vars_in_scope['$this'] = clone $context->vars_in_scope['$this']; } elseif ($context->self) { $this_atomic = new TNamedObject($context->self); - $this_atomic->was_static = true; + $this_atomic->is_static = true; $use_context->vars_in_scope['$this'] = new Union([$this_atomic]); } diff --git a/src/Psalm/Internal/Analyzer/DataFlowNodeData.php b/src/Psalm/Internal/Analyzer/DataFlowNodeData.php index a33ac96961f..6973c69f219 100644 --- a/src/Psalm/Internal/Analyzer/DataFlowNodeData.php +++ b/src/Psalm/Internal/Analyzer/DataFlowNodeData.php @@ -4,6 +4,8 @@ /** * @psalm-immutable + * + * @internal */ class DataFlowNodeData { diff --git a/src/Psalm/Internal/Analyzer/FunctionLike/ReturnTypeAnalyzer.php b/src/Psalm/Internal/Analyzer/FunctionLike/ReturnTypeAnalyzer.php index 2566009bcba..3203c93b34e 100644 --- a/src/Psalm/Internal/Analyzer/FunctionLike/ReturnTypeAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/FunctionLike/ReturnTypeAnalyzer.php @@ -156,7 +156,6 @@ public static function verifyReturnType( && ScopeAnalyzer::getControlActions( $function_stmts, $type_provider, - $codebase->config->exit_functions, [] ) !== [ScopeAnalyzer::ACTION_END] && !$inferred_yield_types @@ -177,7 +176,6 @@ public static function verifyReturnType( $control_actions = ScopeAnalyzer::getControlActions( $function_stmts, $type_provider, - $codebase->config->exit_functions, [], false ); @@ -237,13 +235,11 @@ public static function verifyReturnType( } $number_of_types = count($inferred_return_type_parts); - // we filter TNever and TEmpty that have no bearing on the return type + // we filter TNever that have no bearing on the return type if ($number_of_types > 1) { $inferred_return_type_parts = array_filter( $inferred_return_type_parts, - static function (Union $union_type): bool { - return !($union_type->isNever() || $union_type->isEmpty()); - } + static fn(Union $union_type): bool => !$union_type->isNever() ); } @@ -490,7 +486,7 @@ static function (Union $union_type): bool { return null; } - if ($inferred_return_type->hasMixed() || $inferred_return_type->isEmpty()) { + if ($inferred_return_type->hasMixed()) { if (IssueBuffer::accepts( new MixedInferredReturnType( 'Could not verify return type \'' . $declared_return_type . '\' for ' . @@ -942,7 +938,7 @@ private static function addOrUpdateReturnType( } $allow_native_type = !$docblock_only - && $codebase->php_major_version >= 7 + && $codebase->analysis_php_version_id >= 7_00_00 && ( $codebase->allow_backwards_incompatible_changes || $is_final @@ -955,8 +951,7 @@ private static function addOrUpdateReturnType( $source->getNamespace(), $source->getAliasedClassesFlipped(), $source->getFQCLN(), - $codebase->php_major_version, - $codebase->php_minor_version + $codebase->analysis_php_version_id ) : null, $inferred_return_type->toNamespacedString( $source->getNamespace(), @@ -970,7 +965,7 @@ private static function addOrUpdateReturnType( $source->getFQCLN(), true ), - $inferred_return_type->canBeFullyExpressedInPhp($codebase->php_major_version, $codebase->php_minor_version), + $inferred_return_type->canBeFullyExpressedInPhp($codebase->analysis_php_version_id), $function_like_storage->return_type_description ?? null ); } diff --git a/src/Psalm/Internal/Analyzer/FunctionLike/ReturnTypeCollector.php b/src/Psalm/Internal/Analyzer/FunctionLike/ReturnTypeCollector.php index f0ab1bc8447..824d6c97a34 100644 --- a/src/Psalm/Internal/Analyzer/FunctionLike/ReturnTypeCollector.php +++ b/src/Psalm/Internal/Analyzer/FunctionLike/ReturnTypeCollector.php @@ -19,6 +19,8 @@ /** * A class for analysing a given method call's effects in relation to $this/self and also looking at return types + * + * @internal */ class ReturnTypeCollector { @@ -31,8 +33,6 @@ class ReturnTypeCollector * @return list a list of return types * * @psalm-suppress ComplexMethod to be refactored - * - * TODO: This would probably benefit from using the list of exit_functions */ public static function getReturnTypes( Codebase $codebase, diff --git a/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php b/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php index 99a11cbb337..47e70f364d1 100644 --- a/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php @@ -503,7 +503,7 @@ public function analyze( && !$inferred_return_type->isSingleIntLiteral() && !$inferred_return_type->isSingleStringLiteral() && !$inferred_return_type->isTrue() - && $inferred_return_type->getId() !== 'array' + && !$inferred_return_type->isEmptyArray() ) { $manipulator->makePure(); } @@ -533,7 +533,6 @@ public function analyze( $final_actions = ScopeAnalyzer::getControlActions( $this->function->getStmts() ?: [], null, - $codebase->config->exit_functions, [] ); @@ -1443,7 +1442,7 @@ public function addOrUpdateParamType( } $allow_native_type = !$docblock_only - && $codebase->php_major_version >= 7 + && $codebase->analysis_php_version_id >= 7_00_00 && ( $codebase->allow_backwards_incompatible_changes || $is_final @@ -1457,8 +1456,7 @@ public function addOrUpdateParamType( $this->source->getNamespace(), $this->source->getAliasedClassesFlipped(), $this->source->getFQCLN(), - $project_analyzer->getCodebase()->php_major_version, - $project_analyzer->getCodebase()->php_minor_version + $project_analyzer->getCodebase()->analysis_php_version_id ) : null, $inferred_return_type->toNamespacedString( $this->source->getNamespace(), @@ -1813,7 +1811,7 @@ private function getFunctionInformation( $this_object_type = new TNamedObject($context->self); } - $this_object_type->was_static = !$storage->final; + $this_object_type->is_static = !$storage->final; if ($this->storage instanceof MethodStorage && $this->storage->if_this_is_type) { $template_result = new TemplateResult($this->getTemplateTypeMap() ?? [], []); diff --git a/src/Psalm/Internal/Analyzer/IssueData.php b/src/Psalm/Internal/Analyzer/IssueData.php index 4ab16f2602e..318afe57f6a 100644 --- a/src/Psalm/Internal/Analyzer/IssueData.php +++ b/src/Psalm/Internal/Analyzer/IssueData.php @@ -6,6 +6,9 @@ use const STR_PAD_LEFT; +/** + * @internal + */ class IssueData { /** diff --git a/src/Psalm/Internal/Analyzer/MethodComparator.php b/src/Psalm/Internal/Analyzer/MethodComparator.php index 16ff2d9d98b..0de16d9ef99 100644 --- a/src/Psalm/Internal/Analyzer/MethodComparator.php +++ b/src/Psalm/Internal/Analyzer/MethodComparator.php @@ -39,6 +39,9 @@ use function strpos; use function strtolower; +/** + * @internal + */ class MethodComparator { /** @@ -92,7 +95,7 @@ public static function compare( $cased_implementer_method_id, $prevent_method_signature_mismatch, $prevent_abstract_override, - $codebase->php_major_version >= 8, + $codebase->analysis_php_version_id >= 8_00_00, $code_location, $suppressed_issues ); @@ -559,9 +562,7 @@ private static function compareMethodSignatureParams( $implementer_classlike_storage->parent_class ); - $is_contained_by = (($codebase->php_major_version === 7 - && $codebase->php_minor_version === 4) - || $codebase->php_major_version >= 8) + $is_contained_by = $codebase->analysis_php_version_id >= 7_04_00 && $guide_param_signature_type ? UnionTypeComparator::isContainedBy( $codebase, @@ -575,7 +576,7 @@ private static function compareMethodSignatureParams( if (!$is_contained_by) { $config = Config::getInstance(); - if ($codebase->php_major_version >= 8 + if ($codebase->analysis_php_version_id >= 8_00_00 || $guide_classlike_storage->is_trait === $implementer_classlike_storage->is_trait || !in_array($guide_classlike_storage->name, $implementer_classlike_storage->used_traits) || $implementer_method_storage->defining_fqcln !== $implementer_classlike_storage->name @@ -853,9 +854,7 @@ private static function compareMethodSignatureReturnTypes( $implementer_classlike_storage->parent_class ) : null; - $is_contained_by = (($codebase->php_major_version === 7 - && $codebase->php_minor_version === 4) - || $codebase->php_major_version >= 8) + $is_contained_by = $codebase->analysis_php_version_id >= 7_04_00 && $implementer_signature_return_type ? UnionTypeComparator::isContainedBy( $codebase, @@ -865,7 +864,7 @@ private static function compareMethodSignatureReturnTypes( : UnionTypeComparator::isContainedByInPhp($implementer_signature_return_type, $guide_signature_return_type); if (!$is_contained_by) { - if ($codebase->php_major_version >= 8 + if ($codebase->analysis_php_version_id >= 8_00_00 || $guide_classlike_storage->is_trait === $implementer_classlike_storage->is_trait || !in_array($guide_classlike_storage->name, $implementer_classlike_storage->used_traits) || $implementer_method_storage->defining_fqcln !== $implementer_classlike_storage->name diff --git a/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php b/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php index 4e99005e19e..fe3b80318df 100644 --- a/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php @@ -569,7 +569,7 @@ private function generatePHPVersionMessage(): string { $codebase = $this->codebase; - $version = $codebase->php_major_version . '.' . $codebase->php_minor_version; + $version = $codebase->getMajorAnalysisPhpVersion() . '.' . $codebase->getMinorAnalysisPhpVersion(); switch ($codebase->php_version_source) { case 'cli': @@ -1297,17 +1297,15 @@ public function setPhpVersion(string $version, string $source): void $php_major_version = (int) $php_major_version; $php_minor_version = (int) $php_minor_version; - if ($this->codebase->php_major_version !== $php_major_version - || $this->codebase->php_minor_version !== $php_minor_version - ) { + $analysis_php_version_id = $php_major_version * 10_000 + $php_minor_version * 100; + + if ($this->codebase->analysis_php_version_id !== $analysis_php_version_id) { // reset lexer and parser when php version changes StatementsProvider::clearLexer(); StatementsProvider::clearParser(); } - $this->codebase->php_major_version = $php_major_version; - $this->codebase->php_minor_version = $php_minor_version; - $this->codebase->analysis_php_version_id = $php_major_version * 10000 + $php_minor_version * 100; + $this->codebase->analysis_php_version_id = $analysis_php_version_id; $this->codebase->php_version_source = $source; } diff --git a/src/Psalm/Internal/Analyzer/ScopeAnalyzer.php b/src/Psalm/Internal/Analyzer/ScopeAnalyzer.php index d15b91bbf9e..700677ca252 100644 --- a/src/Psalm/Internal/Analyzer/ScopeAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/ScopeAnalyzer.php @@ -14,7 +14,6 @@ use function array_values; use function count; use function in_array; -use function strtolower; /** * @internal @@ -67,7 +66,6 @@ public static function doesEverBreak(array $stmts): bool /** * @param array $stmts - * @param array $exit_functions * @param list<'loop'|'switch'> $break_types * @param bool $return_is_exit Exit and Throw statements are treated differently from return if this is false * @@ -78,7 +76,6 @@ public static function doesEverBreak(array $stmts): bool public static function getControlActions( array $stmts, ?NodeDataProvider $nodes, - array $exit_functions, array $break_types, bool $return_is_exit = true ): array { @@ -109,32 +106,6 @@ public static function getControlActions( return array_values(array_unique(array_merge($control_actions, [self::ACTION_END]))); } - if ($exit_functions) { - if ($stmt->expr instanceof PhpParser\Node\Expr\FuncCall - || $stmt->expr instanceof PhpParser\Node\Expr\StaticCall - ) { - if ($stmt->expr instanceof PhpParser\Node\Expr\FuncCall) { - /** @var string|null */ - $resolved_name = $stmt->expr->name->getAttribute('resolvedName'); - - if ($resolved_name && isset($exit_functions[strtolower($resolved_name)])) { - return array_values(array_unique(array_merge($control_actions, [self::ACTION_END]))); - } - } elseif ($stmt->expr->class instanceof PhpParser\Node\Name - && $stmt->expr->name instanceof PhpParser\Node\Identifier - ) { - /** @var string|null */ - $resolved_class_name = $stmt->expr->class->getAttribute('resolvedName'); - - if ($resolved_class_name - && isset($exit_functions[strtolower($resolved_class_name . '::' . $stmt->expr->name)]) - ) { - return array_values(array_unique(array_merge($control_actions, [self::ACTION_END]))); - } - } - } - } - continue; } @@ -174,23 +145,19 @@ public static function getControlActions( $if_statement_actions = self::getControlActions( $stmt->stmts, $nodes, - $exit_functions, $break_types, $return_is_exit ); $all_leave = !array_filter( $if_statement_actions, - function ($action) { - return $action === self::ACTION_NONE; - } + fn($action) => $action === self::ACTION_NONE ); $else_statement_actions = $stmt->else ? self::getControlActions( $stmt->else->stmts, $nodes, - $exit_functions, $break_types, $return_is_exit ) : []; @@ -199,9 +166,7 @@ function ($action) { && $else_statement_actions && !array_filter( $else_statement_actions, - function ($action) { - return $action === self::ACTION_NONE; - } + fn($action) => $action === self::ACTION_NONE ); $all_elseif_actions = []; @@ -211,7 +176,6 @@ function ($action) { $elseif_control_actions = self::getControlActions( $elseif->stmts, $nodes, - $exit_functions, $break_types, $return_is_exit ); @@ -219,9 +183,7 @@ function ($action) { $all_leave = $all_leave && !array_filter( $elseif_control_actions, - function ($action) { - return $action === self::ACTION_NONE; - } + fn($action) => $action === self::ACTION_NONE ); $all_elseif_actions = array_merge($elseif_control_actions, $all_elseif_actions); @@ -248,9 +210,7 @@ function ($action) { $else_statement_actions, $all_elseif_actions ), - function ($action) { - return $action !== self::ACTION_NONE; - } + fn($action) => $action !== self::ACTION_NONE ); } @@ -268,7 +228,6 @@ function ($action) { $case_actions = self::getControlActions( $case->stmts, $nodes, - $exit_functions, array_merge($break_types, ['switch']), $return_is_exit ); @@ -311,9 +270,7 @@ function ($action) { $all_case_actions = array_filter( $all_case_actions, - function ($action) { - return $action !== self::ACTION_NONE; - } + fn($action) => $action !== self::ACTION_NONE ); if ($has_default_terminator || $stmt->getAttribute('allMatched', false)) { @@ -334,16 +291,13 @@ function ($action) { $loop_actions = self::getControlActions( $stmt->stmts, $nodes, - $exit_functions, array_merge($break_types, ['loop']), $return_is_exit ); $control_actions = array_filter( array_merge($control_actions, $loop_actions), - function ($action) { - return $action !== self::ACTION_NONE; - } + fn($action) => $action !== self::ACTION_NONE ); if ($stmt instanceof PhpParser\Node\Stmt\While_ @@ -393,16 +347,13 @@ function ($action) { $try_statement_actions = self::getControlActions( $stmt->stmts, $nodes, - $exit_functions, $break_types, $return_is_exit ); $try_leaves = !array_filter( $try_statement_actions, - function ($action) { - return $action === self::ACTION_NONE; - } + fn($action) => $action === self::ACTION_NONE ); $all_catch_actions = []; @@ -414,7 +365,6 @@ function ($action) { $catch_actions = self::getControlActions( $catch->stmts, $nodes, - $exit_functions, $break_types, $return_is_exit ); @@ -422,9 +372,7 @@ function ($action) { $all_leave = $all_leave && !array_filter( $catch_actions, - function ($action) { - return $action === self::ACTION_NONE; - } + fn($action) => $action === self::ACTION_NONE ); if (!$all_leave) { @@ -453,7 +401,6 @@ function ($action) { $finally_statement_actions = self::getControlActions( $stmt->finally->stmts, $nodes, - $exit_functions, $break_types, $return_is_exit ); @@ -462,9 +409,7 @@ function ($action) { return array_merge( array_filter( $control_actions, - function ($action) { - return $action !== self::ACTION_NONE; - } + fn($action) => $action !== self::ACTION_NONE ), $finally_statement_actions ); @@ -473,9 +418,7 @@ function ($action) { $control_actions = array_filter( array_merge($control_actions, $try_statement_actions), - function ($action) { - return $action !== self::ACTION_NONE; - } + fn($action) => $action !== self::ACTION_NONE ); } } diff --git a/src/Psalm/Internal/Analyzer/Statements/Block/ForeachAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Block/ForeachAnalyzer.php index bdcf834154e..f03f3d0a0b5 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Block/ForeachAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Block/ForeachAnalyzer.php @@ -49,6 +49,7 @@ use Psalm\Type\Atomic\TList; use Psalm\Type\Atomic\TMixed; use Psalm\Type\Atomic\TNamedObject; +use Psalm\Type\Atomic\TNever; use Psalm\Type\Atomic\TNonEmptyArray; use Psalm\Type\Atomic\TNonEmptyList; use Psalm\Type\Atomic\TNull; @@ -278,7 +279,7 @@ public static function analyze( } if ($stmt->keyVar instanceof PhpParser\Node\Expr\Variable && is_string($stmt->keyVar->name)) { - $key_type = $key_type ?? Type::getMixed(); + $key_type ??= Type::getMixed(); AssignmentAnalyzer::analyze( $statements_analyzer, @@ -291,7 +292,7 @@ public static function analyze( ); } - $value_type = $value_type ?? Type::getMixed(); + $value_type ??= Type::getMixed(); if ($stmt->byRef) { $value_type->by_ref = true; @@ -434,9 +435,7 @@ public static function checkIteratorType( } // if it's an empty array, we cannot iterate over it - if ($iterator_atomic_type instanceof TArray - && $iterator_atomic_type->type_params[1]->isEmpty() - ) { + if ($iterator_atomic_type instanceof TArray && $iterator_atomic_type->isEmptyArray()) { $always_non_empty_array = false; $has_valid_iterator = true; continue; @@ -505,7 +504,10 @@ public static function checkIteratorType( $invalid_iterator_types[] = $iterator_atomic_type->getKey(); $value_type = Type::getMixed(); - } elseif ($iterator_atomic_type instanceof TObject || $iterator_atomic_type instanceof TMixed) { + } elseif ($iterator_atomic_type instanceof TObject || + $iterator_atomic_type instanceof TMixed || + $iterator_atomic_type instanceof TNever + ) { $has_valid_iterator = true; $value_type = Type::getMixed(); $key_type = Type::getMixed(); @@ -998,9 +1000,7 @@ public static function getKeyValueParamsForTraversableObject( : array_values( array_map( /** @param array $arr */ - function (array $arr) use ($iterator_atomic_type): Union { - return $arr[$iterator_atomic_type->value] ?? Type::getMixed(); - }, + fn(array $arr): Union => $arr[$iterator_atomic_type->value] ?? Type::getMixed(), $generic_storage->template_types ) ); diff --git a/src/Psalm/Internal/Analyzer/Statements/Block/IfConditionalAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Block/IfConditionalAnalyzer.php index cd4002e511d..eb6ecfb1d0f 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Block/IfConditionalAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Block/IfConditionalAnalyzer.php @@ -28,6 +28,9 @@ use function array_values; use function count; +/** + * @internal + */ class IfConditionalAnalyzer { public static function analyze( @@ -73,11 +76,9 @@ public static function analyze( $entry_clauses = array_values( array_filter( $entry_clauses, - function (Clause $c) use ($changed_var_ids): bool { - return count($c->possibilities) > 1 - || $c->wedge - || !isset($changed_var_ids[array_keys($c->possibilities)[0]]); - } + fn(Clause $c): bool => count($c->possibilities) > 1 + || $c->wedge + || !isset($changed_var_ids[array_keys($c->possibilities)[0]]) ) ); } @@ -206,9 +207,7 @@ function (Clause $c) use ($changed_var_ids): bool { * * @return true */ - function (Union $_): bool { - return true; - }, + fn(Union $_): bool => true, array_diff_key( $if_conditional_context->vars_in_scope, $pre_condition_vars_in_scope, diff --git a/src/Psalm/Internal/Analyzer/Statements/Block/IfElse/ElseAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Block/IfElse/ElseAnalyzer.php index f2071b329ea..5944847fdff 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Block/IfElse/ElseAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Block/IfElse/ElseAnalyzer.php @@ -23,6 +23,9 @@ use function preg_match; use function preg_quote; +/** + * @internal + */ class ElseAnalyzer { /** @@ -154,7 +157,6 @@ public static function analyze( ? ScopeAnalyzer::getControlActions( $else->stmts, $statements_analyzer->node_data, - $codebase->config->exit_functions, [] ) : [ScopeAnalyzer::ACTION_NONE]; diff --git a/src/Psalm/Internal/Analyzer/Statements/Block/IfElse/ElseIfAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Block/IfElse/ElseIfAnalyzer.php index 105bf206255..bac5b65fd84 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Block/IfElse/ElseIfAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Block/IfElse/ElseIfAnalyzer.php @@ -38,6 +38,9 @@ use function preg_quote; use function spl_object_id; +/** + * @internal + */ class ElseIfAnalyzer { /** @@ -150,9 +153,7 @@ function (Clause $c) use ($assigned_in_conditional_var_ids, $elseif_cond_id): Cl $elseif_context_clauses = array_values( array_filter( $elseif_context_clauses, - function ($c) use ($reconciled_expression_clauses): bool { - return !in_array($c->hash, $reconciled_expression_clauses); - } + fn($c): bool => !in_array($c->hash, $reconciled_expression_clauses) ) ); } @@ -164,9 +165,7 @@ function ($c) use ($reconciled_expression_clauses): bool { try { if (array_filter( $entry_clauses, - function ($clause): bool { - return (bool)$clause->possibilities; - } + fn($clause): bool => (bool)$clause->possibilities )) { $omit_keys = array_reduce( $entry_clauses, @@ -174,9 +173,7 @@ function ($clause): bool { * @param array $carry * @return array */ - function (array $carry, Clause $clause): array { - return array_merge($carry, array_keys($clause->possibilities)); - }, + fn(array $carry, Clause $clause): array => array_merge($carry, array_keys($clause->possibilities)), [] ); @@ -312,7 +309,6 @@ function (array $carry, Clause $clause): array { $final_actions = ScopeAnalyzer::getControlActions( $elseif->stmts, $statements_analyzer->node_data, - $codebase->config->exit_functions, [] ); // has a return/throw at end @@ -339,7 +335,7 @@ function (array $carry, Clause $clause): array { $reasonable_clause_count = count($if_scope->reasonable_clauses); - if ($reasonable_clause_count && $reasonable_clause_count < 20000 && $elseif_clauses) { + if ($reasonable_clause_count && $reasonable_clause_count < 20_000 && $elseif_clauses) { $if_scope->reasonable_clauses = Algebra::combineOredClauses( $if_scope->reasonable_clauses, $elseif_clauses, diff --git a/src/Psalm/Internal/Analyzer/Statements/Block/IfElse/IfAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Block/IfElse/IfAnalyzer.php index 005a2722db2..f5e7e79f5fc 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Block/IfElse/IfAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Block/IfElse/IfAnalyzer.php @@ -38,6 +38,9 @@ use function strpos; use function substr; +/** + * @internal + */ class IfAnalyzer { /** @@ -75,7 +78,6 @@ public static function analyze( $final_actions = ScopeAnalyzer::getControlActions( $stmt->stmts, $statements_analyzer->node_data, - $codebase->config->exit_functions, [] ); diff --git a/src/Psalm/Internal/Analyzer/Statements/Block/IfElseAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Block/IfElseAnalyzer.php index 1f9dcc35d81..221ab6ca49d 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Block/IfElseAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Block/IfElseAnalyzer.php @@ -85,7 +85,6 @@ public static function analyze( $final_actions = ScopeAnalyzer::getControlActions( $stmt->stmts, null, - $codebase->config->exit_functions, [] ); @@ -187,9 +186,7 @@ function (Clause $c) use ($mixed_var_ids, $cond_object_id): Clause { $if_context->clauses = array_values( array_filter( $if_context->clauses, - function ($c) use ($reconciled_expression_clauses): bool { - return !in_array($c->hash, $reconciled_expression_clauses); - } + fn($c): bool => !in_array($c->hash, $reconciled_expression_clauses) ) ); @@ -240,9 +237,7 @@ function ($c) use ($reconciled_expression_clauses): bool { if (array_filter( $context->clauses, - function ($clause): bool { - return (bool)$clause->possibilities; - } + fn($clause): bool => (bool)$clause->possibilities )) { $omit_keys = array_reduce( $context->clauses, @@ -250,9 +245,7 @@ function ($clause): bool { * @param array $carry * @return array */ - function (array $carry, Clause $clause): array { - return array_merge($carry, array_keys($clause->possibilities)); - }, + fn(array $carry, Clause $clause): array => array_merge($carry, array_keys($clause->possibilities)), [] ); diff --git a/src/Psalm/Internal/Analyzer/Statements/Block/LoopAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Block/LoopAnalyzer.php index 1cfd4023eee..a5ed096112a 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Block/LoopAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Block/LoopAnalyzer.php @@ -4,7 +4,6 @@ use PhpParser; use Psalm\CodeLocation; -use Psalm\Config; use Psalm\Context; use Psalm\Exception\ComplicatedExpressionException; use Psalm\Internal\Algebra; @@ -96,7 +95,6 @@ public static function analyze( $final_actions = ScopeAnalyzer::getControlActions( $stmts, $statements_analyzer->node_data, - Config::getInstance()->exit_functions, [] ); diff --git a/src/Psalm/Internal/Analyzer/Statements/Block/SwitchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Block/SwitchAnalyzer.php index fd492eba78d..570881470bb 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Block/SwitchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Block/SwitchAnalyzer.php @@ -3,7 +3,6 @@ namespace Psalm\Internal\Analyzer\Statements\Block; use PhpParser; -use Psalm\Config; use Psalm\Context; use Psalm\Internal\Algebra; use Psalm\Internal\Analyzer\ScopeAnalyzer; @@ -73,8 +72,6 @@ public static function analyze( $case_action_map = []; - $config = Config::getInstance(); - // create a map of case statement -> ultimate exit type for ($i = count($stmt->cases) - 1; $i >= 0; --$i) { $case = $stmt->cases[$i]; @@ -82,7 +79,6 @@ public static function analyze( $case_actions = $case_action_map[$i] = ScopeAnalyzer::getControlActions( $case->stmts, $statements_analyzer->node_data, - $config->exit_functions, ['switch'] ); @@ -171,7 +167,7 @@ public static function analyze( ); if (isset($case_vars_in_scope_reconciled[$switch_var_id]) - && $case_vars_in_scope_reconciled[$switch_var_id]->isEmpty() + && $case_vars_in_scope_reconciled[$switch_var_id]->isNever() ) { $all_options_matched = true; } diff --git a/src/Psalm/Internal/Analyzer/Statements/Block/SwitchCaseAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Block/SwitchCaseAnalyzer.php index d8802a1cd47..3e97c915ba0 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Block/SwitchCaseAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Block/SwitchCaseAnalyzer.php @@ -591,7 +591,7 @@ private static function handleNonReturningCase( if (!$case->cond && $switch_var_id && isset($case_context->vars_in_scope[$switch_var_id]) - && $case_context->vars_in_scope[$switch_var_id]->isEmpty() + && $case_context->vars_in_scope[$switch_var_id]->isNever() ) { if (IssueBuffer::accepts( new ParadoxicalCondition( diff --git a/src/Psalm/Internal/Analyzer/Statements/Block/TryAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Block/TryAnalyzer.php index 8ecffb85d89..9f2f8428f5c 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Block/TryAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Block/TryAnalyzer.php @@ -52,7 +52,6 @@ public static function analyze( $catch_actions[$i] = ScopeAnalyzer::getControlActions( $catch->stmts, $statements_analyzer->node_data, - $codebase->config->exit_functions, [] ); $all_catches_leave = $all_catches_leave && !in_array(ScopeAnalyzer::ACTION_NONE, $catch_actions[$i], true); @@ -105,7 +104,6 @@ public static function analyze( $try_block_control_actions = ScopeAnalyzer::getControlActions( $stmt->stmts, $statements_analyzer->node_data, - $codebase->config->exit_functions, [] ); @@ -359,7 +357,6 @@ function (string $fq_catch_class) use ($codebase): TNamedObject { $catch_actions[$i] = ScopeAnalyzer::getControlActions( $catch->stmts, $statements_analyzer->node_data, - $codebase->config->exit_functions, [] ); diff --git a/src/Psalm/Internal/Analyzer/Statements/BreakAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/BreakAnalyzer.php index 2a988563cde..c2bc83491c6 100644 --- a/src/Psalm/Internal/Analyzer/Statements/BreakAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/BreakAnalyzer.php @@ -10,6 +10,9 @@ use function end; +/** + * @internal + */ class BreakAnalyzer { public static function analyze( diff --git a/src/Psalm/Internal/Analyzer/Statements/ContinueAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/ContinueAnalyzer.php index 17826f14e63..864c62ede87 100644 --- a/src/Psalm/Internal/Analyzer/Statements/ContinueAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/ContinueAnalyzer.php @@ -13,6 +13,9 @@ use function end; +/** + * @internal + */ class ContinueAnalyzer { public static function analyze( diff --git a/src/Psalm/Internal/Analyzer/Statements/EchoAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/EchoAnalyzer.php index 8b9be56b4fa..1cfea01d028 100644 --- a/src/Psalm/Internal/Analyzer/Statements/EchoAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/EchoAnalyzer.php @@ -19,6 +19,9 @@ use Psalm\Type; use Psalm\Type\TaintKind; +/** + * @internal + */ class EchoAnalyzer { public static function analyze( diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/ArrayAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/ArrayAnalyzer.php index bfee36f3ac7..a3f7b191afc 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/ArrayAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/ArrayAnalyzer.php @@ -510,9 +510,7 @@ private static function handleUnpackedArray( if ($unpacked_atomic_type instanceof TKeyedArray) { foreach ($unpacked_atomic_type->properties as $key => $property_value) { if (is_string($key)) { - if ($codebase->php_major_version < 8 || - ($codebase->php_major_version === 8 && $codebase->php_minor_version < 1) - ) { + if ($codebase->analysis_php_version_id <= 8_00_00) { IssueBuffer::maybeAdd( new DuplicateArrayKey( 'String keys are not supported in unpacked arrays', @@ -549,15 +547,13 @@ private static function handleUnpackedArray( && count($unpacked_atomic_type->type_params) === 2 )) { /** @psalm-suppress PossiblyUndefinedArrayOffset provably true, but Psalm can’t see it */ - if ($unpacked_atomic_type->type_params[1]->isEmpty()) { + if ($unpacked_atomic_type->type_params[1]->isNever()) { continue; } $array_creation_info->can_create_objectlike = false; if ($unpacked_atomic_type->type_params[0]->hasString()) { - if ($codebase->php_major_version < 8 || - ($codebase->php_major_version === 8 && $codebase->php_minor_version < 1) - ) { + if ($codebase->analysis_php_version_id <= 8_00_00) { IssueBuffer::maybeAdd( new DuplicateArrayKey( 'String keys are not supported in unpacked arrays', @@ -583,7 +579,7 @@ private static function handleUnpackedArray( ) ); } elseif ($unpacked_atomic_type instanceof TList) { - if ($unpacked_atomic_type->type_param->isEmpty()) { + if ($unpacked_atomic_type->type_param->isNever()) { continue; } $array_creation_info->can_create_objectlike = false; diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/ArrayCreationInfo.php b/src/Psalm/Internal/Analyzer/Statements/Expression/ArrayCreationInfo.php index ca0e761b283..98a0635cbb4 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/ArrayCreationInfo.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/ArrayCreationInfo.php @@ -6,6 +6,9 @@ use Psalm\Type\Atomic; use Psalm\Type\Union; +/** + * @internal + */ class ArrayCreationInfo { /** diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php b/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php index 035effe4fc2..77f96188410 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php @@ -71,6 +71,8 @@ use function strtolower; use function substr; +use const JSON_THROW_ON_ERROR; + /** * @internal * This class transform conditions in code into "assertions" that will be reconciled with the type already known of a @@ -149,7 +151,7 @@ public static function scrapeAssertions( if ($var_name) { if ($candidate_if_types) { - $if_types[$var_name] = [['@' . json_encode($candidate_if_types[0])]]; + $if_types[$var_name] = [['@' . json_encode($candidate_if_types[0], JSON_THROW_ON_ERROR)]]; } else { $if_types[$var_name] = [['!falsy']]; } diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php index a2eb2c60f0a..7652589699b 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php @@ -739,7 +739,7 @@ private static function analyzeNestedArrayAssignment( return; } - if ($child_stmt_var_type->isEmpty()) { + if ($child_stmt_var_type->isNever()) { $child_stmt_var_type = Type::getEmptyArray(); $statements_analyzer->node_data->setType($child_stmt->var, $child_stmt_var_type); } diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/AssignedProperty.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/AssignedProperty.php index 9756e4fc5f7..c1d06eb6fc8 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/AssignedProperty.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/AssignedProperty.php @@ -4,6 +4,9 @@ use Psalm\Type\Union; +/** + * @internal + */ class AssignedProperty { /** diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/AssignmentAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/AssignmentAnalyzer.php index 407c6d0441e..6cb610b2b1e 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/AssignmentAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/AssignmentAnalyzer.php @@ -569,7 +569,7 @@ public static function analyze( return false; } - $context->vars_in_scope[$var_id] = Type::getEmpty(); + $context->vars_in_scope[$var_id] = Type::getNever(); $context->inside_assignment = $was_in_assignment; @@ -739,16 +739,12 @@ private static function taintAssignment( $unspecialized_parent_nodes = array_filter( $parent_nodes, - function ($parent_node) { - return !$parent_node->specialization_key; - } + fn($parent_node) => !$parent_node->specialization_key ); $specialized_parent_nodes = array_filter( $parent_nodes, - function ($parent_node) { - return (bool) $parent_node->specialization_key; - } + fn($parent_node) => (bool) $parent_node->specialization_key ); $new_parent_nodes = []; @@ -1024,11 +1020,11 @@ public static function assignByRefParam( $by_ref_out_type->parent_nodes += $existing_type->parent_nodes; } - if ($existing_type->getId() !== 'array') { + if (!$existing_type->isEmptyArray()) { $context->vars_in_scope[$var_id] = $by_ref_out_type; if (!($stmt_type = $statements_analyzer->node_data->getType($stmt)) - || $stmt_type->isEmpty() + || $stmt_type->isNever() ) { $statements_analyzer->node_data->setType($stmt, clone $by_ref_type); } @@ -1043,7 +1039,7 @@ public static function assignByRefParam( $stmt_type = $statements_analyzer->node_data->getType($stmt); - if (!$stmt_type || $stmt_type->isEmpty()) { + if (!$stmt_type || $stmt_type->isNever()) { $statements_analyzer->node_data->setType($stmt, clone $by_ref_type); } diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/AndAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/AndAnalyzer.php index 1f4c28f1b42..df0c64194db 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/AndAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/AndAnalyzer.php @@ -104,9 +104,7 @@ public static function analyze( $context_clauses = array_values( array_filter( $context_clauses, - function ($c) use ($reconciled_expression_clauses): bool { - return !in_array($c->hash, $reconciled_expression_clauses); - } + fn($c): bool => !in_array($c->hash, $reconciled_expression_clauses) ) ); @@ -210,9 +208,7 @@ function ($c) use ($reconciled_expression_clauses): bool { $if_context->reconciled_expression_clauses = array_merge( $if_context->reconciled_expression_clauses, array_map( - function ($c) { - return $c->hash; - }, + fn($c) => $c->hash, $partitioned_clauses[1] ) ); diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ArithmeticOpAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ArithmeticOpAnalyzer.php index f7f7752c1ef..037f426ec20 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ArithmeticOpAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ArithmeticOpAnalyzer.php @@ -75,9 +75,9 @@ public static function analyze( $right_type = $nodes->getType($right); $config = Config::getInstance(); - if ($left_type && $left_type->isEmpty()) { + if ($left_type && $left_type->isNever()) { $left_type = $right_type; - } elseif ($right_type && $right_type->isEmpty()) { + } elseif ($right_type && $right_type->isNever()) { $right_type = $left_type; } @@ -896,7 +896,7 @@ public static function arithmeticOperation( $result = $operand1 - $operand2; } elseif ($operation instanceof PhpParser\Node\Expr\BinaryOp\Mod) { if ($operand2 === 0) { - return Type::getEmpty(); + return Type::getNever(); } $result = $operand1 % $operand2; @@ -916,7 +916,7 @@ public static function arithmeticOperation( $result = $operand1 >> $operand2; } elseif ($operation instanceof PhpParser\Node\Expr\BinaryOp\Div) { if ($operand2 === 0) { - return Type::getEmpty(); + return Type::getNever(); } $result = $operand1 / $operand2; @@ -1269,9 +1269,11 @@ private static function analyzePowBetweenIntRange( $new_result_type = Type::getInt(true, 0); } elseif ($right_type_part->min_bound === 0 && $right_type_part->max_bound === 0) { $new_result_type = Type::getInt(true, 1); + } elseif ($right_type_part->isNegative()) { + $new_result_type = Type::getFloat(); } else { - //technically could be a float(INF)... - $new_result_type = Type::getEmpty(); + $new_result_type = new Union([new TFloat(), new TLiteralInt(0), new TLiteralInt(1)]); + $new_result_type->from_calculation = true; } } else { //$left_type_part may be a mix of positive, negative and 0 @@ -1305,7 +1307,7 @@ private static function analyzeModBetweenIntRange( if ($right_type_part->min_bound !== null && $right_type_part->min_bound === $right_type_part->max_bound) { //if the second operand is a literal, we can be pretty detailed if ($right_type_part->max_bound === 0) { - $new_result_type = Type::getEmpty(); + $new_result_type = Type::getNever(); } else { if ($left_type_part->isPositiveOrZero()) { if ($right_type_part->isPositive()) { diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/OrAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/OrAnalyzer.php index d584d43f69d..d6d675b522c 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/OrAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/OrAnalyzer.php @@ -167,9 +167,7 @@ public static function analyze( $negated_left_clauses = array_values( array_filter( $negated_left_clauses, - function ($c) use ($reconciled_expression_clauses): bool { - return !in_array($c->hash, $reconciled_expression_clauses); - } + fn($c): bool => !in_array($c->hash, $reconciled_expression_clauses) ) ); @@ -240,9 +238,7 @@ function ($c) use ($reconciled_expression_clauses): bool { $right_context->reconciled_expression_clauses = array_merge( $context->reconciled_expression_clauses, array_map( - function ($c) { - return $c->hash; - }, + fn($c) => $c->hash, $partitioned_clauses[1] ) ); @@ -252,9 +248,7 @@ function ($c) { $context->reconciled_expression_clauses = array_merge( $context->reconciled_expression_clauses, array_map( - function ($c) { - return $c->hash; - }, + fn($c) => $c->hash, $partitioned_clauses[1] ) ); diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/BitwiseNotAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/BitwiseNotAnalyzer.php index fa8c19d6ce6..878dc11eca8 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/BitwiseNotAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/BitwiseNotAnalyzer.php @@ -21,6 +21,9 @@ use Psalm\Type\Atomic\TString; use Psalm\Type\Union; +/** + * @internal + */ class BitwiseNotAnalyzer { public static function analyze( diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/BooleanNotAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/BooleanNotAnalyzer.php index cf135ee9d91..fc3d6eb73fd 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/BooleanNotAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/BooleanNotAnalyzer.php @@ -8,6 +8,9 @@ use Psalm\Internal\Analyzer\StatementsAnalyzer; use Psalm\Type; +/** + * @internal + */ class BooleanNotAnalyzer { public static function analyze( diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php index 84b777c7e20..bc82d57d329 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php @@ -486,7 +486,7 @@ private static function checkFunctionLikeTypeMatches( if ($function_param->is_variadic) { $arg_type = $unpacked_atomic_array->getGenericValueType(); - } elseif ($codebase->php_major_version >= 8 + } elseif ($codebase->analysis_php_version_id >= 8_00_00 && $allow_named_args && isset($unpacked_atomic_array->properties[$function_param->name]) ) { @@ -552,7 +552,9 @@ private static function checkFunctionLikeTypeMatches( continue; } - if (($codebase->php_major_version < 8 || !$allow_named_args) && !$key_type->isInt()) { + if (($codebase->analysis_php_version_id < 8_00_00 || !$allow_named_args) + && !$key_type->isInt() + ) { $invalid_string_key = true; continue; @@ -578,7 +580,7 @@ private static function checkFunctionLikeTypeMatches( 'Method ' . $cased_method_id . ' called with unpacked iterable ' . $arg_type->getId() . ' with invalid key (must be ' - . ($codebase->php_major_version < 8 ? 'int' : 'int|string') . ')', + . ($codebase->analysis_php_version_id < 8_00_00 ? 'int' : 'int|string') . ')', new CodeLocation($statements_analyzer->getSource(), $arg->value), $cased_method_id ), @@ -586,7 +588,7 @@ private static function checkFunctionLikeTypeMatches( ); } if ($invalid_string_key) { - if ($codebase->php_major_version < 8) { + if ($codebase->analysis_php_version_id < 8_00_00) { IssueBuffer::maybeAdd( new $issue_type( 'String keys not supported in unpacked arguments', @@ -983,7 +985,7 @@ public static function verifyType( ), $statements_analyzer->getSuppressedIssues() ); - } else { + } elseif ($cased_method_id !== 'echo' && $cased_method_id !== 'print') { IssueBuffer::maybeAdd( new ArgumentTypeCoercion( 'Argument ' . ($argument_offset + 1) . $method_identifier . ' expects ' . $param_type->getId() . @@ -1360,7 +1362,7 @@ private static function coerceValueAfterGatekeeperArgument( && $input_atomic_type->value === $param_atomic_type->value ) { foreach ($input_atomic_type->type_params as $i => $type_param) { - if ($type_param->isEmpty() && isset($param_atomic_type->type_params[$i])) { + if ($type_param->isNever() && isset($param_atomic_type->type_params[$i])) { $input_type_changed = true; $input_atomic_type->type_params[$i] = clone $param_atomic_type->type_params[$i]; diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentMapPopulator.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentMapPopulator.php index cf5fe6f2d80..ff8c2058ff5 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentMapPopulator.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentMapPopulator.php @@ -18,6 +18,9 @@ use function substr; use function token_get_all; +/** + * @internal + */ class ArgumentMapPopulator { /** diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php index 1404cb97a45..f068d0dc4c6 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php @@ -358,17 +358,13 @@ private static function handleClosureArg( $replace_template_result = new TemplateResult( array_map( - function ($template_map) use ($codebase) { - return array_map( - function ($lower_bounds) use ($codebase) { - return TemplateStandinTypeReplacer::getMostSpecificTypeFromBounds( - $lower_bounds, - $codebase - ); - }, - $template_map - ); - }, + fn($template_map) => array_map( + fn($lower_bounds) => TemplateStandinTypeReplacer::getMostSpecificTypeFromBounds( + $lower_bounds, + $codebase + ), + $template_map + ), $template_result->lower_bounds ), [] @@ -1207,7 +1203,7 @@ private static function evaluateArbitraryParam( ); foreach ($context->vars_in_scope[$var_id]->getAtomicTypes() as $type) { - if ($type instanceof TArray && $type->type_params[1]->isEmpty()) { + if ($type instanceof TArray && $type->isEmptyArray()) { $context->vars_in_scope[$var_id]->removeType('array'); $context->vars_in_scope[$var_id]->addType( new TArray( diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArrayFunctionArgumentsAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArrayFunctionArgumentsAnalyzer.php index 654e9bd829b..81517036547 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArrayFunctionArgumentsAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArrayFunctionArgumentsAnalyzer.php @@ -33,9 +33,9 @@ use Psalm\Type\Atomic\TArray; use Psalm\Type\Atomic\TCallable; use Psalm\Type\Atomic\TClosure; -use Psalm\Type\Atomic\TEmpty; use Psalm\Type\Atomic\TKeyedArray; use Psalm\Type\Atomic\TList; +use Psalm\Type\Atomic\TNever; use Psalm\Type\Atomic\TNonEmptyArray; use Psalm\Type\Atomic\TNonEmptyList; use Psalm\Type\Union; @@ -147,9 +147,7 @@ public static function handleAddition( $unpacked_args = array_filter( $args, - function ($arg) { - return $arg->unpack; - } + fn($arg) => $arg->unpack ); if ($method_id === 'array_push' && !$unpacked_args) { @@ -544,8 +542,8 @@ public static function handleByRefArrayAdjustment( if ($array_atomic_type->count === 0) { $array_atomic_type = new TArray( [ - new Union([new TEmpty]), - new Union([new TEmpty]), + new Union([new TNever]), + new Union([new TNever]), ] ); } else { @@ -561,8 +559,8 @@ public static function handleByRefArrayAdjustment( if ($array_atomic_type->count === 0) { $array_atomic_type = new TArray( [ - new Union([new TEmpty]), - new Union([new TEmpty]), + new Union([new TNever]), + new Union([new TNever]), ] ); } else { diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ClassTemplateParamCollector.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ClassTemplateParamCollector.php index 1450e9e30af..094286c8d5c 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ClassTemplateParamCollector.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ClassTemplateParamCollector.php @@ -16,6 +16,9 @@ use function array_merge; use function array_search; +/** + * @internal + */ class ClassTemplateParamCollector { /** @@ -265,12 +268,8 @@ private static function expandType( true ); - if ($expanded instanceof Atomic) { - $output_type_extends[] = $expanded; - } else { - foreach ($expanded as $type) { - $output_type_extends[] = $type; - } + foreach ($expanded as $type) { + $output_type_extends[] = $type; } } else { $output_type_extends[] = $type_extends_atomic; diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php index 9b6bf31b1ea..cc4483cd1f5 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php @@ -363,9 +363,8 @@ public static function analyze( $statements_analyzer->node_data->setIfTrueAssertions( $stmt, array_map( - function (Assertion $assertion) use ($inferred_lower_bounds, $codebase): Assertion { - return $assertion->getUntemplatedCopy($inferred_lower_bounds ?: [], null, $codebase); - }, + fn(Assertion $assertion): Assertion => + $assertion->getUntemplatedCopy($inferred_lower_bounds ?: [], null, $codebase), $function_call_info->function_storage->if_true_assertions ) ); @@ -375,9 +374,8 @@ function (Assertion $assertion) use ($inferred_lower_bounds, $codebase): Asserti $statements_analyzer->node_data->setIfFalseAssertions( $stmt, array_map( - function (Assertion $assertion) use ($inferred_lower_bounds, $codebase): Assertion { - return $assertion->getUntemplatedCopy($inferred_lower_bounds ?: [], null, $codebase); - }, + fn(Assertion $assertion): Assertion => + $assertion->getUntemplatedCopy($inferred_lower_bounds ?: [], null, $codebase), $function_call_info->function_storage->if_false_assertions ) ); @@ -935,9 +933,7 @@ private static function processAssertFunctionEffects( $context->vars_in_scope, $changed_var_ids, array_map( - function ($_): bool { - return true; - }, + fn($_): bool => true, $assert_type_assertions ), $statements_analyzer, diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallReturnTypeFetcher.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallReturnTypeFetcher.php index 7a61039cdc1..94005bf8f8f 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallReturnTypeFetcher.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallReturnTypeFetcher.php @@ -125,7 +125,7 @@ public static function fetch( $template_result->lower_bounds[$template_name] = [ 'fn-' . $function_id => [ new TemplateBound( - Type::getInt(false, $codebase->php_major_version) + Type::getInt(false, $codebase->getMajorAnalysisPhpVersion()) ) ] ]; @@ -135,8 +135,7 @@ public static function fetch( new TemplateBound( Type::getInt( false, - 10000 * $codebase->php_major_version - + 100 * $codebase->php_minor_version + $codebase->analysis_php_version_id ) ) ] @@ -145,7 +144,7 @@ public static function fetch( $template_result->lower_bounds[$template_name] = [ 'fn-' . $function_id => [ new TemplateBound( - Type::getEmpty() + Type::getNever() ) ] ]; @@ -391,14 +390,13 @@ private static function getReturnTypeFromCallMapWithArgs( foreach ($atomic_types['array']->properties as $property) { // empty, never and possibly undefined can't count for min value if (!$property->possibly_undefined - && !$property->isEmpty() && !$property->isNever() ) { $min++; } - //empty and never can't count for max value because we know keys are undefined - if (!$property->isEmpty() && !$property->isNever()) { + //never can't count for max value because we know keys are undefined + if (!$property->isNever()) { $max++; } } @@ -417,8 +415,7 @@ private static function getReturnTypeFromCallMapWithArgs( } if ($atomic_types['array'] instanceof TArray - && $atomic_types['array']->type_params[0]->isEmpty() - && $atomic_types['array']->type_params[1]->isEmpty() + && $atomic_types['array']->isEmptyArray() ) { return Type::getInt(false, 0); } diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicCallContext.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicCallContext.php index fe0c730e402..8477dd558d3 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicCallContext.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicCallContext.php @@ -5,6 +5,9 @@ use PhpParser; use Psalm\Internal\MethodIdentifier; +/** + * @internal + */ class AtomicCallContext { /** @var MethodIdentifier */ diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicMethodCallAnalysisResult.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicMethodCallAnalysisResult.php index e4d5d5e0eb2..ba2e13db734 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicMethodCallAnalysisResult.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicMethodCallAnalysisResult.php @@ -5,6 +5,9 @@ use Psalm\Internal\MethodIdentifier; use Psalm\Type\Union; +/** + * @internal + */ class AtomicMethodCallAnalysisResult { /** diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicMethodCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicMethodCallAnalyzer.php index 33d6d173ac6..db99b12ecb8 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicMethodCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicMethodCallAnalyzer.php @@ -27,12 +27,12 @@ use Psalm\Storage\ClassLikeStorage; use Psalm\Type; use Psalm\Type\Atomic; -use Psalm\Type\Atomic\TEmpty; use Psalm\Type\Atomic\TEmptyMixed; use Psalm\Type\Atomic\TFalse; use Psalm\Type\Atomic\TGenericObject; use Psalm\Type\Atomic\TMixed; use Psalm\Type\Atomic\TNamedObject; +use Psalm\Type\Atomic\TNever; use Psalm\Type\Atomic\TNonEmptyMixed; use Psalm\Type\Atomic\TNull; use Psalm\Type\Atomic\TObject; @@ -56,6 +56,8 @@ * methods. * * The happy path (i.e 99% of method calls) is handled in ExistingAtomicMethodCallAnalyzer + * + * @internal */ class AtomicMethodCallAnalyzer extends CallAnalyzer { @@ -571,7 +573,7 @@ private static function handleInvalidClass( case TTemplateParam::class: case TEmptyMixed::class: - case TEmpty::class: + case TNever::class: case TMixed::class: case TNonEmptyMixed::class: case TObject::class: diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/ExistingAtomicMethodCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/ExistingAtomicMethodCallAnalyzer.php index 1fda720d0c2..3f559414e1e 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/ExistingAtomicMethodCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/ExistingAtomicMethodCallAnalyzer.php @@ -48,6 +48,9 @@ use function in_array; use function strtolower; +/** + * @internal + */ class ExistingAtomicMethodCallAnalyzer extends CallAnalyzer { /** @@ -415,17 +418,11 @@ public static function analyze( $statements_analyzer->node_data->setIfTrueAssertions( $stmt, array_map( - function (Assertion $assertion) use ( - $class_template_params, + fn(Assertion $assertion): Assertion => $assertion->getUntemplatedCopy( + $class_template_params ?: [], $lhs_var_id, $codebase - ): Assertion { - return $assertion->getUntemplatedCopy( - $class_template_params ?: [], - $lhs_var_id, - $codebase - ); - }, + ), $method_storage->if_true_assertions ) ); @@ -435,17 +432,11 @@ function (Assertion $assertion) use ( $statements_analyzer->node_data->setIfFalseAssertions( $stmt, array_map( - function (Assertion $assertion) use ( - $class_template_params, + fn(Assertion $assertion): Assertion => $assertion->getUntemplatedCopy( + $class_template_params ?: [], $lhs_var_id, $codebase - ): Assertion { - return $assertion->getUntemplatedCopy( - $class_template_params ?: [], - $lhs_var_id, - $codebase - ); - }, + ), $method_storage->if_false_assertions ) ); diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallProhibitionAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallProhibitionAnalyzer.php index a3f4660a6ae..255bc8af2e4 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallProhibitionAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallProhibitionAnalyzer.php @@ -11,6 +11,9 @@ use Psalm\Issue\InternalMethod; use Psalm\IssueBuffer; +/** + * @internal + */ class MethodCallProhibitionAnalyzer { /** diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallPurityAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallPurityAnalyzer.php index d0d49fdd706..1b5859bd660 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallPurityAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallPurityAnalyzer.php @@ -19,6 +19,9 @@ use Psalm\Storage\MethodStorage; use Psalm\Type; +/** + * @internal + */ class MethodCallPurityAnalyzer { public static function analyze( diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallReturnTypeFetcher.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallReturnTypeFetcher.php index cc99d748ec5..2ef9cd6fbba 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallReturnTypeFetcher.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallReturnTypeFetcher.php @@ -34,6 +34,9 @@ use function in_array; use function strtolower; +/** + * @internal + */ class MethodCallReturnTypeFetcher { /** @@ -311,16 +314,12 @@ public static function taintMethodCallResult( $unspecialized_parent_nodes = array_filter( $parent_nodes, - function ($parent_node) { - return !$parent_node->specialization_key; - } + fn($parent_node) => !$parent_node->specialization_key ); $specialized_parent_nodes = array_filter( $parent_nodes, - function ($parent_node) { - return (bool) $parent_node->specialization_key; - } + fn($parent_node) => (bool) $parent_node->specialization_key ); $var_node = DataFlowNode::getForAssignment( @@ -570,7 +569,7 @@ public static function replaceTemplateTypes( $template_result->lower_bounds[$template_type->param_name] = [ 'fn-' . strtolower((string) $method_id) => [ new TemplateBound( - Type::getInt(false, $codebase->php_major_version) + Type::getInt(false, $codebase->getMajorAnalysisPhpVersion()) ) ] ]; @@ -580,8 +579,7 @@ public static function replaceTemplateTypes( new TemplateBound( Type::getInt( false, - 10000 * $codebase->php_major_version - + 100 * $codebase->php_minor_version + $codebase->analysis_php_version_id ) ) ] @@ -589,7 +587,7 @@ public static function replaceTemplateTypes( } else { $template_result->lower_bounds[$template_type->param_name] = [ ($template_type->defining_class) => [ - new TemplateBound(Type::getEmpty()) + new TemplateBound(Type::getNever()) ] ]; } diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodVisibilityAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodVisibilityAnalyzer.php index 48ff4dac803..ad6c4fcc41a 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodVisibilityAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodVisibilityAnalyzer.php @@ -17,6 +17,9 @@ use function end; use function strtolower; +/** + * @internal + */ class MethodVisibilityAnalyzer { /** diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MissingMethodCallHandler.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MissingMethodCallHandler.php index 3b050ce763f..dfb3d26ed14 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MissingMethodCallHandler.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MissingMethodCallHandler.php @@ -25,6 +25,9 @@ use function array_map; use function array_merge; +/** + * @internal + */ class MissingMethodCallHandler { public static function handleMagicMethod( @@ -172,14 +175,12 @@ public static function handleMagicMethod( /** * @return PhpParser\Node\Expr\ArrayItem */ - function (PhpParser\Node\Arg $arg): PhpParser\Node\Expr\ArrayItem { - return new VirtualArrayItem( - $arg->value, - null, - false, - $arg->getAttributes() - ); - }, + fn(PhpParser\Node\Arg $arg): PhpParser\Node\Expr\ArrayItem => new VirtualArrayItem( + $arg->value, + null, + false, + $arg->getAttributes() + ), $stmt->getArgs() ); diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/MethodCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/MethodCallAnalyzer.php index 04f0b96cd6a..d37c9a2d4ff 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/MethodCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/MethodCallAnalyzer.php @@ -225,9 +225,7 @@ public static function analyze( if (count($possible_new_class_types) > 0) { $class_type = array_reduce( $possible_new_class_types, - function (?Union $type_1, Union $type_2) use ($codebase): Union { - return Type::combineUnionTypes($type_1, $type_2, $codebase); - } + fn(?Union $type_1, Union $type_2): Union => Type::combineUnionTypes($type_1, $type_2, $codebase) ); } diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NamedFunctionCallHandler.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NamedFunctionCallHandler.php index cb9143412dc..6882a4d9067 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NamedFunctionCallHandler.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NamedFunctionCallHandler.php @@ -367,9 +367,7 @@ public static function handle( foreach ($anded_assertions as $assertions) { $referenced_var_ids = array_map( - function (array $_): bool { - return true; - }, + fn(array $_): bool => true, $assertions ); diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NewAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NewAnalyzer.php index 2f9548c4195..e01f3be786e 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NewAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NewAnalyzer.php @@ -464,9 +464,7 @@ private static function analyzeNamedConstructor( $statements_analyzer->node_data->setIfTrueAssertions( $stmt, array_map( - function ($assertion) use ($generic_params, $codebase) { - return $assertion->getUntemplatedCopy($generic_params, null, $codebase); - }, + fn($assertion) => $assertion->getUntemplatedCopy($generic_params, null, $codebase), $method_storage->if_true_assertions ) ); @@ -476,9 +474,7 @@ function ($assertion) use ($generic_params, $codebase) { $statements_analyzer->node_data->setIfFalseAssertions( $stmt, array_map( - function ($assertion) use ($generic_params, $codebase) { - return $assertion->getUntemplatedCopy($generic_params, null, $codebase); - }, + fn($assertion) => $assertion->getUntemplatedCopy($generic_params, null, $codebase), $method_storage->if_false_assertions ) ); @@ -500,23 +496,19 @@ function ($assertion) use ($generic_params, $codebase) { $template_name, $storage->template_extended_params, array_map( - function ($type_map) use ($codebase) { - return array_map( - function ($bounds) use ($codebase) { - return TemplateStandinTypeReplacer::getMostSpecificTypeFromBounds( - $bounds, - $codebase - ); - }, - $type_map - ); - }, + fn($type_map) => array_map( + fn($bounds) => TemplateStandinTypeReplacer::getMostSpecificTypeFromBounds( + $bounds, + $codebase + ), + $type_map + ), $template_result->lower_bounds ) ); } else { if ($fq_class_name === 'SplObjectStorage') { - $generic_param_type = Type::getEmpty(); + $generic_param_type = Type::getNever(); } else { $generic_param_type = clone array_values($base_type)[0]; } @@ -534,7 +526,7 @@ function ($bounds) use ($codebase) { $generic_param_types ); - $result_atomic_type->was_static = $from_static; + $result_atomic_type->is_static = $from_static; $statements_analyzer->node_data->setType( $stmt, @@ -555,15 +547,13 @@ function ($bounds) use ($codebase) { $fq_class_name, array_values( array_map( - function ($map) { - return clone reset($map); - }, + fn($map) => clone reset($map), $storage->template_types ) ) ); - $result_atomic_type->was_static = $from_static; + $result_atomic_type->is_static = $from_static; $statements_analyzer->node_data->setType( $stmt, diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php index 04b86338104..fba791e191a 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php @@ -60,6 +60,9 @@ use function in_array; use function strtolower; +/** + * @internal + */ class AtomicStaticCallAnalyzer { public static function analyze( @@ -399,9 +402,10 @@ private static function handleNamedCall( $mixin_candidates[] = clone $mixin_candidate; } - $mixin_candidates_no_generic = array_filter($mixin_candidates, function ($check): bool { - return !($check instanceof TGenericObject); - }); + $mixin_candidates_no_generic = array_filter( + $mixin_candidates, + fn($check): bool => !($check instanceof TGenericObject) + ); // $mixin_candidates_no_generic will only be empty when there are TGenericObject entries. // In that case, Union will be initialized with an empty array but @@ -612,14 +616,12 @@ private static function handleNamedCall( } $array_values = array_map( - function (PhpParser\Node\Arg $arg): PhpParser\Node\Expr\ArrayItem { - return new VirtualArrayItem( - $arg->value, - null, - false, - $arg->getAttributes() - ); - }, + fn(PhpParser\Node\Arg $arg): PhpParser\Node\Expr\ArrayItem => new VirtualArrayItem( + $arg->value, + null, + false, + $arg->getAttributes() + ), $args ); diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php index 6951396f72a..8f1a6773eb6 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php @@ -44,6 +44,9 @@ use function strtolower; use function substr; +/** + * @internal + */ class ExistingAtomicStaticCallAnalyzer { /** @@ -327,9 +330,8 @@ public static function analyze( $statements_analyzer->node_data->setIfTrueAssertions( $stmt, array_map( - function (Assertion $assertion) use ($generic_params, $codebase): Assertion { - return $assertion->getUntemplatedCopy($generic_params, null, $codebase); - }, + fn(Assertion $assertion): Assertion => + $assertion->getUntemplatedCopy($generic_params, null, $codebase), $method_storage->if_true_assertions ) ); @@ -339,9 +341,8 @@ function (Assertion $assertion) use ($generic_params, $codebase): Assertion { $statements_analyzer->node_data->setIfFalseAssertions( $stmt, array_map( - function (Assertion $assertion) use ($generic_params, $codebase): Assertion { - return $assertion->getUntemplatedCopy($generic_params, null, $codebase); - }, + fn(Assertion $assertion): Assertion => + $assertion->getUntemplatedCopy($generic_params, null, $codebase), $method_storage->if_false_assertions ) ); @@ -416,7 +417,7 @@ function (Assertion $assertion) use ($generic_params, $codebase): Assertion { } } - $return_type_candidate = $return_type_candidate ?? Type::getMixed(); + $return_type_candidate ??= Type::getMixed(); StaticCallAnalyzer::taintReturnType( $statements_analyzer, @@ -504,7 +505,7 @@ private static function getMethodReturnType( $template_result->lower_bounds[$template_type->param_name] = [ 'fn-' . strtolower((string)$method_id) => [ new TemplateBound( - Type::getInt(false, $codebase->php_major_version) + Type::getInt(false, $codebase->getMajorAnalysisPhpVersion()) ) ] ]; @@ -514,8 +515,7 @@ private static function getMethodReturnType( new TemplateBound( Type::getInt( false, - 10000 * $codebase->php_major_version - + 100 * $codebase->php_minor_version + $codebase->analysis_php_version_id ) ) ] @@ -523,7 +523,7 @@ private static function getMethodReturnType( } else { $template_result->lower_bounds[$template_type->param_name] = [ ($template_type->defining_class) => [ - new TemplateBound(Type::getEmpty()) + new TemplateBound(Type::getNever()) ] ]; } diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/CallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/CallAnalyzer.php index b40ec32f97d..941d88f7088 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/CallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/CallAnalyzer.php @@ -831,8 +831,8 @@ public static function applyAssertionsToContext( ); $assert_clauses = FormulaGenerator::getFormula( - mt_rand(0, 1000000), - mt_rand(0, 1000000), + mt_rand(0, 1_000_000), + mt_rand(0, 1_000_000), $conditional, $context->self, $statements_analyzer, @@ -892,17 +892,13 @@ public static function applyAssertionsToContext( if ($type_assertions) { $template_type_map = array_map( - function ($type_map) use ($codebase) { - return array_map( - function ($bounds) use ($codebase) { - return TemplateStandinTypeReplacer::getMostSpecificTypeFromBounds( - $bounds, - $codebase - ); - }, - $type_map - ); - }, + fn($type_map) => array_map( + fn($bounds) => TemplateStandinTypeReplacer::getMostSpecificTypeFromBounds( + $bounds, + $codebase + ), + $type_map + ), $inferred_lower_bounds ); @@ -1106,9 +1102,7 @@ public static function checkTemplateResult( if (count($lower_bounds) > 1) { $bounds_with_equality = array_filter( $lower_bounds, - function ($lower_bound) { - return (bool)$lower_bound->equality_bound_classlike; - } + fn($lower_bound) => (bool)$lower_bound->equality_bound_classlike ); if (!$bounds_with_equality) { @@ -1117,9 +1111,7 @@ function ($lower_bound) { $equality_types = array_unique( array_map( - function ($bound_with_equality) { - return $bound_with_equality->type->getId(); - }, + fn($bound_with_equality) => $bound_with_equality->type->getId(), $bounds_with_equality ) ); diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php index 4f80b23e0a7..3d34b3f44f2 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php @@ -49,6 +49,9 @@ use function count; use function get_class; +/** + * @internal + */ class CastAnalyzer { public static function analyze( @@ -232,7 +235,7 @@ public static function analyze( $keyed_array->is_list = true; $permissible_atomic_types[] = $keyed_array; } elseif ($type instanceof TNull) { - $permissible_atomic_types[] = new TArray([Type::getEmpty(), Type::getEmpty()]); + $permissible_atomic_types[] = new TArray([Type::getNever(), Type::getNever()]); } elseif ($type instanceof TArray || $type instanceof TList || $type instanceof TKeyedArray @@ -261,7 +264,7 @@ public static function analyze( } if ($stmt instanceof PhpParser\Node\Expr\Cast\Unset_ - && $statements_analyzer->getCodebase()->php_major_version < 8 + && $statements_analyzer->getCodebase()->analysis_php_version_id <= 7_04_00 ) { if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->expr, $context) === false) { return false; diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/CloneAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/CloneAnalyzer.php index 45ed9e9a7f5..efd9fc3dbd1 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/CloneAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/CloneAnalyzer.php @@ -23,6 +23,9 @@ use function array_merge; use function array_pop; +/** + * @internal + */ class CloneAnalyzer { public static function analyze( diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/EmptyAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/EmptyAnalyzer.php index e874371ff57..429ad9bd1e3 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/EmptyAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/EmptyAnalyzer.php @@ -11,6 +11,9 @@ use Psalm\IssueBuffer; use Psalm\Type; +/** + * @internal + */ class EmptyAnalyzer { public static function analyze( diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/EncapsulatedStringAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/EncapsulatedStringAnalyzer.php index a329ba448a2..d39a94178a0 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/EncapsulatedStringAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/EncapsulatedStringAnalyzer.php @@ -16,6 +16,9 @@ use function in_array; +/** + * @internal + */ class EncapsulatedStringAnalyzer { public static function analyze( diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/ExitAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/ExitAnalyzer.php index 308a5e77aff..6892e2b669b 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/ExitAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/ExitAnalyzer.php @@ -21,6 +21,9 @@ use Psalm\Type\TaintKind; use Psalm\Type\Union; +/** + * @internal + */ class ExitAnalyzer { public static function analyze( @@ -136,7 +139,7 @@ public static function analyze( } } - $statements_analyzer->node_data->setType($stmt, Type::getEmpty()); + $statements_analyzer->node_data->setType($stmt, Type::getNever()); $context->has_returned = true; diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/ExpressionIdentifier.php b/src/Psalm/Internal/Analyzer/Statements/Expression/ExpressionIdentifier.php index ed779f5a539..6383746d300 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/ExpressionIdentifier.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/ExpressionIdentifier.php @@ -14,6 +14,9 @@ use function is_string; use function strtolower; +/** + * @internal + */ class ExpressionIdentifier { public static function getVarId( diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php index e2feccaa649..90675e5d728 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php @@ -56,7 +56,6 @@ use Psalm\Type\Atomic\TClassConstant; use Psalm\Type\Atomic\TClassString; use Psalm\Type\Atomic\TClassStringMap; -use Psalm\Type\Atomic\TEmpty; use Psalm\Type\Atomic\TFalse; use Psalm\Type\Atomic\TFloat; use Psalm\Type\Atomic\TInt; @@ -67,6 +66,7 @@ use Psalm\Type\Atomic\TLiteralString; use Psalm\Type\Atomic\TMixed; use Psalm\Type\Atomic\TNamedObject; +use Psalm\Type\Atomic\TNever; use Psalm\Type\Atomic\TNonEmptyArray; use Psalm\Type\Atomic\TNonEmptyList; use Psalm\Type\Atomic\TNull; @@ -552,7 +552,10 @@ public static function getArrayAccessTypeGivenOffset( foreach ($array_type->getAtomicTypes() as $type_string => $type) { $original_type = $type; - if ($type instanceof TMixed || $type instanceof TTemplateParam || $type instanceof TEmpty) { + if ($type instanceof TMixed + || $type instanceof TTemplateParam + || $type instanceof TNever + ) { if (!$type instanceof TTemplateParam || $type->as->isMixed() || !$type->as->isSingle()) { $array_access_type = self::handleMixedArrayAccess( $context, @@ -591,7 +594,7 @@ public static function getArrayAccessTypeGivenOffset( $statements_analyzer->getSuppressedIssues() ); - $array_access_type = new Union([new TEmpty]); + $array_access_type = new Union([new TNever]); } } else { if (!$context->inside_isset && !MethodCallAnalyzer::hasNullsafe($stmt->var)) { @@ -1001,7 +1004,7 @@ public static function replaceOffsetTypeWithInts(Union $offset_type): Union } /** - * @param TMixed|TTemplateParam|TEmpty $type + * @param TMixed|TTemplateParam|TNever $type */ public static function handleMixedArrayAccess( Context $context, @@ -1072,7 +1075,7 @@ public static function handleMixedArrayAccess( return Type::combineUnionTypes( $array_access_type, - Type::getMixed($type instanceof TEmpty) + Type::getMixed($type instanceof TNever) ); } @@ -1104,10 +1107,9 @@ private static function handleArrayAccessOnArray( if ($in_assignment && $type instanceof TArray - && $type->type_params[0]->isEmpty() - && $type->type_params[1]->isEmpty() + && $type->isEmptyArray() ) { - $from_empty_array = $type->type_params[0]->isEmpty() && $type->type_params[1]->isEmpty(); + $from_empty_array = $type->isEmptyArray(); if (count($key_values) === 1) { $from_mixed_array = $type->type_params[1]->isMixed(); @@ -1117,7 +1119,7 @@ private static function handleArrayAccessOnArray( // ok, type becomes an TKeyedArray $array_type->removeType($type_string); $type = new TKeyedArray([ - $key_values[0] => $from_mixed_array ? Type::getMixed() : Type::getEmpty() + $key_values[0] => $from_mixed_array ? Type::getMixed() : Type::getNever() ]); $type->sealed = $from_empty_array; @@ -1239,12 +1241,12 @@ private static function handleArrayAccessOnTArray( ): void { // if we're assigning to an empty array with a key offset, refashion that array if ($in_assignment) { - if ($type->type_params[0]->isEmpty()) { + if ($type->isEmptyArray()) { $type->type_params[0] = $offset_type->isMixed() ? Type::getArrayKey() : $offset_type; } - } elseif (!$type->type_params[0]->isEmpty()) { + } elseif (!$type->isEmptyArray()) { $expected_offset_type = $type->type_params[0]->hasMixed() ? new Union([new TArrayKey]) : $type->type_params[0]; @@ -1365,7 +1367,7 @@ private static function handleArrayAccessOnTArray( $type->type_params[1] ); - if ($array_access_type->isEmpty() + if ($array_access_type->isNever() && !$array_type->hasMixed() && !$in_assignment && !$context->inside_isset @@ -1524,7 +1526,7 @@ private static function handleArrayAccessOnKeyedArray( clone $type->properties[$key_value] ); } elseif ($in_assignment) { - $type->properties[$key_value] = new Union([new TEmpty]); + $type->properties[$key_value] = new Union([new TNever]); $array_access_type = Type::combineUnionTypes( $array_access_type, @@ -1572,9 +1574,7 @@ private static function handleArrayAccessOnKeyedArray( $formatted_keys = implode( ', ', array_map( - function ($key) { - return is_int($key) ? $key : '\'' . $key . '\''; - }, + fn($key) => is_int($key) ? $key : '\'' . $key . '\'', $object_like_keys ) ); @@ -1947,7 +1947,7 @@ private static function handleArrayAccessOnString( $valid_offset_type = Type::getInt(false, 0); } elseif ($type instanceof TLiteralString) { if ($type->value === '') { - $valid_offset_type = Type::getEmpty(); + $valid_offset_type = Type::getNever(); } elseif (strlen($type->value) < 10) { $valid_offsets = []; @@ -2007,21 +2007,11 @@ private static function checkArrayOffsetType( true ); - if ($expanded instanceof Atomic) { - if (!$expanded instanceof TClassConstant) { - $has_valid_absolute_offset = self::checkArrayOffsetType( - $offset_type, - [$expanded], - $codebase - ); - } - } else { - $has_valid_absolute_offset = self::checkArrayOffsetType( - $offset_type, - $expanded, - $codebase - ); - } + $has_valid_absolute_offset = self::checkArrayOffsetType( + $offset_type, + $expanded, + $codebase + ); if ($has_valid_absolute_offset) { break; diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ClassConstFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ClassConstFetchAnalyzer.php index e6cb06ca161..dc6fa069753 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ClassConstFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ClassConstFetchAnalyzer.php @@ -158,7 +158,7 @@ public static function analyze( if ($first_part_lc === 'static') { $static_named_object = new TNamedObject($fq_class_name); - $static_named_object->was_static = true; + $static_named_object->is_static = true; $statements_analyzer->node_data->setType( $stmt, diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/InstancePropertyFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/InstancePropertyFetchAnalyzer.php index 35fb44b1acd..775c86ceaf9 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/InstancePropertyFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/InstancePropertyFetchAnalyzer.php @@ -120,7 +120,7 @@ public static function analyze( return true; } - if ($stmt_var_type->isEmpty()) { + if ($stmt_var_type->isNever()) { if (IssueBuffer::accepts( new MixedPropertyFetch( 'Cannot fetch property on empty var ' . $stmt_var_id, diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/IncDecExpressionAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/IncDecExpressionAnalyzer.php index 38f9a9a3ee5..4ad506e9207 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/IncDecExpressionAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/IncDecExpressionAnalyzer.php @@ -17,6 +17,9 @@ use Psalm\Node\Scalar\VirtualLNumber; use Psalm\Type; +/** + * @internal + */ class IncDecExpressionAnalyzer { /** diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/InstanceofAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/InstanceofAnalyzer.php index e87bfe2fa8e..2c2ba1292c5 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/InstanceofAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/InstanceofAnalyzer.php @@ -14,6 +14,9 @@ use function in_array; use function strtolower; +/** + * @internal + */ class InstanceofAnalyzer { public static function analyze( diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/IssetAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/IssetAnalyzer.php index 40f33819210..6b5d206946d 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/IssetAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/IssetAnalyzer.php @@ -8,6 +8,9 @@ use Psalm\Internal\Analyzer\StatementsAnalyzer; use Psalm\Type; +/** + * @internal + */ class IssetAnalyzer { public static function analyze( diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/MagicConstAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/MagicConstAnalyzer.php index 4432087d9b8..6690096dd2a 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/MagicConstAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/MagicConstAnalyzer.php @@ -16,6 +16,9 @@ use Psalm\Type\Atomic\TNonEmptyString; use Psalm\Type\Union; +/** + * @internal + */ class MagicConstAnalyzer { public static function analyze( diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/MatchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/MatchAnalyzer.php index 139fde071d8..2e2ee100d94 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/MatchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/MatchAnalyzer.php @@ -40,6 +40,9 @@ use function spl_object_id; use function substr; +/** + * @internal + */ class MatchAnalyzer { public static function analyze( @@ -261,12 +264,10 @@ public static function analyze( if (isset($vars_in_scope_reconciled[$switch_var_id])) { $array_literal_types = array_filter( $vars_in_scope_reconciled[$switch_var_id]->getAtomicTypes(), - function ($type) { - return $type instanceof TLiteralInt - || $type instanceof TLiteralString - || $type instanceof TLiteralFloat - || $type instanceof TEnumCase; - } + fn($type) => $type instanceof TLiteralInt + || $type instanceof TLiteralString + || $type instanceof TLiteralFloat + || $type instanceof TEnumCase ); if ($array_literal_types) { @@ -311,9 +312,8 @@ private static function convertCondsToConditional( } $array_items = array_map( - function ($cond): PhpParser\Node\Expr\ArrayItem { - return new VirtualArrayItem($cond, null, false, $cond->getAttributes()); - }, + fn($cond): PhpParser\Node\Expr\ArrayItem => + new VirtualArrayItem($cond, null, false, $cond->getAttributes()), $conds ); diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/PrintAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/PrintAnalyzer.php index b65193abd39..558544a6094 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/PrintAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/PrintAnalyzer.php @@ -18,6 +18,9 @@ use Psalm\Type; use Psalm\Type\TaintKind; +/** + * @internal + */ class PrintAnalyzer { public static function analyze( diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/SimpleTypeInferer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/SimpleTypeInferer.php index 5a1bb9975ed..ae4b60eb247 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/SimpleTypeInferer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/SimpleTypeInferer.php @@ -42,6 +42,8 @@ /** * This class takes a statement and return its type by analyzing each part of the statement if necessary + * + * @internal */ class SimpleTypeInferer { @@ -697,7 +699,7 @@ private static function handleUnpackedArray( $array_creation_info->property_types[$new_offset] = $property_value; } } elseif ($unpacked_atomic_type instanceof TArray) { - if ($unpacked_atomic_type->type_params[1]->isEmpty()) { + if ($unpacked_atomic_type->isEmptyArray()) { continue; } $array_creation_info->can_create_objectlike = false; @@ -719,7 +721,7 @@ private static function handleUnpackedArray( ) ); } elseif ($unpacked_atomic_type instanceof TList) { - if ($unpacked_atomic_type->type_param->isEmpty()) { + if ($unpacked_atomic_type->type_param->isNever()) { continue; } $array_creation_info->can_create_objectlike = false; diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/TernaryAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/TernaryAnalyzer.php index 872c7346e1c..d436ebcaae0 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/TernaryAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/TernaryAnalyzer.php @@ -129,9 +129,7 @@ function (Clause $c) use ($mixed_var_ids, $cond_id): Clause { $ternary_clauses = array_values( array_filter( $ternary_clauses, - function ($c) use ($reconciled_expression_clauses): bool { - return !in_array($c->hash, $reconciled_expression_clauses); - } + fn($c): bool => !in_array($c->hash, $reconciled_expression_clauses) ) ); } diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/UnaryPlusMinusAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/UnaryPlusMinusAnalyzer.php index 174854476bb..4bc7a4388d4 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/UnaryPlusMinusAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/UnaryPlusMinusAnalyzer.php @@ -20,6 +20,9 @@ use Psalm\Type\Atomic\TString; use Psalm\Type\Union; +/** + * @internal + */ class UnaryPlusMinusAnalyzer { /** diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/YieldAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/YieldAnalyzer.php index d539c3ef467..4847e04599b 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/YieldAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/YieldAnalyzer.php @@ -22,6 +22,9 @@ use Psalm\Type\Atomic\TGenericObject; use Psalm\Type\Atomic\TNamedObject; +/** + * @internal + */ class YieldAnalyzer { public static function analyze( @@ -138,7 +141,7 @@ public static function analyze( $expression_type = Type::getMixed(); } } else { - $expression_type = Type::getEmpty(); + $expression_type = Type::getNever(); } $yield_type = null; diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/YieldFromAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/YieldFromAnalyzer.php index f9c03c41335..16a7cf247f4 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/YieldFromAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/YieldFromAnalyzer.php @@ -14,6 +14,9 @@ use function strtolower; +/** + * @internal + */ class YieldFromAnalyzer { public static function analyze( diff --git a/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php index 0ce2af836e8..552a3ecaedf 100644 --- a/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php @@ -470,20 +470,20 @@ private static function handleExpression( return YieldFromAnalyzer::analyze($statements_analyzer, $stmt, $context); } - $php_major_version = $statements_analyzer->getCodebase()->php_major_version; - $php_minor_version = $statements_analyzer->getCodebase()->php_minor_version; + $codebase = $statements_analyzer->getCodebase(); + $analysis_php_version_id = $codebase->analysis_php_version_id; - if ($stmt instanceof PhpParser\Node\Expr\Match_ && $php_major_version >= 8) { + if ($stmt instanceof PhpParser\Node\Expr\Match_ && $analysis_php_version_id >= 8_00_00) { return MatchAnalyzer::analyze($statements_analyzer, $stmt, $context); } - if ($stmt instanceof PhpParser\Node\Expr\Throw_ && $php_major_version >= 8) { + if ($stmt instanceof PhpParser\Node\Expr\Throw_ && $analysis_php_version_id >= 8_00_00) { return ThrowAnalyzer::analyze($statements_analyzer, $stmt, $context); } if (($stmt instanceof PhpParser\Node\Expr\NullsafePropertyFetch || $stmt instanceof PhpParser\Node\Expr\NullsafeMethodCall) - && $php_major_version >= 8 + && $analysis_php_version_id >= 8_00_00 ) { return NullsafeAnalyzer::analyze($statements_analyzer, $stmt, $context); } @@ -496,7 +496,7 @@ private static function handleExpression( if (IssueBuffer::accepts( new UnrecognizedExpression( 'Psalm does not understand ' . get_class($stmt) . ' for PHP ' . - $php_major_version . ' ' . $php_minor_version, + $codebase->getMajorAnalysisPhpVersion() . '.' . $codebase->getMinorAnalysisPhpVersion(), new CodeLocation($statements_analyzer->getSource(), $stmt) ), $statements_analyzer->getSuppressedIssues() diff --git a/src/Psalm/Internal/Analyzer/Statements/GlobalAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/GlobalAnalyzer.php index c716bc2bfbc..1781f242d72 100644 --- a/src/Psalm/Internal/Analyzer/Statements/GlobalAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/GlobalAnalyzer.php @@ -15,6 +15,9 @@ use function is_string; +/** + * @internal + */ class GlobalAnalyzer { public static function analyze( diff --git a/src/Psalm/Internal/Analyzer/Statements/ReturnAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/ReturnAnalyzer.php index 6936fc33e14..64fe69b8935 100644 --- a/src/Psalm/Internal/Analyzer/Statements/ReturnAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/ReturnAnalyzer.php @@ -178,7 +178,7 @@ public static function analyze( $statements_analyzer->getSuppressedIssues() ); - $stmt_type = Type::getEmpty(); + $stmt_type = Type::getNever(); } if ($stmt_type->isVoid()) { diff --git a/src/Psalm/Internal/Analyzer/Statements/StaticAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/StaticAnalyzer.php index 6133afd4509..13152602d6c 100644 --- a/src/Psalm/Internal/Analyzer/Statements/StaticAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/StaticAnalyzer.php @@ -23,6 +23,9 @@ use function is_string; +/** + * @internal + */ class StaticAnalyzer { public static function analyze( diff --git a/src/Psalm/Internal/Analyzer/Statements/ThrowAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/ThrowAnalyzer.php index 665a1c40d4d..fbe259dfeff 100644 --- a/src/Psalm/Internal/Analyzer/Statements/ThrowAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/ThrowAnalyzer.php @@ -85,7 +85,7 @@ public static function analyze( } if ($stmt instanceof PhpParser\Node\Expr\Throw_) { - $statements_analyzer->node_data->setType($stmt, Type::getEmpty()); + $statements_analyzer->node_data->setType($stmt, Type::getNever()); } return true; diff --git a/src/Psalm/Internal/Analyzer/Statements/UnsetAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/UnsetAnalyzer.php index c7cfeb6d5a3..b11ce758a63 100644 --- a/src/Psalm/Internal/Analyzer/Statements/UnsetAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/UnsetAnalyzer.php @@ -9,16 +9,19 @@ use Psalm\Type; use Psalm\Type\Atomic\TArray; use Psalm\Type\Atomic\TArrayKey; -use Psalm\Type\Atomic\TEmpty; use Psalm\Type\Atomic\TKeyedArray; use Psalm\Type\Atomic\TList; use Psalm\Type\Atomic\TMixed; +use Psalm\Type\Atomic\TNever; use Psalm\Type\Atomic\TNonEmptyArray; use Psalm\Type\Atomic\TNonEmptyMixed; use Psalm\Type\Union; use function count; +/** + * @internal + */ class UnsetAnalyzer { public static function analyze( @@ -84,8 +87,8 @@ public static function analyze( } else { $root_type->addType( new TArray([ - new Union([new TEmpty]), - new Union([new TEmpty]), + new Union([new TNever]), + new Union([new TNever]), ]) ); } diff --git a/src/Psalm/Internal/Analyzer/Statements/UnusedAssignmentRemover.php b/src/Psalm/Internal/Analyzer/Statements/UnusedAssignmentRemover.php index 30c01b4e28f..3f6e50e81a7 100644 --- a/src/Psalm/Internal/Analyzer/Statements/UnusedAssignmentRemover.php +++ b/src/Psalm/Internal/Analyzer/Statements/UnusedAssignmentRemover.php @@ -19,6 +19,9 @@ use function token_get_all; use function trim; +/** + * @internal + */ class UnusedAssignmentRemover { /** diff --git a/src/Psalm/Internal/Analyzer/TypeAnalyzer.php b/src/Psalm/Internal/Analyzer/TypeAnalyzer.php index 2a8451c1e02..9d1648c5961 100644 --- a/src/Psalm/Internal/Analyzer/TypeAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/TypeAnalyzer.php @@ -2,12 +2,7 @@ namespace Psalm\Internal\Analyzer; -use Psalm\Codebase; -use Psalm\Internal\Type\Comparator\AtomicTypeComparator; -use Psalm\Internal\Type\Comparator\TypeComparisonResult; -use Psalm\Internal\Type\Comparator\UnionTypeComparator; use Psalm\Type; -use Psalm\Type\Atomic; use Psalm\Type\Union; use function array_keys; @@ -19,56 +14,6 @@ */ class TypeAnalyzer { - /** - * Does the input param type match the given param type - * - * @deprecated in favour of UnionTypeComparator, going to be removed in Psalm 5 - * @psalm-suppress PossiblyUnusedMethod - */ - public static function isContainedBy( - Codebase $codebase, - Union $input_type, - Union $container_type, - bool $ignore_null = false, - bool $ignore_false = false, - ?TypeComparisonResult $union_comparison_result = null, - bool $allow_interface_equality = false - ): bool { - return UnionTypeComparator::isContainedBy( - $codebase, - $input_type, - $container_type, - $ignore_null, - $ignore_false, - $union_comparison_result, - $allow_interface_equality - ); - } - - /** - * Does the input param atomic type match the given param atomic type - * - * @deprecated in favour of AtomicTypeComparator, going to be removed in Psalm 5 - * @psalm-suppress PossiblyUnusedMethod - */ - public static function isAtomicContainedBy( - Codebase $codebase, - Atomic $input_type_part, - Atomic $container_type_part, - bool $allow_interface_equality = false, - bool $allow_float_int_equality = true, - ?TypeComparisonResult $atomic_comparison_result = null - ): bool { - return AtomicTypeComparator::isContainedBy( - $codebase, - $input_type_part, - $container_type_part, - $allow_interface_equality, - $allow_float_int_equality, - $atomic_comparison_result - ); - } - /** * Takes two arrays of types and merges them * diff --git a/src/Psalm/Internal/Clause.php b/src/Psalm/Internal/Clause.php index 5497f579b9b..a3c6e70232f 100644 --- a/src/Psalm/Internal/Clause.php +++ b/src/Psalm/Internal/Clause.php @@ -19,6 +19,8 @@ use function strpos; use function substr; +use const JSON_THROW_ON_ERROR; + /** * @internal * @@ -110,7 +112,7 @@ public function __construct( sort($possibilities[$i]); } - $this->hash = md5((string) json_encode($possibilities)); + $this->hash = md5(json_encode($possibilities, JSON_THROW_ON_ERROR)); } } diff --git a/src/Psalm/Internal/Cli/LanguageServer.php b/src/Psalm/Internal/Cli/LanguageServer.php index e8689ee968c..22fd434ed87 100644 --- a/src/Psalm/Internal/Cli/LanguageServer.php +++ b/src/Psalm/Internal/Cli/LanguageServer.php @@ -52,6 +52,9 @@ require_once __DIR__ . '/../Composer.php'; require_once __DIR__ . '/../IncludeCollector.php'; +/** + * @internal + */ final class LanguageServer { /** @param array $argv */ @@ -118,7 +121,7 @@ function (string $arg) use ($valid_long_options): void { if (!array_key_exists('use-ini-defaults', $options)) { ini_set('display_errors', '1'); ini_set('display_startup_errors', '1'); - ini_set('memory_limit', (string) (8 * 1024 * 1024 * 1024)); + ini_set('memory_limit', (string) (8 * 1_024 * 1_024 * 1_024)); } if (array_key_exists('help', $options)) { @@ -140,51 +143,51 @@ function (string $arg) use ($valid_long_options): void { if (array_key_exists('h', $options)) { echo <<runAndCollect( // we ignore the FQN because of a hack in scoper.inc that needs full path // phpcs:ignore SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly.ReferenceViaFullyQualifiedName - function () use ($current_dir, $options, $vendor_dir): ?\Composer\Autoload\ClassLoader { - return CliUtils::requireAutoloaders($current_dir, isset($options['r']), $vendor_dir); - } + fn(): ?\Composer\Autoload\ClassLoader => + CliUtils::requireAutoloaders($current_dir, isset($options['r']), $vendor_dir) ); if (array_key_exists('v', $options)) { diff --git a/src/Psalm/Internal/Cli/Plugin.php b/src/Psalm/Internal/Cli/Plugin.php index 3e180d5af6d..b91f24dc372 100644 --- a/src/Psalm/Internal/Cli/Plugin.php +++ b/src/Psalm/Internal/Cli/Plugin.php @@ -21,6 +21,9 @@ require_once __DIR__ . '/../CliUtils.php'; require_once __DIR__ . '/../Composer.php'; +/** + * @internal + */ final class Plugin { public static function run(): void diff --git a/src/Psalm/Internal/Cli/Psalm.php b/src/Psalm/Internal/Cli/Psalm.php index 36043f4cf89..b512574332b 100644 --- a/src/Psalm/Internal/Cli/Psalm.php +++ b/src/Psalm/Internal/Cli/Psalm.php @@ -33,7 +33,7 @@ use Psalm\Report; use Psalm\Report\ReportOptions; use RuntimeException; -use Webmozart\PathUtil\Path; +use Symfony\Component\Filesystem\Path; use function array_filter; use function array_key_exists; @@ -73,6 +73,7 @@ use function version_compare; use const DIRECTORY_SEPARATOR; +use const JSON_THROW_ON_ERROR; use const LC_CTYPE; use const PHP_EOL; use const PHP_OS; @@ -87,6 +88,9 @@ require_once __DIR__ . '/../IncludeCollector.php'; require_once __DIR__ . '/../../IssueBuffer.php'; +/** + * @internal + */ final class Psalm { private const SHORT_OPTIONS = [ @@ -189,7 +193,7 @@ public static function run(array $argv): void if (array_key_exists('h', $options)) { - echo CliUtils::getPsalmHelpText(); + echo self::getHelpText(); /* --shepherd[=host] Send data to Shepherd, Psalm's GitHub integration tool. @@ -213,9 +217,8 @@ public static function run(array $argv): void $first_autoloader = $include_collector->runAndCollect( // we ignore the FQN because of a hack in scoper.inc that needs full path // phpcs:ignore SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly.ReferenceViaFullyQualifiedName - function () use ($current_dir, $options, $vendor_dir): ?\Composer\Autoload\ClassLoader { - return CliUtils::requireAutoloaders($current_dir, isset($options['r']), $vendor_dir); - } + fn(): ?\Composer\Autoload\ClassLoader => + CliUtils::requireAutoloaders($current_dir, isset($options['r']), $vendor_dir) ); $run_taint_analysis = self::shouldRunTaintAnalysis($options); @@ -472,7 +475,7 @@ private static function setMemoryLimit(array $options): void ini_set('display_errors', 'stderr'); ini_set('display_startup_errors', '1'); - $memoryLimit = (8 * 1024 * 1024 * 1024); + $memoryLimit = (8 * 1_024 * 1_024 * 1_024); if (array_key_exists('memory-limit', $options)) { $memoryLimit = $options['memory-limit']; @@ -497,18 +500,16 @@ private static function generateConfig(string $current_dir, array &$args): void $args = array_values(array_filter( $args, - function (string $arg): bool { - return $arg !== '--ansi' - && $arg !== '--no-ansi' - && $arg !== '-i' - && $arg !== '--init' - && $arg !== '--debug' - && $arg !== '--debug-by-line' - && $arg !== '--debug-emitted-issues' - && strpos($arg, '--disable-extension=') !== 0 - && strpos($arg, '--root=') !== 0 - && strpos($arg, '--r=') !== 0; - } + fn(string $arg): bool => $arg !== '--ansi' + && $arg !== '--no-ansi' + && $arg !== '-i' + && $arg !== '--init' + && $arg !== '--debug' + && $arg !== '--debug-by-line' + && $arg !== '--debug-emitted-issues' + && strpos($arg, '--disable-extension=') !== 0 + && strpos($arg, '--root=') !== 0 + && strpos($arg, '--r=') !== 0 )); $init_level = null; @@ -739,7 +740,10 @@ private static function storeTypeMap(Providers $providers, Config $config, strin $expected_references ); - $type_map_string = json_encode(['files' => $name_file_map, 'references' => $reference_dictionary]); + $type_map_string = json_encode( + ['files' => $name_file_map, 'references' => $reference_dictionary], + JSON_THROW_ON_ERROR + ); $providers->file_provider->setContents( $type_map_location, @@ -1066,9 +1070,7 @@ private static function storeFlowGraph(array $options, ProjectAnalyzer $project_ if ($flow_graph !== null && $dump_taint_graph !== null) { file_put_contents($dump_taint_graph, "digraph Taints {\n\t". implode("\n\t", array_map( - function (array $edges) { - return '"'.implode('" -> "', $edges).'"'; - }, + fn(array $edges) => '"'.implode('" -> "', $edges).'"', $flow_graph->summarizeEdges() )) . "\n}\n"); @@ -1170,4 +1172,169 @@ private static function generateStubs( ); } } + + /** + * @psalm-pure + */ + private static function getHelpText(): string + { + return <<runAndCollect( // we ignore the FQN because of a hack in scoper.inc that needs full path // phpcs:ignore SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly.ReferenceViaFullyQualifiedName - function () use ($current_dir, $options, $vendor_dir): ?\Composer\Autoload\ClassLoader { - return CliUtils::requireAutoloaders($current_dir, isset($options['r']), $vendor_dir); - } + fn(): ?\Composer\Autoload\ClassLoader => + CliUtils::requireAutoloaders($current_dir, isset($options['r']), $vendor_dir) ); @@ -421,8 +423,8 @@ private static function setMemoryLimit(): void { $memLimit = CliUtils::getMemoryLimitInBytes(); // Magic number is 4096M in bytes - if ($memLimit > 0 && $memLimit < 8 * 1024 * 1024 * 1024) { - ini_set('memory_limit', (string) (8 * 1024 * 1024 * 1024)); + if ($memLimit > 0 && $memLimit < 8 * 1_024 * 1_024 * 1_024) { + ini_set('memory_limit', (string) (8 * 1_024 * 1_024 * 1_024)); } } diff --git a/src/Psalm/Internal/Cli/Refactor.php b/src/Psalm/Internal/Cli/Refactor.php index 1c87f4e84a2..af1a59d12f8 100644 --- a/src/Psalm/Internal/Cli/Refactor.php +++ b/src/Psalm/Internal/Cli/Refactor.php @@ -56,6 +56,9 @@ require_once __DIR__ . '/../IncludeCollector.php'; require_once __DIR__ . '/../../IssueBuffer.php'; +/** + * @internal + */ final class Refactor { /** @param array $argv */ @@ -119,37 +122,37 @@ function (string $arg) use ($valid_long_options): void { if (array_key_exists('h', $options)) { echo <<runAndCollect( // we ignore the FQN because of a hack in scoper.inc that needs full path // phpcs:ignore SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly.ReferenceViaFullyQualifiedName - function () use ($current_dir, $options, $vendor_dir): ?\Composer\Autoload\ClassLoader { - return CliUtils::requireAutoloaders($current_dir, isset($options['r']), $vendor_dir); - } + fn(): ?\Composer\Autoload\ClassLoader => + CliUtils::requireAutoloaders($current_dir, isset($options['r']), $vendor_dir) ); // If Xdebug is enabled, restart without it diff --git a/src/Psalm/Internal/CliUtils.php b/src/Psalm/Internal/CliUtils.php index 810a8581a13..b553a7757b0 100644 --- a/src/Psalm/Internal/CliUtils.php +++ b/src/Psalm/Internal/CliUtils.php @@ -3,6 +3,7 @@ namespace Psalm\Internal; use Composer\Autoload\ClassLoader; +use JsonException; use PackageVersions\Versions; use Phar; use Psalm\Config; @@ -44,10 +45,14 @@ use function trim; use const DIRECTORY_SEPARATOR; +use const JSON_THROW_ON_ERROR; use const PHP_EOL; use const STDERR; use const STDIN; +/** + * @internal + */ final class CliUtils { public static function requireAutoloaders( @@ -163,8 +168,17 @@ public static function getVendorDir(string $current_dir): string if (!file_exists($composer_json_path)) { return 'vendor'; } + try { + $composer_json = json_decode(file_get_contents($composer_json_path), true, 512, JSON_THROW_ON_ERROR); + } catch (JsonException $e) { + fwrite( + STDERR, + 'Invalid composer.json at ' . $composer_json_path . "\n" . $e->getMessage() . "\n" + ); + exit(1); + } - if (!$composer_json = json_decode(file_get_contents($composer_json_path), true)) { + if (!$composer_json) { fwrite( STDERR, 'Invalid composer.json at ' . $composer_json_path . "\n" @@ -313,171 +327,6 @@ public static function getPathsToCheck($f_paths): ?array return $paths_to_check; } - /** - * @psalm-pure - * @todo move to Psalm\Internal\Cli\Psalm once \Psalm\getPsalmHelpText() is removed in Psalm 5 - */ - public static function getPsalmHelpText(): string - { - return <<files_to_analyze = array_filter( $this->files_to_analyze, - function (string $file_path): bool { - return $this->file_provider->fileExists($file_path); - } + fn(string $file_path): bool => $this->file_provider->fileExists($file_path) ); $this->doAnalysis($project_analyzer, $pool_size); diff --git a/src/Psalm/Internal/Codebase/ClassLikes.php b/src/Psalm/Internal/Codebase/ClassLikes.php index 372d1da901f..658d7e194a5 100644 --- a/src/Psalm/Internal/Codebase/ClassLikes.php +++ b/src/Psalm/Internal/Codebase/ClassLikes.php @@ -806,7 +806,10 @@ public function getTraitNode(string $fq_trait_name): PhpParser\Node\Stmt\Trait_ throw new UnexpectedValueException('Storage should exist for ' . $fq_trait_name); } - $file_statements = $this->statements_provider->getStatementsForFile($storage->location->file_path, '7.4'); + $file_statements = $this->statements_provider->getStatementsForFile( + $storage->location->file_path, + ProjectAnalyzer::getInstance()->getCodebase()->analysis_php_version_id + ); $trait_finder = new TraitFinder($fq_trait_name); @@ -1601,29 +1604,23 @@ public function getConstantsForClass(string $class_name, int $visibility): array if ($visibility === ReflectionProperty::IS_PUBLIC) { return array_filter( $storage->constants, - function ($constant) { - return $constant->type - && $constant->visibility === ClassLikeAnalyzer::VISIBILITY_PUBLIC; - } + fn($constant) => $constant->type + && $constant->visibility === ClassLikeAnalyzer::VISIBILITY_PUBLIC ); } if ($visibility === ReflectionProperty::IS_PROTECTED) { return array_filter( $storage->constants, - function ($constant) { - return $constant->type - && ($constant->visibility === ClassLikeAnalyzer::VISIBILITY_PUBLIC - || $constant->visibility === ClassLikeAnalyzer::VISIBILITY_PROTECTED); - } + fn($constant) => $constant->type + && ($constant->visibility === ClassLikeAnalyzer::VISIBILITY_PUBLIC + || $constant->visibility === ClassLikeAnalyzer::VISIBILITY_PROTECTED) ); } return array_filter( $storage->constants, - function ($constant) { - return $constant->type !== null; - } + fn($constant) => $constant->type !== null ); } diff --git a/src/Psalm/Internal/Codebase/ConstantTypeResolver.php b/src/Psalm/Internal/Codebase/ConstantTypeResolver.php index c27cc523f7c..dfc1a7f0d4e 100644 --- a/src/Psalm/Internal/Codebase/ConstantTypeResolver.php +++ b/src/Psalm/Internal/Codebase/ConstantTypeResolver.php @@ -25,7 +25,6 @@ use Psalm\Type; use Psalm\Type\Atomic; use Psalm\Type\Atomic\TArray; -use Psalm\Type\Atomic\TEmpty; use Psalm\Type\Atomic\TFalse; use Psalm\Type\Atomic\TKeyedArray; use Psalm\Type\Atomic\TLiteralClassString; @@ -33,6 +32,7 @@ use Psalm\Type\Atomic\TLiteralInt; use Psalm\Type\Atomic\TLiteralString; use Psalm\Type\Atomic\TMixed; +use Psalm\Type\Atomic\TNever; use Psalm\Type\Atomic\TNull; use Psalm\Type\Atomic\TString; use Psalm\Type\Atomic\TTrue; @@ -185,7 +185,7 @@ public static function resolve( $auto_key = 0; if (!$c->entries) { - return new TArray([Type::getEmpty(), Type::getEmpty()]); + return new TArray([Type::getNever(), Type::getNever()]); } $is_list = true; @@ -199,7 +199,7 @@ public static function resolve( $visited_constant_ids + [$c_id => true] ); - if ($spread_array instanceof TArray && $spread_array->type_params[1]->isEmpty()) { + if ($spread_array instanceof TArray && $spread_array->isEmptyArray()) { continue; } @@ -255,8 +255,8 @@ public static function resolve( if (empty($properties)) { $resolved_type = new TArray([ - new Union([new TEmpty()]), - new Union([new TEmpty()]), + new Union([new TNever()]), + new Union([new TNever()]), ]); } else { $resolved_type = new TKeyedArray($properties); diff --git a/src/Psalm/Internal/Codebase/DataFlowGraph.php b/src/Psalm/Internal/Codebase/DataFlowGraph.php index 988a814062f..4a41bf68a3e 100644 --- a/src/Psalm/Internal/Codebase/DataFlowGraph.php +++ b/src/Psalm/Internal/Codebase/DataFlowGraph.php @@ -15,6 +15,9 @@ use function strpos; use function substr; +/** + * @internal + */ abstract class DataFlowGraph { /** @var array> */ diff --git a/src/Psalm/Internal/Codebase/InternalCallMapHandler.php b/src/Psalm/Internal/Codebase/InternalCallMapHandler.php index 8af4d0cbd75..a41fa171f0c 100644 --- a/src/Psalm/Internal/Codebase/InternalCallMapHandler.php +++ b/src/Psalm/Internal/Codebase/InternalCallMapHandler.php @@ -347,8 +347,8 @@ public static function getCallablesFromCallMap(string $function_id): ?array public static function getCallMap(): array { $codebase = ProjectAnalyzer::getInstance()->getCodebase(); - $analyzer_major_version = $codebase->php_major_version; - $analyzer_minor_version = $codebase->php_minor_version; + $analyzer_major_version = $codebase->getMajorAnalysisPhpVersion(); + $analyzer_minor_version = $codebase->getMinorAnalysisPhpVersion(); $analyzer_version = $analyzer_major_version . '.' . $analyzer_minor_version; $current_version = self::PHP_MAJOR_VERSION . '.' . self::PHP_MINOR_VERSION; diff --git a/src/Psalm/Internal/Codebase/Populator.php b/src/Psalm/Internal/Codebase/Populator.php index c293367f7f6..15cc6fc8304 100644 --- a/src/Psalm/Internal/Codebase/Populator.php +++ b/src/Psalm/Internal/Codebase/Populator.php @@ -3,7 +3,6 @@ namespace Psalm\Internal\Codebase; use InvalidArgumentException; -use Psalm\Config; use Psalm\Internal\Analyzer\ClassLikeAnalyzer; use Psalm\Internal\MethodIdentifier; use Psalm\Internal\Provider\ClassLikeStorageProvider; @@ -14,11 +13,6 @@ use Psalm\Progress\Progress; use Psalm\Storage\ClassLikeStorage; use Psalm\Storage\FileStorage; -use Psalm\Type; -use Psalm\Type\Atomic\TArray; -use Psalm\Type\Atomic\TGenericObject; -use Psalm\Type\Atomic\TIterable; -use Psalm\Type\Atomic\TNamedObject; use Psalm\Type\Atomic\TTemplateParam; use Psalm\Type\Union; @@ -65,18 +59,12 @@ class Populator */ private $classlikes; - /** - * @var Config - */ - private $config; - /** * @var FileReferenceProvider */ private $file_reference_provider; public function __construct( - Config $config, ClassLikeStorageProvider $classlike_storage_provider, FileStorageProvider $file_storage_provider, ClassLikes $classlikes, @@ -87,7 +75,6 @@ public function __construct( $this->file_storage_provider = $file_storage_provider; $this->classlikes = $classlikes; $this->progress = $progress; - $this->config = $config; $this->file_reference_provider = $file_reference_provider; } @@ -110,26 +97,6 @@ public function populateCodebase(): void } foreach ($this->classlike_storage_provider->getNew() as $class_storage) { - if ($this->config->allow_phpstorm_generics) { - foreach ($class_storage->properties as $property_storage) { - if ($property_storage->type) { - $this->convertPhpStormGenericToPsalmGeneric($property_storage->type, true); - } - } - - foreach ($class_storage->methods as $method_storage) { - if ($method_storage->return_type) { - $this->convertPhpStormGenericToPsalmGeneric($method_storage->return_type); - } - - foreach ($method_storage->params as $param_storage) { - if ($param_storage->type) { - $this->convertPhpStormGenericToPsalmGeneric($param_storage->type); - } - } - } - } - foreach ($class_storage->dependent_classlikes as $dependent_classlike_lc => $_) { try { $dependee_storage = $this->classlike_storage_provider->get($dependent_classlike_lc); @@ -141,22 +108,6 @@ public function populateCodebase(): void } } - if ($this->config->allow_phpstorm_generics) { - foreach ($all_file_storage as $file_storage) { - foreach ($file_storage->functions as $function_storage) { - if ($function_storage->return_type) { - $this->convertPhpStormGenericToPsalmGeneric($function_storage->return_type); - } - - foreach ($function_storage->params as $param_storage) { - if ($param_storage->type) { - $this->convertPhpStormGenericToPsalmGeneric($param_storage->type); - } - } - } - } - } - $this->progress->debug('FileStorage is populated' . "\n"); ClassLikeStorageProvider::populated(); @@ -575,10 +526,8 @@ private function populateDataFromParentClass( $storage->constants = array_merge( array_filter( $parent_storage->constants, - function ($constant) { - return $constant->visibility === ClassLikeAnalyzer::VISIBILITY_PUBLIC - || $constant->visibility === ClassLikeAnalyzer::VISIBILITY_PROTECTED; - } + fn($constant) => $constant->visibility === ClassLikeAnalyzer::VISIBILITY_PUBLIC + || $constant->visibility === ClassLikeAnalyzer::VISIBILITY_PROTECTED ), $storage->constants ); @@ -636,9 +585,7 @@ private function populateInterfaceDataFromParentInterfaces( $storage->constants = array_merge( array_filter( $parent_interface_storage->constants, - function ($constant) { - return $constant->visibility === ClassLikeAnalyzer::VISIBILITY_PUBLIC; - } + fn($constant) => $constant->visibility === ClassLikeAnalyzer::VISIBILITY_PUBLIC ), $storage->constants ); @@ -742,9 +689,7 @@ private function populateDataFromImplementedInterfaces( $storage->constants = array_merge( array_filter( $implemented_interface_storage->constants, - function ($constant) { - return $constant->visibility === ClassLikeAnalyzer::VISIBILITY_PUBLIC; - } + fn($constant) => $constant->visibility === ClassLikeAnalyzer::VISIBILITY_PUBLIC ), $storage->constants ); @@ -991,65 +936,6 @@ private function populateFileStorage(FileStorage $storage, array $dependent_file $storage->populated = true; } - private function convertPhpStormGenericToPsalmGeneric(Union $candidate, bool $is_property = false): void - { - if (!$candidate->from_docblock) { - //never convert a type that comes from a signature - return; - } - - $atomic_types = $candidate->getAtomicTypes(); - - if (isset($atomic_types['array']) && count($atomic_types) > 1 && !isset($atomic_types['null'])) { - $iterator_name = null; - $generic_params = null; - $iterator_key = null; - - try { - foreach ($atomic_types as $type_key => $type) { - if ($type instanceof TIterable - || ($type instanceof TNamedObject - && (!$type->from_docblock || $is_property) - && ( - strtolower($type->value) === 'traversable' - || $this->classlikes->interfaceExtends( - $type->value, - 'Traversable' - ) - || $this->classlikes->classImplements( - $type->value, - 'Traversable' - ) - )) - ) { - $iterator_name = $type->value; - $iterator_key = $type_key; - } elseif ($type instanceof TArray) { - $generic_params = $type->type_params; - } - } - } catch (InvalidArgumentException $e) { - // ignore class-not-found issues - } - - if ($iterator_name && $iterator_key && $generic_params) { - if ($iterator_name === 'iterable') { - $generic_iterator = new TIterable($generic_params); - } else { - if (strtolower($iterator_name) === 'generator') { - $generic_params[] = Type::getMixed(); - $generic_params[] = Type::getMixed(); - } - $generic_iterator = new TGenericObject($iterator_name, $generic_params); - } - - $candidate->removeType('array'); - $candidate->removeType($iterator_key); - $candidate->addType($generic_iterator); - } - } - } - protected function inheritMethodsFromParent( ClassLikeStorage $storage, ClassLikeStorage $parent_storage diff --git a/src/Psalm/Internal/Codebase/ReferenceMapGenerator.php b/src/Psalm/Internal/Codebase/ReferenceMapGenerator.php index 80a6ad20019..1421846bc1d 100644 --- a/src/Psalm/Internal/Codebase/ReferenceMapGenerator.php +++ b/src/Psalm/Internal/Codebase/ReferenceMapGenerator.php @@ -4,6 +4,9 @@ use Psalm\Internal\Provider\ClassLikeStorageProvider; +/** + * @internal + */ class ReferenceMapGenerator { /** diff --git a/src/Psalm/Internal/Codebase/Reflection.php b/src/Psalm/Internal/Codebase/Reflection.php index ed3b3fc1b2d..69348f04785 100644 --- a/src/Psalm/Internal/Codebase/Reflection.php +++ b/src/Psalm/Internal/Codebase/Reflection.php @@ -431,9 +431,7 @@ public static function getPsalmTypeFromReflectionType(?ReflectionType $reflectio $type = implode( '|', array_map( - function (ReflectionNamedType $reflection) { - return $reflection->getName(); - }, + fn(ReflectionNamedType $reflection) => $reflection->getName(), $reflection_type->getTypes() ) ); diff --git a/src/Psalm/Internal/Codebase/Scanner.php b/src/Psalm/Internal/Codebase/Scanner.php index fb6e1bf5c63..e510bf63827 100644 --- a/src/Psalm/Internal/Codebase/Scanner.php +++ b/src/Psalm/Internal/Codebase/Scanner.php @@ -316,11 +316,9 @@ private function scanFilePaths(int $pool_size): bool $filetype_scanners = $this->config->getFiletypeScanners(); $files_to_scan = array_filter( $this->files_to_scan, - function (string $file_path): bool { - return $this->file_provider->fileExists($file_path) - && (!isset($this->scanned_files[$file_path]) - || (isset($this->files_to_deep_scan[$file_path]) && !$this->scanned_files[$file_path])); - } + fn(string $file_path): bool => $this->file_provider->fileExists($file_path) + && (!isset($this->scanned_files[$file_path]) + || (isset($this->files_to_deep_scan[$file_path]) && !$this->scanned_files[$file_path])) ); $this->files_to_scan = []; diff --git a/src/Psalm/Internal/Codebase/TaintFlowGraph.php b/src/Psalm/Internal/Codebase/TaintFlowGraph.php index 2812bb6a1fd..1f61889f6b4 100644 --- a/src/Psalm/Internal/Codebase/TaintFlowGraph.php +++ b/src/Psalm/Internal/Codebase/TaintFlowGraph.php @@ -41,6 +41,11 @@ use function strlen; use function substr; +use const JSON_THROW_ON_ERROR; + +/** + * @internal + */ class TaintFlowGraph extends DataFlowGraph { /** @var array */ @@ -465,8 +470,8 @@ private function getChildNodes( $new_destination->path_types = array_merge($generated_source->path_types, [$path_type]); $key = $to_id . - ' ' . json_encode($new_destination->specialized_calls) . - ' ' . json_encode($new_destination->taints); + ' ' . json_encode($new_destination->specialized_calls, JSON_THROW_ON_ERROR) . + ' ' . json_encode($new_destination->taints, JSON_THROW_ON_ERROR); $new_sources[$key] = $new_destination; } @@ -516,9 +521,7 @@ private function getSpecializedSources(DataFlowNode $source): array return array_filter( $generated_sources, - function ($new_source): bool { - return isset($this->forward_edges[$new_source->id]); - } + fn($new_source): bool => isset($this->forward_edges[$new_source->id]) ); } } diff --git a/src/Psalm/Internal/Codebase/VariableUseGraph.php b/src/Psalm/Internal/Codebase/VariableUseGraph.php index 4698f3e2dbc..2e8c192e180 100644 --- a/src/Psalm/Internal/Codebase/VariableUseGraph.php +++ b/src/Psalm/Internal/Codebase/VariableUseGraph.php @@ -10,6 +10,9 @@ use function array_merge; use function count; +/** + * @internal + */ class VariableUseGraph extends DataFlowGraph { /** @var array> */ diff --git a/src/Psalm/Internal/DataFlow/DataFlowNode.php b/src/Psalm/Internal/DataFlow/DataFlowNode.php index 39f45644042..95c2327970d 100644 --- a/src/Psalm/Internal/DataFlow/DataFlowNode.php +++ b/src/Psalm/Internal/DataFlow/DataFlowNode.php @@ -8,6 +8,8 @@ /** * @psalm-consistent-constructor + * + * @internal */ class DataFlowNode { diff --git a/src/Psalm/Internal/DataFlow/Path.php b/src/Psalm/Internal/DataFlow/Path.php index 05d120b4a35..e1fb988508f 100644 --- a/src/Psalm/Internal/DataFlow/Path.php +++ b/src/Psalm/Internal/DataFlow/Path.php @@ -4,6 +4,8 @@ /** * @psalm-immutable + * + * @internal */ class Path { diff --git a/src/Psalm/Internal/DataFlow/TaintSink.php b/src/Psalm/Internal/DataFlow/TaintSink.php index 408f13ccd84..2d89b628d3c 100644 --- a/src/Psalm/Internal/DataFlow/TaintSink.php +++ b/src/Psalm/Internal/DataFlow/TaintSink.php @@ -2,6 +2,9 @@ namespace Psalm\Internal\DataFlow; +/** + * @internal + */ class TaintSink extends DataFlowNode { } diff --git a/src/Psalm/Internal/DataFlow/TaintSource.php b/src/Psalm/Internal/DataFlow/TaintSource.php index c3b9acc6015..747155f7bcb 100644 --- a/src/Psalm/Internal/DataFlow/TaintSource.php +++ b/src/Psalm/Internal/DataFlow/TaintSource.php @@ -2,6 +2,9 @@ namespace Psalm\Internal\DataFlow; +/** + * @internal + */ class TaintSource extends DataFlowNode { } diff --git a/src/Psalm/Internal/ErrorHandler.php b/src/Psalm/Internal/ErrorHandler.php index 802f4727dbc..3d96162d2d1 100644 --- a/src/Psalm/Internal/ErrorHandler.php +++ b/src/Psalm/Internal/ErrorHandler.php @@ -16,6 +16,9 @@ use const E_STRICT; use const STDERR; +/** + * @internal + */ final class ErrorHandler { /** @var bool */ diff --git a/src/Psalm/Internal/EventDispatcher.php b/src/Psalm/Internal/EventDispatcher.php index 82db6300091..29e8067490d 100644 --- a/src/Psalm/Internal/EventDispatcher.php +++ b/src/Psalm/Internal/EventDispatcher.php @@ -33,26 +33,15 @@ use Psalm\Plugin\EventHandler\Event\StringInterpreterEvent; use Psalm\Plugin\EventHandler\RemoveTaintsInterface; use Psalm\Plugin\EventHandler\StringInterpreterInterface; -use Psalm\Plugin\Hook\AfterAnalysisInterface as LegacyAfterAnalysisInterface; -use Psalm\Plugin\Hook\AfterClassLikeAnalysisInterface as LegacyAfterClassLikeAnalysisInterface; -use Psalm\Plugin\Hook\AfterClassLikeExistenceCheckInterface as LegacyAfterClassLikeExistenceCheckInterface; -use Psalm\Plugin\Hook\AfterClassLikeVisitInterface as LegacyAfterClassLikeVisitInterface; -use Psalm\Plugin\Hook\AfterCodebasePopulatedInterface as LegacyAfterCodebasePopulatedInterface; -use Psalm\Plugin\Hook\AfterEveryFunctionCallAnalysisInterface as LegacyAfterEveryFunctionCallAnalysisInterface; -use Psalm\Plugin\Hook\AfterExpressionAnalysisInterface as LegacyAfterExpressionAnalysisInterface; -use Psalm\Plugin\Hook\AfterFileAnalysisInterface as LegacyAfterFileAnalysisInterface; -use Psalm\Plugin\Hook\AfterFunctionCallAnalysisInterface as LegacyAfterFunctionCallAnalysisInterface; -use Psalm\Plugin\Hook\AfterFunctionLikeAnalysisInterface as LegacyAfterFunctionLikeAnalysisInterface; -use Psalm\Plugin\Hook\AfterMethodCallAnalysisInterface as LegacyAfterMethodCallAnalysisInterface; -use Psalm\Plugin\Hook\AfterStatementAnalysisInterface as LegacyAfterStatementAnalysisInterface; -use Psalm\Plugin\Hook\BeforeFileAnalysisInterface as LegacyBeforeFileAnalysisInterface; -use Psalm\Plugin\Hook\StringInterpreterInterface as LegacyStringInterpreterInterface; use Psalm\Type\Atomic\TLiteralString; use function array_merge; use function count; use function is_subclass_of; +/** + * @internal + */ class EventDispatcher { /** @@ -61,8 +50,6 @@ class EventDispatcher * @var list> */ private $after_method_checks = []; - /** @var list> */ - private $legacy_after_method_checks = []; /** * Static methods to be called after project function checks have completed @@ -74,8 +61,6 @@ class EventDispatcher * @var list> */ public $after_function_checks = []; - /** @var list> */ - public $legacy_after_function_checks = []; /** * Static methods to be called after every function call @@ -87,8 +72,6 @@ class EventDispatcher * @var list> */ public $after_every_function_checks = []; - /** @var list> */ - public $legacy_after_every_function_checks = []; /** * Static methods to be called after expression checks have completed @@ -96,8 +79,6 @@ class EventDispatcher * @var list> */ public $after_expression_checks = []; - /** @var list> */ - public $legacy_after_expression_checks = []; /** * Static methods to be called after statement checks have completed @@ -105,8 +86,6 @@ class EventDispatcher * @var list> */ public $after_statement_checks = []; - /** @var list> */ - public $legacy_after_statement_checks = []; /** * Static methods to be called after method checks have completed @@ -114,8 +93,6 @@ class EventDispatcher * @var list> */ public $string_interpreters = []; - /** @var list> */ - public $legacy_string_interpreters = []; /** * Static methods to be called after classlike exists checks have completed @@ -123,8 +100,6 @@ class EventDispatcher * @var list> */ public $after_classlike_exists_checks = []; - /** @var list> */ - public $legacy_after_classlike_exists_checks = []; /** * Static methods to be called after classlike checks have completed @@ -132,8 +107,6 @@ class EventDispatcher * @var list> */ public $after_classlike_checks = []; - /** @var list> */ - public $legacy_after_classlike_checks = []; /** * Static methods to be called after classlikes have been scanned @@ -141,8 +114,6 @@ class EventDispatcher * @var list> */ private $after_visit_classlikes = []; - /** @var list> */ - private $legacy_after_visit_classlikes = []; /** * Static methods to be called after codebase has been populated @@ -150,8 +121,6 @@ class EventDispatcher * @var list> */ public $after_codebase_populated = []; - /** @var list> */ - public $legacy_after_codebase_populated = []; /** * Static methods to be called after codebase has been populated @@ -159,8 +128,6 @@ class EventDispatcher * @var list> */ public $after_analysis = []; - /** @var list> */ - public $legacy_after_analysis = []; /** * Static methods to be called after a file has been analyzed @@ -168,8 +135,6 @@ class EventDispatcher * @var list> */ public $after_file_checks = []; - /** @var list> */ - public $legacy_after_file_checks = []; /** * Static methods to be called before a file is analyzed @@ -177,8 +142,6 @@ class EventDispatcher * @var list> */ public $before_file_checks = []; - /** @var list> */ - public $legacy_before_file_checks = []; /** * Static methods to be called after functionlike checks have completed @@ -186,8 +149,6 @@ class EventDispatcher * @var list> */ public $after_functionlike_checks = []; - /** @var list> */ - public $legacy_after_functionlike_checks = []; /** * Static methods to be called to see if taints should be added @@ -208,87 +169,59 @@ class EventDispatcher */ public function registerClass(string $class): void { - if (is_subclass_of($class, LegacyAfterMethodCallAnalysisInterface::class)) { - $this->legacy_after_method_checks[] = $class; - } elseif (is_subclass_of($class, AfterMethodCallAnalysisInterface::class)) { + if (is_subclass_of($class, AfterMethodCallAnalysisInterface::class)) { $this->after_method_checks[] = $class; } - if (is_subclass_of($class, LegacyAfterFunctionCallAnalysisInterface::class)) { - $this->legacy_after_function_checks[] = $class; - } elseif (is_subclass_of($class, AfterFunctionCallAnalysisInterface::class)) { + if (is_subclass_of($class, AfterFunctionCallAnalysisInterface::class)) { $this->after_function_checks[] = $class; } - if (is_subclass_of($class, LegacyAfterEveryFunctionCallAnalysisInterface::class)) { - $this->legacy_after_every_function_checks[] = $class; - } elseif (is_subclass_of($class, AfterEveryFunctionCallAnalysisInterface::class)) { + if (is_subclass_of($class, AfterEveryFunctionCallAnalysisInterface::class)) { $this->after_every_function_checks[] = $class; } - if (is_subclass_of($class, LegacyAfterExpressionAnalysisInterface::class)) { - $this->legacy_after_expression_checks[] = $class; - } elseif (is_subclass_of($class, AfterExpressionAnalysisInterface::class)) { + if (is_subclass_of($class, AfterExpressionAnalysisInterface::class)) { $this->after_expression_checks[] = $class; } - if (is_subclass_of($class, LegacyAfterStatementAnalysisInterface::class)) { - $this->legacy_after_statement_checks[] = $class; - } elseif (is_subclass_of($class, AfterStatementAnalysisInterface::class)) { + if (is_subclass_of($class, AfterStatementAnalysisInterface::class)) { $this->after_statement_checks[] = $class; } - if (is_subclass_of($class, LegacyStringInterpreterInterface::class)) { - $this->legacy_string_interpreters[] = $class; - } elseif (is_subclass_of($class, StringInterpreterInterface::class)) { + if (is_subclass_of($class, StringInterpreterInterface::class)) { $this->string_interpreters[] = $class; } - if (is_subclass_of($class, LegacyAfterClassLikeExistenceCheckInterface::class)) { - $this->legacy_after_classlike_exists_checks[] = $class; - } elseif (is_subclass_of($class, AfterClassLikeExistenceCheckInterface::class)) { + if (is_subclass_of($class, AfterClassLikeExistenceCheckInterface::class)) { $this->after_classlike_exists_checks[] = $class; } - if (is_subclass_of($class, LegacyAfterClassLikeAnalysisInterface::class)) { - $this->legacy_after_classlike_checks[] = $class; - } elseif (is_subclass_of($class, AfterClassLikeAnalysisInterface::class)) { + if (is_subclass_of($class, AfterClassLikeAnalysisInterface::class)) { $this->after_classlike_checks[] = $class; } - if (is_subclass_of($class, LegacyAfterClassLikeVisitInterface::class)) { - $this->legacy_after_visit_classlikes[] = $class; - } elseif (is_subclass_of($class, AfterClassLikeVisitInterface::class)) { + if (is_subclass_of($class, AfterClassLikeVisitInterface::class)) { $this->after_visit_classlikes[] = $class; } - if (is_subclass_of($class, LegacyAfterCodebasePopulatedInterface::class)) { - $this->legacy_after_codebase_populated[] = $class; - } elseif (is_subclass_of($class, AfterCodebasePopulatedInterface::class)) { + if (is_subclass_of($class, AfterCodebasePopulatedInterface::class)) { $this->after_codebase_populated[] = $class; } - if (is_subclass_of($class, LegacyAfterAnalysisInterface::class)) { - $this->legacy_after_analysis[] = $class; - } elseif (is_subclass_of($class, AfterAnalysisInterface::class)) { + if (is_subclass_of($class, AfterAnalysisInterface::class)) { $this->after_analysis[] = $class; } - if (is_subclass_of($class, LegacyAfterFileAnalysisInterface::class)) { - $this->legacy_after_file_checks[] = $class; - } elseif (is_subclass_of($class, AfterFileAnalysisInterface::class)) { + if (is_subclass_of($class, AfterFileAnalysisInterface::class)) { $this->after_file_checks[] = $class; } - if (is_subclass_of($class, LegacyBeforeFileAnalysisInterface::class)) { - $this->legacy_before_file_checks[] = $class; - } elseif (is_subclass_of($class, BeforeFileAnalysisInterface::class)) { + if (is_subclass_of($class, BeforeFileAnalysisInterface::class)) { $this->before_file_checks[] = $class; } - if (is_subclass_of($class, LegacyAfterFunctionLikeAnalysisInterface::class)) { - $this->legacy_after_functionlike_checks[] = $class; - } elseif (is_subclass_of($class, AfterFunctionLikeAnalysisInterface::class)) { + if (is_subclass_of($class, AfterFunctionLikeAnalysisInterface::class)) { $this->after_functionlike_checks[] = $class; } @@ -303,7 +236,7 @@ public function registerClass(string $class): void public function hasAfterMethodCallAnalysisHandlers(): bool { - return count($this->after_method_checks) || count($this->legacy_after_method_checks); + return count($this->after_method_checks) > 0; } public function dispatchAfterMethodCallAnalysis(AfterMethodCallAnalysisEvent $event): void @@ -311,24 +244,6 @@ public function dispatchAfterMethodCallAnalysis(AfterMethodCallAnalysisEvent $ev foreach ($this->after_method_checks as $handler) { $handler::afterMethodCallAnalysis($event); } - - foreach ($this->legacy_after_method_checks as $handler) { - $file_replacements = $event->getFileReplacements(); - $return_type_candidate = $event->getReturnTypeCandidate(); - $handler::afterMethodCallAnalysis( - $event->getExpr(), - $event->getMethodId(), - $event->getAppearingMethodId(), - $event->getDeclaringMethodId(), - $event->getContext(), - $event->getStatementsSource(), - $event->getCodebase(), - $file_replacements, - $return_type_candidate - ); - $event->setFileReplacements($file_replacements); - $event->setReturnTypeCandidate($return_type_candidate); - } } public function dispatchAfterFunctionCallAnalysis(AfterFunctionCallAnalysisEvent $event): void @@ -336,20 +251,6 @@ public function dispatchAfterFunctionCallAnalysis(AfterFunctionCallAnalysisEvent foreach ($this->after_function_checks as $handler) { $handler::afterFunctionCallAnalysis($event); } - - foreach ($this->legacy_after_function_checks as $handler) { - $file_replacements = $event->getFileReplacements(); - $handler::afterFunctionCallAnalysis( - $event->getExpr(), - $event->getFunctionId(), - $event->getContext(), - $event->getStatementsSource(), - $event->getCodebase(), - $event->getReturnTypeCandidate(), - $file_replacements - ); - $event->setFileReplacements($file_replacements); - } } public function dispatchAfterEveryFunctionCallAnalysis(AfterEveryFunctionCallAnalysisEvent $event): void @@ -357,16 +258,6 @@ public function dispatchAfterEveryFunctionCallAnalysis(AfterEveryFunctionCallAna foreach ($this->after_every_function_checks as $handler) { $handler::afterEveryFunctionCallAnalysis($event); } - - foreach ($this->legacy_after_every_function_checks as $handler) { - $handler::afterEveryFunctionCallAnalysis( - $event->getExpr(), - $event->getFunctionId(), - $event->getContext(), - $event->getStatementsSource(), - $event->getCodebase() - ); - } } public function dispatchAfterExpressionAnalysis(AfterExpressionAnalysisEvent $event): ?bool @@ -377,20 +268,6 @@ public function dispatchAfterExpressionAnalysis(AfterExpressionAnalysisEvent $ev } } - foreach ($this->legacy_after_expression_checks as $handler) { - $file_replacements = $event->getFileReplacements(); - if ($handler::afterExpressionAnalysis( - $event->getExpr(), - $event->getContext(), - $event->getStatementsSource(), - $event->getCodebase(), - $file_replacements - ) === false) { - return false; - } - $event->setFileReplacements($file_replacements); - } - return null; } @@ -402,20 +279,6 @@ public function dispatchAfterStatementAnalysis(AfterStatementAnalysisEvent $even } } - foreach ($this->legacy_after_statement_checks as $handler) { - $file_replacements = $event->getFileReplacements(); - if ($handler::afterStatementAnalysis( - $event->getStmt(), - $event->getContext(), - $event->getStatementsSource(), - $event->getCodebase(), - $file_replacements - ) === false) { - return false; - } - $event->setFileReplacements($file_replacements); - } - return null; } @@ -427,12 +290,6 @@ public function dispatchStringInterpreter(StringInterpreterEvent $event): ?TLite } } - foreach ($this->legacy_string_interpreters as $handler) { - if ($type = $handler::getTypeFromValue($event->getValue())) { - return $type; - } - } - return null; } @@ -441,18 +298,6 @@ public function dispatchAfterClassLikeExistenceCheck(AfterClassLikeExistenceChec foreach ($this->after_classlike_exists_checks as $handler) { $handler::afterClassLikeExistenceCheck($event); } - - foreach ($this->legacy_after_classlike_exists_checks as $handler) { - $file_replacements = $event->getFileReplacements(); - $handler::afterClassLikeExistenceCheck( - $event->getFqClassName(), - $event->getCodeLocation(), - $event->getStatementsSource(), - $event->getCodebase(), - $file_replacements - ); - $event->setFileReplacements($file_replacements); - } } public function dispatchAfterClassLikeAnalysis(AfterClassLikeAnalysisEvent $event): ?bool @@ -463,26 +308,12 @@ public function dispatchAfterClassLikeAnalysis(AfterClassLikeAnalysisEvent $even } } - foreach ($this->legacy_after_classlike_checks as $handler) { - $file_replacements = $event->getFileReplacements(); - if ($handler::afterStatementAnalysis( - $event->getStmt(), - $event->getClasslikeStorage(), - $event->getStatementsSource(), - $event->getCodebase(), - $file_replacements - ) === false) { - return false; - } - $event->setFileReplacements($file_replacements); - } - return null; } public function hasAfterClassLikeVisitHandlers(): bool { - return count($this->after_visit_classlikes) || count($this->legacy_after_visit_classlikes); + return count($this->after_visit_classlikes) > 0; } public function dispatchAfterClassLikeVisit(AfterClassLikeVisitEvent $event): void @@ -490,18 +321,6 @@ public function dispatchAfterClassLikeVisit(AfterClassLikeVisitEvent $event): vo foreach ($this->after_visit_classlikes as $handler) { $handler::afterClassLikeVisit($event); } - - foreach ($this->legacy_after_visit_classlikes as $handler) { - $file_replacements = $event->getFileReplacements(); - $handler::afterClassLikeVisit( - $event->getStmt(), - $event->getStorage(), - $event->getStatementsSource(), - $event->getCodebase(), - $file_replacements - ); - $event->setFileReplacements($file_replacements); - } } public function dispatchAfterCodebasePopulated(AfterCodebasePopulatedEvent $event): void @@ -509,12 +328,6 @@ public function dispatchAfterCodebasePopulated(AfterCodebasePopulatedEvent $even foreach ($this->after_codebase_populated as $handler) { $handler::afterCodebasePopulated($event); } - - foreach ($this->legacy_after_codebase_populated as $handler) { - $handler::afterCodebasePopulated( - $event->getCodebase() - ); - } } public function dispatchAfterAnalysis(AfterAnalysisEvent $event): void @@ -522,16 +335,6 @@ public function dispatchAfterAnalysis(AfterAnalysisEvent $event): void foreach ($this->after_analysis as $handler) { $handler::afterAnalysis($event); } - - foreach ($this->legacy_after_analysis as $handler) { - /** @psalm-suppress MixedArgumentTypeCoercion due to Psalm bug */ - $handler::afterAnalysis( - $event->getCodebase(), - $event->getIssues(), - $event->getBuildInfo(), - $event->getSourceControlInfo() - ); - } } public function dispatchAfterFileAnalysis(AfterFileAnalysisEvent $event): void @@ -539,15 +342,6 @@ public function dispatchAfterFileAnalysis(AfterFileAnalysisEvent $event): void foreach ($this->after_file_checks as $handler) { $handler::afterAnalyzeFile($event); } - - foreach ($this->legacy_after_file_checks as $handler) { - $handler::afterAnalyzeFile( - $event->getStatementsSource(), - $event->getFileContext(), - $event->getFileStorage(), - $event->getCodebase() - ); - } } public function dispatchBeforeFileAnalysis(BeforeFileAnalysisEvent $event): void @@ -555,15 +349,6 @@ public function dispatchBeforeFileAnalysis(BeforeFileAnalysisEvent $event): void foreach ($this->before_file_checks as $handler) { $handler::beforeAnalyzeFile($event); } - - foreach ($this->legacy_before_file_checks as $handler) { - $handler::beforeAnalyzeFile( - $event->getStatementsSource(), - $event->getFileContext(), - $event->getFileStorage(), - $event->getCodebase() - ); - } } public function dispatchAfterFunctionLikeAnalysis(AfterFunctionLikeAnalysisEvent $event): ?bool @@ -574,20 +359,6 @@ public function dispatchAfterFunctionLikeAnalysis(AfterFunctionLikeAnalysisEvent } } - foreach ($this->legacy_after_functionlike_checks as $handler) { - $file_replacements = $event->getFileReplacements(); - if ($handler::afterStatementAnalysis( - $event->getStmt(), - $event->getClasslikeStorage(), - $event->getStatementsSource(), - $event->getCodebase(), - $file_replacements - ) === false) { - return false; - } - $event->setFileReplacements($file_replacements); - } - return null; } diff --git a/src/Psalm/Internal/ExecutionEnvironment/BuildInfoCollector.php b/src/Psalm/Internal/ExecutionEnvironment/BuildInfoCollector.php index 858e7f4b031..25d731dcc96 100644 --- a/src/Psalm/Internal/ExecutionEnvironment/BuildInfoCollector.php +++ b/src/Psalm/Internal/ExecutionEnvironment/BuildInfoCollector.php @@ -12,10 +12,14 @@ use function strpos; use function strtotime; +use const JSON_THROW_ON_ERROR; + /** * Environment variables collector for CI environment. * * @author Kitamura Satoshi + * + * @internal */ class BuildInfoCollector { @@ -282,7 +286,7 @@ protected function fillGithubActions(): BuildInfoCollector if (isset($this->env['GITHUB_EVENT_PATH'])) { $event_json = file_get_contents((string) $this->env['GITHUB_EVENT_PATH']); /** @var array */ - $event_data = json_decode($event_json, true); + $event_data = json_decode($event_json, true, 512, JSON_THROW_ON_ERROR); if (isset($event_data['head_commit'])) { /** diff --git a/src/Psalm/Internal/ExecutionEnvironment/GitInfoCollector.php b/src/Psalm/Internal/ExecutionEnvironment/GitInfoCollector.php index e78212859d4..68d3ff77eed 100644 --- a/src/Psalm/Internal/ExecutionEnvironment/GitInfoCollector.php +++ b/src/Psalm/Internal/ExecutionEnvironment/GitInfoCollector.php @@ -19,6 +19,8 @@ * Git repository info collector. * * @author Kitamura Satoshi + * + * @internal */ class GitInfoCollector { diff --git a/src/Psalm/Internal/FileManipulation/CodeMigration.php b/src/Psalm/Internal/FileManipulation/CodeMigration.php index 086af29a667..83e3c79ceed 100644 --- a/src/Psalm/Internal/FileManipulation/CodeMigration.php +++ b/src/Psalm/Internal/FileManipulation/CodeMigration.php @@ -4,6 +4,8 @@ /** * @psalm-immutable + * + * @internal */ class CodeMigration { diff --git a/src/Psalm/Internal/FileManipulation/FunctionDocblockManipulator.php b/src/Psalm/Internal/FileManipulation/FunctionDocblockManipulator.php index 147ba4e7ccf..3e8bbe7ae6d 100644 --- a/src/Psalm/Internal/FileManipulation/FunctionDocblockManipulator.php +++ b/src/Psalm/Internal/FileManipulation/FunctionDocblockManipulator.php @@ -300,7 +300,7 @@ public function setParamType( string $new_type, string $phpdoc_type ): void { - $new_type = str_replace(['', '', ''], '', $new_type); + $new_type = str_replace(['', '', ''], '', $new_type); if ($php_type === 'static') { $php_type = ''; diff --git a/src/Psalm/Internal/FileManipulation/PropertyDocblockManipulator.php b/src/Psalm/Internal/FileManipulation/PropertyDocblockManipulator.php index 0ee9654a462..a8d560758b8 100644 --- a/src/Psalm/Internal/FileManipulation/PropertyDocblockManipulator.php +++ b/src/Psalm/Internal/FileManipulation/PropertyDocblockManipulator.php @@ -154,7 +154,7 @@ public function setType( bool $is_php_compatible, ?string $description = null ): void { - $new_type = str_replace(['', '', ''], '', $new_type); + $new_type = str_replace(['', '', ''], '', $new_type); $this->new_php_type = $php_type; $this->new_phpdoc_type = $phpdoc_type; diff --git a/src/Psalm/Internal/Fork/ForkProcessDoneMessage.php b/src/Psalm/Internal/Fork/ForkProcessDoneMessage.php index edda8ca1d97..4f955b2bde9 100644 --- a/src/Psalm/Internal/Fork/ForkProcessDoneMessage.php +++ b/src/Psalm/Internal/Fork/ForkProcessDoneMessage.php @@ -4,6 +4,8 @@ /** * @psalm-immutable + * + * @internal */ class ForkProcessDoneMessage implements ForkMessage { diff --git a/src/Psalm/Internal/Fork/ForkProcessErrorMessage.php b/src/Psalm/Internal/Fork/ForkProcessErrorMessage.php index 0794c9d83ee..26c620d5001 100644 --- a/src/Psalm/Internal/Fork/ForkProcessErrorMessage.php +++ b/src/Psalm/Internal/Fork/ForkProcessErrorMessage.php @@ -4,6 +4,8 @@ /** * @psalm-immutable + * + * @internal */ class ForkProcessErrorMessage implements ForkMessage { diff --git a/src/Psalm/Internal/Fork/ForkTaskDoneMessage.php b/src/Psalm/Internal/Fork/ForkTaskDoneMessage.php index 7c4322038ae..ccfba7ff107 100644 --- a/src/Psalm/Internal/Fork/ForkTaskDoneMessage.php +++ b/src/Psalm/Internal/Fork/ForkTaskDoneMessage.php @@ -4,6 +4,8 @@ /** * @psalm-immutable + * + * @internal */ class ForkTaskDoneMessage implements ForkMessage { diff --git a/src/Psalm/Internal/Fork/Pool.php b/src/Psalm/Internal/Fork/Pool.php index 3d0ad04ae02..e97604a68b5 100644 --- a/src/Psalm/Internal/Fork/Pool.php +++ b/src/Psalm/Internal/Fork/Pool.php @@ -67,6 +67,8 @@ * * Fork off to n-processes and divide up tasks between * each process. + * + * @internal */ class Pool { @@ -264,7 +266,7 @@ public function __construct( if ($bytes_written < $bytes_to_write) { // wait a bit - usleep(500000); + usleep(500_000); } } @@ -366,7 +368,7 @@ private function readResultsFromChildren(): array // For each stream that was ready, read the content. foreach ($needs_read as $file) { - $buffer = fread($file, 1024); + $buffer = fread($file, 1_024); if ($buffer !== false) { $content[(int)$file] .= $buffer; } diff --git a/src/Psalm/Internal/Fork/PsalmRestarter.php b/src/Psalm/Internal/Fork/PsalmRestarter.php index 0ca2e1ac9d4..4f55442b336 100644 --- a/src/Psalm/Internal/Fork/PsalmRestarter.php +++ b/src/Psalm/Internal/Fork/PsalmRestarter.php @@ -39,9 +39,7 @@ protected function requiresRestart($default): bool { $this->required = (bool) array_filter( $this->disabledExtensions, - function (string $extension): bool { - return extension_loaded($extension); - } + fn(string $extension): bool => extension_loaded($extension) ); return $default || $this->required; diff --git a/src/Psalm/Internal/IncludeCollector.php b/src/Psalm/Internal/IncludeCollector.php index 6cd24779ebb..617b2b72fb9 100644 --- a/src/Psalm/Internal/IncludeCollector.php +++ b/src/Psalm/Internal/IncludeCollector.php @@ -17,6 +17,8 @@ * Used to execute code that may cause file inclusions, and report what files have been included * NOTE: dependencies of this class should be kept at minimum, as it's used before autoloader is * registered. + * + * @internal */ final class IncludeCollector { diff --git a/src/Psalm/Internal/Json/Json.php b/src/Psalm/Internal/Json/Json.php index 2e2445fed53..ab35c3acd7f 100644 --- a/src/Psalm/Internal/Json/Json.php +++ b/src/Psalm/Internal/Json/Json.php @@ -13,6 +13,8 @@ /** * Provides ability of pretty printed JSON output. + * + * @internal */ class Json { diff --git a/src/Psalm/Internal/LanguageServer/Client/TextDocument.php b/src/Psalm/Internal/LanguageServer/Client/TextDocument.php index 26cd6462fb9..c30aa05ed07 100644 --- a/src/Psalm/Internal/LanguageServer/Client/TextDocument.php +++ b/src/Psalm/Internal/LanguageServer/Client/TextDocument.php @@ -16,6 +16,8 @@ /** * Provides method handlers for all textDocument/* methods + * + * @internal */ class TextDocument { diff --git a/src/Psalm/Internal/LanguageServer/IdGenerator.php b/src/Psalm/Internal/LanguageServer/IdGenerator.php index a7eee0fafab..ebac085c127 100644 --- a/src/Psalm/Internal/LanguageServer/IdGenerator.php +++ b/src/Psalm/Internal/LanguageServer/IdGenerator.php @@ -6,6 +6,8 @@ /** * Generates unique, incremental IDs for use as request IDs + * + * @internal */ class IdGenerator { diff --git a/src/Psalm/Internal/LanguageServer/LanguageServer.php b/src/Psalm/Internal/LanguageServer/LanguageServer.php index 6716f2024ac..b78e41d3a30 100644 --- a/src/Psalm/Internal/LanguageServer/LanguageServer.php +++ b/src/Psalm/Internal/LanguageServer/LanguageServer.php @@ -103,6 +103,11 @@ class LanguageServer extends Dispatcher */ protected $onchange_paths_to_analyze = []; + /** + * @var array> + */ + protected $current_issues = []; + public function __construct( ProtocolReader $reader, ProtocolWriter $writer, @@ -365,6 +370,7 @@ public function doAnalysis(): void public function emitIssues(array $uris): void { $data = IssueBuffer::clear(); + $this->current_issues = $data; foreach ($uris as $file_path => $uri) { $diagnostics = array_map( @@ -560,4 +566,14 @@ public static function uriToPath(string $uri): string return $filepath; } + + /** + * Get the value of current_issues + * + * @return array> + */ + public function getCurrentIssues(): array + { + return $this->current_issues; + } } diff --git a/src/Psalm/Internal/LanguageServer/ProtocolStreamReader.php b/src/Psalm/Internal/LanguageServer/ProtocolStreamReader.php index 68073df3af4..e1391b4c6eb 100644 --- a/src/Psalm/Internal/LanguageServer/ProtocolStreamReader.php +++ b/src/Psalm/Internal/LanguageServer/ProtocolStreamReader.php @@ -18,6 +18,8 @@ /** * Source: https://github.com/felixfbecker/php-language-server/tree/master/src/ProtocolStreamReader.php + * + * @internal */ class ProtocolStreamReader implements ProtocolReader { diff --git a/src/Psalm/Internal/LanguageServer/Server/TextDocument.php b/src/Psalm/Internal/LanguageServer/Server/TextDocument.php index fecbc070cb0..2d232906c4f 100644 --- a/src/Psalm/Internal/LanguageServer/Server/TextDocument.php +++ b/src/Psalm/Internal/LanguageServer/Server/TextDocument.php @@ -24,7 +24,6 @@ use Psalm\Exception\UnanalyzedFileException; use Psalm\Internal\Analyzer\ProjectAnalyzer; use Psalm\Internal\LanguageServer\LanguageServer; -use Psalm\IssueBuffer; use UnexpectedValueException; use function array_combine; @@ -36,6 +35,8 @@ /** * Provides method handlers for all textDocument/* methods + * + * @internal */ class TextDocument { @@ -262,7 +263,7 @@ public function hover(TextDocumentIdentifier $textDocument, Position $position): * * @param TextDocumentIdentifier $textDocument The text document * @param Position $position The position - * @psalm-return Promise>|Promise + * @psalm-return Promise>|Promise */ public function completion(TextDocumentIdentifier $textDocument, Position $position): Promise { @@ -365,9 +366,15 @@ public function codeAction(TextDocumentIdentifier $textDocument, Range $range): $this->codebase->analyzer->addFilesToAnalyze( array_combine($all_file_paths_to_analyze, $all_file_paths_to_analyze) ); - $this->codebase->analyzer->analyzeFiles($this->project_analyzer, 1, false); - $issues = IssueBuffer::clear(); + try { + $this->codebase->analyzer->analyzeFiles($this->project_analyzer, 1, false); + } catch (UnexpectedValueException $e) { + error_log('codeAction errored on file ' . $file_path. ', Reason: '.$e->getMessage()); + return new Success(null); + } + + $issues = $this->server->getCurrentIssues(); if (empty($issues[$file_path])) { return new Success(null); diff --git a/src/Psalm/Internal/LanguageServer/Server/Workspace.php b/src/Psalm/Internal/LanguageServer/Server/Workspace.php index c1bc2bd7073..8af7f1cada0 100644 --- a/src/Psalm/Internal/LanguageServer/Server/Workspace.php +++ b/src/Psalm/Internal/LanguageServer/Server/Workspace.php @@ -12,6 +12,8 @@ /** * Provides method handlers for all workspace/* methods + * + * @internal */ class Workspace { diff --git a/src/Psalm/Internal/MethodIdentifier.php b/src/Psalm/Internal/MethodIdentifier.php index cf4c81ba63d..b4336ec3a06 100644 --- a/src/Psalm/Internal/MethodIdentifier.php +++ b/src/Psalm/Internal/MethodIdentifier.php @@ -12,6 +12,8 @@ /** * @psalm-immutable + * + * @internal */ class MethodIdentifier { diff --git a/src/Psalm/Internal/PhpTraverser/CustomTraverser.php b/src/Psalm/Internal/PhpTraverser/CustomTraverser.php index 2c96c6b1c9f..2f8f6a44d1f 100644 --- a/src/Psalm/Internal/PhpTraverser/CustomTraverser.php +++ b/src/Psalm/Internal/PhpTraverser/CustomTraverser.php @@ -164,7 +164,7 @@ protected function traverseArray(array $nodes): array } if (!empty($doNodes)) { - while (list($i, $replace) = array_pop($doNodes)) { + while ([$i, $replace] = array_pop($doNodes)) { array_splice($nodes, $i, 1, $replace); } } diff --git a/src/Psalm/Internal/PhpVisitor/CloningVisitor.php b/src/Psalm/Internal/PhpVisitor/CloningVisitor.php index befd1d1adfe..5ea8d9430ec 100644 --- a/src/Psalm/Internal/PhpVisitor/CloningVisitor.php +++ b/src/Psalm/Internal/PhpVisitor/CloningVisitor.php @@ -14,6 +14,8 @@ * Visitor cloning all nodes and linking to the original nodes using an attribute. * * This visitor is required to perform format-preserving pretty prints. + * + * @internal */ class CloningVisitor extends NodeVisitorAbstract { @@ -27,9 +29,7 @@ public function enterNode(Node $node): Node /** * @return Comment */ - function (Comment $c): Comment { - return clone $c; - }, + fn(Comment $c): Comment => clone $c, $cs ) ); diff --git a/src/Psalm/Internal/PhpVisitor/ConditionCloningVisitor.php b/src/Psalm/Internal/PhpVisitor/ConditionCloningVisitor.php index c368f377b00..6adde5b60f5 100644 --- a/src/Psalm/Internal/PhpVisitor/ConditionCloningVisitor.php +++ b/src/Psalm/Internal/PhpVisitor/ConditionCloningVisitor.php @@ -9,6 +9,9 @@ use PhpParser\NodeVisitorAbstract; use Psalm\Internal\Provider\NodeDataProvider; +/** + * @internal + */ class ConditionCloningVisitor extends NodeVisitorAbstract { private $type_provider; diff --git a/src/Psalm/Internal/PhpVisitor/OffsetShifterVisitor.php b/src/Psalm/Internal/PhpVisitor/OffsetShifterVisitor.php index 7e3de329316..18c680054ef 100644 --- a/src/Psalm/Internal/PhpVisitor/OffsetShifterVisitor.php +++ b/src/Psalm/Internal/PhpVisitor/OffsetShifterVisitor.php @@ -6,6 +6,8 @@ /** * Shifts all nodes in a given AST by a set amount + * + * @internal */ class OffsetShifterVisitor extends PhpParser\NodeVisitorAbstract { diff --git a/src/Psalm/Internal/PhpVisitor/PartialParserVisitor.php b/src/Psalm/Internal/PhpVisitor/PartialParserVisitor.php index 9734be22738..c432272dbba 100644 --- a/src/Psalm/Internal/PhpVisitor/PartialParserVisitor.php +++ b/src/Psalm/Internal/PhpVisitor/PartialParserVisitor.php @@ -25,6 +25,8 @@ /** * Given a list of file diffs, this scans an AST to find the sections it can replace, and parses * just those methods. + * + * @internal */ class PartialParserVisitor extends PhpParser\NodeVisitorAbstract { diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/AttributeResolver.php b/src/Psalm/Internal/PhpVisitor/Reflector/AttributeResolver.php index 9caf12d78c3..16668569308 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/AttributeResolver.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/AttributeResolver.php @@ -17,6 +17,9 @@ use function strtolower; +/** + * @internal + */ class AttributeResolver { public static function resolve( diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeDocblockParser.php b/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeDocblockParser.php index d2bb03ce945..cc913806c19 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeDocblockParser.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeDocblockParser.php @@ -422,7 +422,7 @@ public static function parse( $statements = StatementsProvider::parseStatements( $php_string, - $codebase->php_major_version . '.' . $codebase->php_minor_version, + $codebase->analysis_php_version_id, $has_errors ); } catch (Exception $e) { diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php b/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php index 476cb781eef..9dffdfe8026 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php @@ -84,6 +84,9 @@ use const PREG_SPLIT_DELIM_CAPTURE; use const PREG_SPLIT_NO_EMPTY; +/** + * @internal + */ class ClassLikeNodeScanner { /** @@ -426,9 +429,7 @@ public function start(PhpParser\Node\Stmt\ClassLike $node): ?bool usort( $docblock_info->templates, - function (array $l, array $r): int { - return $l[4] > $r[4] ? 1 : -1; - } + fn(array $l, array $r): int => $l[4] > $r[4] ? 1 : -1 ); foreach ($docblock_info->templates as $i => $template_map) { @@ -1495,8 +1496,7 @@ private function visitPropertyDeclaration( $this->file_storage, $this->storage, $this->aliases, - $this->codebase->php_major_version, - $this->codebase->php_minor_version + $this->codebase->analysis_php_version_id ); $signature_type_location = new CodeLocation( diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/ExpressionResolver.php b/src/Psalm/Internal/PhpVisitor/Reflector/ExpressionResolver.php index 3cf9d02950d..2d80914b502 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/ExpressionResolver.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/ExpressionResolver.php @@ -37,6 +37,9 @@ use function interface_exists; use function strtolower; +/** + * @internal + */ class ExpressionResolver { public static function getUnresolvedClassConstExpr( @@ -340,7 +343,7 @@ public static function enterConditional( ) ) ) { - $php_version_id = $codebase->php_major_version * 10000 + $codebase->php_minor_version * 100; + $php_version_id = $codebase->analysis_php_version_id; $evaluator = new ConstExprEvaluator(function (Expr $expr) use ($php_version_id) { if ($expr instanceof ConstFetch && $expr->name->parts === ['PHP_VERSION_ID']) { return $php_version_id; diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/ExpressionScanner.php b/src/Psalm/Internal/PhpVisitor/Reflector/ExpressionScanner.php index 2a6dbf71eb9..42af2598ecc 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/ExpressionScanner.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/ExpressionScanner.php @@ -32,6 +32,9 @@ use const DIRECTORY_SEPARATOR; +/** + * @internal + */ class ExpressionScanner { public static function scan( diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockParser.php b/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockParser.php index f703d7da82d..07c3f4ebb71 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockParser.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockParser.php @@ -29,6 +29,9 @@ use function substr_count; use function trim; +/** + * @internal + */ class FunctionLikeDocblockParser { /** diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockScanner.php b/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockScanner.php index acab93a80f4..d7dad776000 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockScanner.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockScanner.php @@ -54,6 +54,9 @@ use function substr; use function trim; +/** + * @internal + */ class FunctionLikeDocblockScanner { /** @@ -274,9 +277,7 @@ public static function addDocblockInfo( if ($storage instanceof MethodStorage) { $storage->has_docblock_param_types = (bool) array_filter( $storage->params, - function (FunctionLikeParameter $p): bool { - return $p->type !== null && $p->has_docblock_type; - } + fn(FunctionLikeParameter $p): bool => $p->type !== null && $p->has_docblock_type ); } @@ -863,9 +864,7 @@ private static function improveParamsFromDocblock( $params_without_docblock_type = array_filter( $storage->params, - function (FunctionLikeParameter $p): bool { - return !$p->has_docblock_type && (!$p->type || $p->type->hasArray()); - } + fn(FunctionLikeParameter $p): bool => !$p->has_docblock_type && (!$p->type || $p->type->hasArray()) ); if ($params_without_docblock_type) { diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeNodeScanner.php b/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeNodeScanner.php index de938d517b7..5abb9b7cc98 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeNodeScanner.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeNodeScanner.php @@ -64,6 +64,9 @@ use function strpos; use function strtolower; +/** + * @internal + */ class FunctionLikeNodeScanner { /** @@ -300,7 +303,6 @@ public function start(PhpParser\Node\FunctionLike $stmt, bool $fake_method = fal $final_actions = ScopeAnalyzer::getControlActions( $function_stmt->stmts, null, - $this->config->exit_functions, [], false ); @@ -434,8 +436,7 @@ public function start(PhpParser\Node\FunctionLike $stmt, bool $fake_method = fal $this->file_storage, $this->classlike_storage, $this->aliases, - $this->codebase->php_major_version, - $this->codebase->php_minor_version + $this->codebase->analysis_php_version_id ); $storage->return_type_location = new CodeLocation( @@ -486,12 +487,14 @@ public function start(PhpParser\Node\FunctionLike $stmt, bool $fake_method = fal if ($docblock_info) { if ($docblock_info->since_php_major_version && !$this->aliases->namespace) { - if ($docblock_info->since_php_major_version > $this->codebase->php_major_version) { + $analysis_major_php_version = $this->codebase->getMajorAnalysisPhpVersion(); + $analysis_minor_php_version = $this->codebase->getMajorAnalysisPhpVersion(); + if ($docblock_info->since_php_major_version > $analysis_major_php_version) { return false; } - if ($docblock_info->since_php_major_version === $this->codebase->php_major_version - && $docblock_info->since_php_minor_version > $this->codebase->php_minor_version + if ($docblock_info->since_php_major_version === $analysis_major_php_version + && $docblock_info->since_php_minor_version > $analysis_minor_php_version ) { return false; } @@ -828,8 +831,7 @@ private function getTranslatedFunctionParam( $this->file_storage, $this->classlike_storage, $this->aliases, - $this->codebase->php_major_version, - $this->codebase->php_minor_version + $this->codebase->analysis_php_version_id ); if ($is_nullable) { @@ -1043,11 +1045,14 @@ private function createStorageForFunctionLike( } if ($docblock_info) { if ($docblock_info->since_php_major_version && !$this->aliases->namespace) { - if ($docblock_info->since_php_major_version > $this->codebase->php_major_version) { + $analysis_major_php_version = $this->codebase->getMajorAnalysisPhpVersion(); + $analysis_minor_php_version = $this->codebase->getMajorAnalysisPhpVersion(); + if ($docblock_info->since_php_major_version > $analysis_major_php_version) { return false; } - if ($docblock_info->since_php_major_version === $this->codebase->php_major_version - && $docblock_info->since_php_minor_version > $this->codebase->php_minor_version + + if ($docblock_info->since_php_major_version === $analysis_major_php_version + && $docblock_info->since_php_minor_version > $analysis_minor_php_version ) { return false; } @@ -1072,7 +1077,7 @@ private function createStorageForFunctionLike( if ($method_name_lc === strtolower($class_name) && !isset($classlike_storage->methods['__construct']) && strpos($fq_classlike_name, '\\') === false - && $this->codebase->php_major_version < 8 + && $this->codebase->analysis_php_version_id <= 7_04_00 ) { $this->codebase->methods->setDeclaringMethodId( $fq_classlike_name, diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/TypeHintResolver.php b/src/Psalm/Internal/PhpVisitor/Reflector/TypeHintResolver.php index 3dfb0859075..e594ac9afb1 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/TypeHintResolver.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/TypeHintResolver.php @@ -16,6 +16,9 @@ use function implode; use function strtolower; +/** + * @internal + */ class TypeHintResolver { /** @@ -27,8 +30,7 @@ public static function resolve( FileStorage $file_storage, ?ClassLikeStorage $classlike_storage, Aliases $aliases, - int $php_major_version, - int $php_minor_version + int $analysis_php_version_id ): Union { if ($hint instanceof PhpParser\Node\UnionType) { $type = null; @@ -44,8 +46,7 @@ public static function resolve( $file_storage, $classlike_storage, $aliases, - $php_major_version, - $php_minor_version + $analysis_php_version_id ); $type = Type::combineUnionTypes($resolved_type, $type); @@ -93,7 +94,7 @@ public static function resolve( $type = Type::parseString( $fq_type_string, - [$php_major_version, $php_minor_version], + $analysis_php_version_id, [] ); diff --git a/src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php b/src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php index e99259cfca0..85fa7351967 100644 --- a/src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php +++ b/src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php @@ -237,7 +237,7 @@ public function enterNode(PhpParser\Node $node): ?int $this->functionlike_node_scanners[] = $functionlike_node_scanner; if ($classlike_storage - && $this->codebase->php_major_version >= 8 + && $this->codebase->analysis_php_version_id >= 8_00_00 && $node instanceof PhpParser\Node\Stmt\ClassMethod && strtolower($node->name->name) === '__tostring' ) { @@ -247,7 +247,7 @@ public function enterNode(PhpParser\Node $node): ?int $classlike_storage->class_implements['stringable'] = 'Stringable'; } - if (PHP_VERSION_ID >= 80000) { + if (PHP_VERSION_ID >= 8_00_00) { $this->codebase->scanner->queueClassLikeForScanning('Stringable'); } } diff --git a/src/Psalm/Internal/PhpVisitor/TraitFinder.php b/src/Psalm/Internal/PhpVisitor/TraitFinder.php index 93a05225c15..55d177e351d 100644 --- a/src/Psalm/Internal/PhpVisitor/TraitFinder.php +++ b/src/Psalm/Internal/PhpVisitor/TraitFinder.php @@ -14,6 +14,8 @@ /** * Given a list of file diffs, this scans an AST to find the sections it can replace, and parses * just those methods. + * + * @internal */ class TraitFinder extends PhpParser\NodeVisitorAbstract { diff --git a/src/Psalm/Internal/PhpVisitor/TypeMappingVisitor.php b/src/Psalm/Internal/PhpVisitor/TypeMappingVisitor.php index 05704e142ad..da987822342 100644 --- a/src/Psalm/Internal/PhpVisitor/TypeMappingVisitor.php +++ b/src/Psalm/Internal/PhpVisitor/TypeMappingVisitor.php @@ -8,6 +8,9 @@ use PhpParser\NodeVisitorAbstract; use Psalm\Internal\Provider\NodeDataProvider; +/** + * @internal + */ class TypeMappingVisitor extends NodeVisitorAbstract { private $fake_type_provider; diff --git a/src/Psalm/Internal/PluginManager/Command/ShowCommand.php b/src/Psalm/Internal/PluginManager/Command/ShowCommand.php index 50fc46492fe..ce72f1cda02 100644 --- a/src/Psalm/Internal/PluginManager/Command/ShowCommand.php +++ b/src/Psalm/Internal/PluginManager/Command/ShowCommand.php @@ -59,9 +59,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int /** * @return array{0: null|string, 1: string} */ - function (string $class, ?string $package): array { - return [$package, $class]; - }; + fn(string $class, ?string $package): array => [$package, $class]; $io->section('Enabled'); if (count($enabled)) { diff --git a/src/Psalm/Internal/PluginManager/ComposerLock.php b/src/Psalm/Internal/PluginManager/ComposerLock.php index fd5d251e9db..7511d14c3c1 100644 --- a/src/Psalm/Internal/PluginManager/ComposerLock.php +++ b/src/Psalm/Internal/PluginManager/ComposerLock.php @@ -12,6 +12,9 @@ use function json_last_error; use function json_last_error_msg; +/** + * @internal + */ class ComposerLock { /** @var string[] */ diff --git a/src/Psalm/Internal/PluginManager/ConfigFile.php b/src/Psalm/Internal/PluginManager/ConfigFile.php index daab9d8a320..501f48521cd 100644 --- a/src/Psalm/Internal/PluginManager/ConfigFile.php +++ b/src/Psalm/Internal/PluginManager/ConfigFile.php @@ -12,6 +12,9 @@ use function strpos; use function substr; +/** + * @internal + */ class ConfigFile { /** @var string */ diff --git a/src/Psalm/Internal/PluginManager/PluginList.php b/src/Psalm/Internal/PluginManager/PluginList.php index 7f5d90526e7..ddfdb248e8b 100644 --- a/src/Psalm/Internal/PluginManager/PluginList.php +++ b/src/Psalm/Internal/PluginManager/PluginList.php @@ -11,6 +11,9 @@ use function array_search; use function strpos; +/** + * @internal + */ class PluginList { /** @var null|ConfigFile */ diff --git a/src/Psalm/Internal/PluginManager/PluginListFactory.php b/src/Psalm/Internal/PluginManager/PluginListFactory.php index 0683365f72e..6ae81a32b8d 100644 --- a/src/Psalm/Internal/PluginManager/PluginListFactory.php +++ b/src/Psalm/Internal/PluginManager/PluginListFactory.php @@ -11,7 +11,11 @@ use function urlencode; use const DIRECTORY_SEPARATOR; +use const JSON_THROW_ON_ERROR; +/** + * @internal + */ class PluginListFactory { /** @var string */ @@ -68,7 +72,8 @@ private function findLockFiles(): array 'packages' => [], 'packages-dev' => [], ]; - $composer_lock_filenames[] = 'data:application/json,' . urlencode(json_encode($stub_composer_lock)); + $composer_lock_filenames[] = 'data:application/json,' + . urlencode(json_encode($stub_composer_lock, JSON_THROW_ON_ERROR)); } return $composer_lock_filenames; diff --git a/src/Psalm/Internal/Provider/AddRemoveTaints/HtmlFunctionTainter.php b/src/Psalm/Internal/Provider/AddRemoveTaints/HtmlFunctionTainter.php index d3328a5d128..19606c9aa40 100644 --- a/src/Psalm/Internal/Provider/AddRemoveTaints/HtmlFunctionTainter.php +++ b/src/Psalm/Internal/Provider/AddRemoveTaints/HtmlFunctionTainter.php @@ -13,6 +13,9 @@ use const ENT_QUOTES; +/** + * @internal + */ class HtmlFunctionTainter implements AddTaintsInterface, RemoveTaintsInterface { /** diff --git a/src/Psalm/Internal/Provider/FakeFileProvider.php b/src/Psalm/Internal/Provider/FakeFileProvider.php index 5d506bdeb2d..cdb513c6618 100644 --- a/src/Psalm/Internal/Provider/FakeFileProvider.php +++ b/src/Psalm/Internal/Provider/FakeFileProvider.php @@ -6,6 +6,9 @@ use function strpos; use function strtolower; +/** + * @internal + */ class FakeFileProvider extends FileProvider { /** diff --git a/src/Psalm/Internal/Provider/FileProvider.php b/src/Psalm/Internal/Provider/FileProvider.php index 6d85e1458e9..1296574b274 100644 --- a/src/Psalm/Internal/Provider/FileProvider.php +++ b/src/Psalm/Internal/Provider/FileProvider.php @@ -19,6 +19,9 @@ use const DIRECTORY_SEPARATOR; +/** + * @internal + */ class FileProvider { /** diff --git a/src/Psalm/Internal/Provider/FileReferenceCacheProvider.php b/src/Psalm/Internal/Provider/FileReferenceCacheProvider.php index 1d80b56f78e..3ac39b2bca6 100644 --- a/src/Psalm/Internal/Provider/FileReferenceCacheProvider.php +++ b/src/Psalm/Internal/Provider/FileReferenceCacheProvider.php @@ -18,10 +18,12 @@ use const DIRECTORY_SEPARATOR; /** - * @psalm-import-type FileMapType from Analyzer + * @psalm-import-type FileMapType from Analyzer * * Used to determine which files reference other files, necessary for using the --diff * option from the command line. + * + * @internal */ class FileReferenceCacheProvider { diff --git a/src/Psalm/Internal/Provider/FileReferenceProvider.php b/src/Psalm/Internal/Provider/FileReferenceProvider.php index 431726cae6a..ece9a2ac06e 100644 --- a/src/Psalm/Internal/Provider/FileReferenceProvider.php +++ b/src/Psalm/Internal/Provider/FileReferenceProvider.php @@ -21,6 +21,8 @@ * * Used to determine which files reference other files, necessary for using the --diff * option from the command line. + * + * @internal */ class FileReferenceProvider { @@ -184,9 +186,7 @@ public function getDeletedReferencedFiles(): array if (self::$deleted_files === null) { self::$deleted_files = array_filter( array_keys(self::$file_references), - function (string $file_name): bool { - return !file_exists($file_name); - } + fn(string $file_name): bool => !file_exists($file_name) ); } diff --git a/src/Psalm/Internal/Provider/FunctionExistenceProvider.php b/src/Psalm/Internal/Provider/FunctionExistenceProvider.php index 57c4a8c4f1f..17932060080 100644 --- a/src/Psalm/Internal/Provider/FunctionExistenceProvider.php +++ b/src/Psalm/Internal/Provider/FunctionExistenceProvider.php @@ -5,12 +5,14 @@ use Closure; use Psalm\Plugin\EventHandler\Event\FunctionExistenceProviderEvent; use Psalm\Plugin\EventHandler\FunctionExistenceProviderInterface; -use Psalm\Plugin\Hook\FunctionExistenceProviderInterface as LegacyFunctionExistenceProviderInterface; use Psalm\StatementsSource; use function is_subclass_of; use function strtolower; +/** + * @internal + */ class FunctionExistenceProvider { /** @@ -21,21 +23,9 @@ class FunctionExistenceProvider */ private static $handlers = []; - /** - * @var array< - * lowercase-string, - * array - * > - */ - private static $legacy_handlers = []; - public function __construct() { self::$handlers = []; - self::$legacy_handlers = []; } /** @@ -43,13 +33,7 @@ public function __construct() */ public function registerClass(string $class): void { - if (is_subclass_of($class, LegacyFunctionExistenceProviderInterface::class, true)) { - $callable = Closure::fromCallable([$class, 'doesFunctionExist']); - - foreach ($class::getFunctionIds() as $function_id) { - $this->registerLegacyClosure($function_id, $callable); - } - } elseif (is_subclass_of($class, FunctionExistenceProviderInterface::class, true)) { + if (is_subclass_of($class, FunctionExistenceProviderInterface::class, true)) { $callable = Closure::fromCallable([$class, 'doesFunctionExist']); foreach ($class::getFunctionIds() as $function_id) { @@ -67,39 +51,15 @@ public function registerClosure(string $function_id, Closure $c): void self::$handlers[$function_id][] = $c; } - /** - * @param lowercase-string $function_id - * @param Closure( - * StatementsSource, - * string - * ): ?bool $c - */ - public function registerLegacyClosure(string $function_id, Closure $c): void - { - self::$legacy_handlers[$function_id][] = $c; - } - public function has(string $function_id): bool { - return isset(self::$handlers[strtolower($function_id)]) || - isset(self::$legacy_handlers[strtolower($function_id)]); + return isset(self::$handlers[strtolower($function_id)]); } public function doesFunctionExist( StatementsSource $statements_source, string $function_id ): ?bool { - foreach (self::$legacy_handlers[strtolower($function_id)] ?? [] as $function_handler) { - $function_exists = $function_handler( - $statements_source, - $function_id - ); - - if ($function_exists !== null) { - return $function_exists; - } - } - foreach (self::$handlers[strtolower($function_id)] ?? [] as $function_handler) { $event = new FunctionExistenceProviderEvent( $statements_source, diff --git a/src/Psalm/Internal/Provider/FunctionParamsProvider.php b/src/Psalm/Internal/Provider/FunctionParamsProvider.php index d6b70c4b9d8..3cf7493ddcd 100644 --- a/src/Psalm/Internal/Provider/FunctionParamsProvider.php +++ b/src/Psalm/Internal/Provider/FunctionParamsProvider.php @@ -8,13 +8,14 @@ use Psalm\Context; use Psalm\Plugin\EventHandler\Event\FunctionParamsProviderEvent; use Psalm\Plugin\EventHandler\FunctionParamsProviderInterface; -use Psalm\Plugin\Hook\FunctionParamsProviderInterface as LegacyFunctionParamsProviderInterface; use Psalm\StatementsSource; use Psalm\Storage\FunctionLikeParameter; -use function is_subclass_of; use function strtolower; +/** + * @internal + */ class FunctionParamsProvider { /** @@ -25,43 +26,20 @@ class FunctionParamsProvider */ private static $handlers = []; - /** - * @var array< - * lowercase-string, - * array, - * ?Context=, - * ?CodeLocation= - * ): ?array> - * > - */ - private static $legacy_handlers = []; - public function __construct() { self::$handlers = []; - self::$legacy_handlers = []; } /** - * @param class-string|class-string $class + * @param class-string $class */ public function registerClass(string $class): void { - if (is_subclass_of($class, LegacyFunctionParamsProviderInterface::class, true)) { - $callable = Closure::fromCallable([$class, 'getFunctionParams']); + $callable = Closure::fromCallable([$class, 'getFunctionParams']); - foreach ($class::getFunctionIds() as $function_id) { - $this->registerLegacyClosure($function_id, $callable); - } - } elseif (is_subclass_of($class, FunctionParamsProviderInterface::class, true)) { - $callable = Closure::fromCallable([$class, 'getFunctionParams']); - - foreach ($class::getFunctionIds() as $function_id) { - $this->registerClosure($function_id, $callable); - } + foreach ($class::getFunctionIds() as $function_id) { + $this->registerClosure($function_id, $callable); } } @@ -73,24 +51,9 @@ public function registerClosure(string $fq_classlike_name, Closure $c): void self::$handlers[strtolower($fq_classlike_name)][] = $c; } - /** - * @param Closure( - * StatementsSource, - * string, - * list, - * ?Context=, - * ?CodeLocation= - * ): ?array $c - */ - public function registerLegacyClosure(string $fq_classlike_name, Closure $c): void - { - self::$legacy_handlers[strtolower($fq_classlike_name)][] = $c; - } - public function has(string $fq_classlike_name): bool { - return isset(self::$handlers[strtolower($fq_classlike_name)]) || - isset(self::$legacy_handlers[strtolower($fq_classlike_name)]); + return isset(self::$handlers[strtolower($fq_classlike_name)]); } /** @@ -120,20 +83,6 @@ public function getFunctionParams( } } - foreach (self::$legacy_handlers[strtolower($function_id)] ?? [] as $class_handler) { - $result = $class_handler( - $statements_source, - $function_id, - $call_args, - $context, - $code_location - ); - - if ($result) { - return $result; - } - } - return null; } } diff --git a/src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php b/src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php index 8b124ea5532..06390ab8069 100644 --- a/src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php @@ -4,7 +4,6 @@ use Closure; use PhpParser; -use PhpParser\Node\Arg; use Psalm\CodeLocation; use Psalm\Context; use Psalm\Internal\Provider\ReturnTypeProvider\ArrayChunkReturnTypeProvider; @@ -41,13 +40,15 @@ use Psalm\Internal\Provider\ReturnTypeProvider\VersionCompareReturnTypeProvider; use Psalm\Plugin\EventHandler\Event\FunctionReturnTypeProviderEvent; use Psalm\Plugin\EventHandler\FunctionReturnTypeProviderInterface; -use Psalm\Plugin\Hook\FunctionReturnTypeProviderInterface as LegacyFunctionReturnTypeProviderInterface; use Psalm\StatementsSource; use Psalm\Type\Union; use function is_subclass_of; use function strtolower; +/** + * @internal + */ class FunctionReturnTypeProvider { /** @@ -58,24 +59,9 @@ class FunctionReturnTypeProvider */ private static $handlers = []; - /** - * @var array< - * lowercase-string, - * array, - * Context, - * CodeLocation - * ): ?Union> - * > - */ - private static $legacy_handlers = []; - public function __construct() { self::$handlers = []; - self::$legacy_handlers = []; $this->registerClass(ArrayChunkReturnTypeProvider::class); $this->registerClass(ArrayColumnReturnTypeProvider::class); @@ -116,13 +102,7 @@ public function __construct() */ public function registerClass(string $class): void { - if (is_subclass_of($class, LegacyFunctionReturnTypeProviderInterface::class, true)) { - $callable = Closure::fromCallable([$class, 'getFunctionReturnType']); - - foreach ($class::getFunctionIds() as $function_id) { - $this->registerLegacyClosure($function_id, $callable); - } - } elseif (is_subclass_of($class, FunctionReturnTypeProviderInterface::class, true)) { + if (is_subclass_of($class, FunctionReturnTypeProviderInterface::class, true)) { $callable = Closure::fromCallable([$class, 'getFunctionReturnType']); foreach ($class::getFunctionIds() as $function_id) { @@ -140,25 +120,9 @@ public function registerClosure(string $function_id, Closure $c): void self::$handlers[$function_id][] = $c; } - /** - * @param lowercase-string $function_id - * @param Closure( - * StatementsSource, - * non-empty-string, - * list, - * Context, - * CodeLocation - * ): ?Union $c - */ - public function registerLegacyClosure(string $function_id, Closure $c): void - { - self::$legacy_handlers[$function_id][] = $c; - } - public function has(string $function_id): bool { - return isset(self::$handlers[strtolower($function_id)]) || - isset(self::$legacy_handlers[strtolower($function_id)]); + return isset(self::$handlers[strtolower($function_id)]); } /** @@ -171,20 +135,6 @@ public function getReturnType( Context $context, CodeLocation $code_location ): ?Union { - foreach (self::$legacy_handlers[strtolower($function_id)] ?? [] as $function_handler) { - $return_type = $function_handler( - $statements_source, - $function_id, - $stmt->getArgs(), - $context, - $code_location - ); - - if ($return_type) { - return $return_type; - } - } - foreach (self::$handlers[strtolower($function_id)] ?? [] as $function_handler) { $event = new FunctionReturnTypeProviderEvent( $statements_source, diff --git a/src/Psalm/Internal/Provider/MethodExistenceProvider.php b/src/Psalm/Internal/Provider/MethodExistenceProvider.php index 0f637728ca6..a2f45ae0706 100644 --- a/src/Psalm/Internal/Provider/MethodExistenceProvider.php +++ b/src/Psalm/Internal/Provider/MethodExistenceProvider.php @@ -6,12 +6,13 @@ use Psalm\CodeLocation; use Psalm\Plugin\EventHandler\Event\MethodExistenceProviderEvent; use Psalm\Plugin\EventHandler\MethodExistenceProviderInterface; -use Psalm\Plugin\Hook\MethodExistenceProviderInterface as LegacyMethodExistenceProviderInterface; use Psalm\StatementsSource; -use function is_subclass_of; use function strtolower; +/** + * @internal + */ class MethodExistenceProvider { /** @@ -22,42 +23,20 @@ class MethodExistenceProvider */ private static $handlers = []; - /** - * @var array< - * lowercase-string, - * array - * > - */ - private static $legacy_handlers = []; - public function __construct() { self::$handlers = []; - self::$legacy_handlers = []; } /** - * @param class-string|class-string $class + * @param class-string $class */ public function registerClass(string $class): void { - if (is_subclass_of($class, LegacyMethodExistenceProviderInterface::class, true)) { - $callable = Closure::fromCallable([$class, 'doesMethodExist']); + $callable = Closure::fromCallable([$class, 'doesMethodExist']); - foreach ($class::getClassLikeNames() as $fq_classlike_name) { - $this->registerLegacyClosure($fq_classlike_name, $callable); - } - } elseif (is_subclass_of($class, MethodExistenceProviderInterface::class, true)) { - $callable = Closure::fromCallable([$class, 'doesMethodExist']); - - foreach ($class::getClassLikeNames() as $fq_classlike_name) { - $this->registerClosure($fq_classlike_name, $callable); - } + foreach ($class::getClassLikeNames() as $fq_classlike_name) { + $this->registerClosure($fq_classlike_name, $callable); } } @@ -69,23 +48,9 @@ public function registerClosure(string $fq_classlike_name, Closure $c): void self::$handlers[strtolower($fq_classlike_name)][] = $c; } - /** - * @param Closure( - * string, - * string, - * ?StatementsSource=, - * ?CodeLocation - * ): ?bool $c - */ - public function registerLegacyClosure(string $fq_classlike_name, Closure $c): void - { - self::$legacy_handlers[strtolower($fq_classlike_name)][] = $c; - } - public function has(string $fq_classlike_name): bool { - return isset(self::$handlers[strtolower($fq_classlike_name)]) || - isset(self::$legacy_handlers[strtolower($fq_classlike_name)]); + return isset(self::$handlers[strtolower($fq_classlike_name)]); } public function doesMethodExist( @@ -94,19 +59,6 @@ public function doesMethodExist( ?StatementsSource $source = null, ?CodeLocation $code_location = null ): ?bool { - foreach (self::$legacy_handlers[strtolower($fq_classlike_name)] ?? [] as $method_handler) { - $method_exists = $method_handler( - $fq_classlike_name, - $method_name_lowercase, - $source, - $code_location - ); - - if ($method_exists !== null) { - return $method_exists; - } - } - foreach (self::$handlers[strtolower($fq_classlike_name)] ?? [] as $method_handler) { $event = new MethodExistenceProviderEvent( $fq_classlike_name, diff --git a/src/Psalm/Internal/Provider/MethodParamsProvider.php b/src/Psalm/Internal/Provider/MethodParamsProvider.php index 7cf39e78f75..537979ea281 100644 --- a/src/Psalm/Internal/Provider/MethodParamsProvider.php +++ b/src/Psalm/Internal/Provider/MethodParamsProvider.php @@ -9,13 +9,15 @@ use Psalm\Internal\Provider\ReturnTypeProvider\PdoStatementSetFetchMode; use Psalm\Plugin\EventHandler\Event\MethodParamsProviderEvent; use Psalm\Plugin\EventHandler\MethodParamsProviderInterface; -use Psalm\Plugin\Hook\MethodParamsProviderInterface as LegacyMethodParamsProviderInterface; use Psalm\StatementsSource; use Psalm\Storage\FunctionLikeParameter; use function is_subclass_of; use function strtolower; +/** + * @internal + */ class MethodParamsProvider { /** @@ -26,25 +28,9 @@ class MethodParamsProvider */ private static $handlers = []; - /** - * @var array< - * lowercase-string, - * array=, - * ?StatementsSource=, - * ?Context=, - * ?CodeLocation= - * ): ?array> - * > - */ - private static $legacy_handlers = []; - public function __construct() { self::$handlers = []; - self::$legacy_handlers = []; $this->registerClass(PdoStatementSetFetchMode::class); } @@ -54,13 +40,7 @@ public function __construct() */ public function registerClass(string $class): void { - if (is_subclass_of($class, LegacyMethodParamsProviderInterface::class, true)) { - $callable = Closure::fromCallable([$class, 'getMethodParams']); - - foreach ($class::getClassLikeNames() as $fq_classlike_name) { - $this->registerLegacyClosure($fq_classlike_name, $callable); - } - } elseif (is_subclass_of($class, MethodParamsProviderInterface::class, true)) { + if (is_subclass_of($class, MethodParamsProviderInterface::class, true)) { $callable = Closure::fromCallable([$class, 'getMethodParams']); foreach ($class::getClassLikeNames() as $fq_classlike_name) { @@ -77,25 +57,9 @@ public function registerClosure(string $fq_classlike_name, Closure $c): void self::$handlers[strtolower($fq_classlike_name)][] = $c; } - /** - * @param Closure( - * string, - * string, - * ?list=, - * ?StatementsSource=, - * ?Context=, - * ?CodeLocation= - * ): ?array $c - */ - public function registerLegacyClosure(string $fq_classlike_name, Closure $c): void - { - self::$legacy_handlers[strtolower($fq_classlike_name)][] = $c; - } - public function has(string $fq_classlike_name): bool { - return isset(self::$handlers[strtolower($fq_classlike_name)]) || - isset(self::$legacy_handlers[strtolower($fq_classlike_name)]); + return isset(self::$handlers[strtolower($fq_classlike_name)]); } /** @@ -111,21 +75,6 @@ public function getMethodParams( ?Context $context = null, ?CodeLocation $code_location = null ): ?array { - foreach (self::$legacy_handlers[strtolower($fq_classlike_name)] ?? [] as $class_handler) { - $result = $class_handler( - $fq_classlike_name, - $method_name_lowercase, - $call_args, - $statements_source, - $context, - $code_location - ); - - if ($result !== null) { - return $result; - } - } - foreach (self::$handlers[strtolower($fq_classlike_name)] ?? [] as $class_handler) { $event = new MethodParamsProviderEvent( $fq_classlike_name, diff --git a/src/Psalm/Internal/Provider/MethodReturnTypeProvider.php b/src/Psalm/Internal/Provider/MethodReturnTypeProvider.php index f29dc77d6cc..197a4564f57 100644 --- a/src/Psalm/Internal/Provider/MethodReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/MethodReturnTypeProvider.php @@ -13,13 +13,15 @@ use Psalm\Internal\Provider\ReturnTypeProvider\SimpleXmlElementAsXml; use Psalm\Plugin\EventHandler\Event\MethodReturnTypeProviderEvent; use Psalm\Plugin\EventHandler\MethodReturnTypeProviderInterface; -use Psalm\Plugin\Hook\MethodReturnTypeProviderInterface as LegacyMethodReturnTypeProviderInterface; use Psalm\StatementsSource; use Psalm\Type\Union; use function is_subclass_of; use function strtolower; +/** + * @internal + */ class MethodReturnTypeProvider { /** @@ -30,28 +32,9 @@ class MethodReturnTypeProvider */ private static $handlers = []; - /** - * @var array< - * lowercase-string, - * array, - * Context, - * CodeLocation, - * ?array=, - * ?string=, - * ?lowercase-string= - * ): ?Union> - * > - */ - private static $legacy_handlers = []; - public function __construct() { self::$handlers = []; - self::$legacy_handlers = []; $this->registerClass(DomNodeAppendChild::class); $this->registerClass(ImagickPixelColorReturnTypeProvider::class); @@ -65,13 +48,7 @@ public function __construct() */ public function registerClass(string $class): void { - if (is_subclass_of($class, LegacyMethodReturnTypeProviderInterface::class, true)) { - $callable = Closure::fromCallable([$class, 'getMethodReturnType']); - - foreach ($class::getClassLikeNames() as $fq_classlike_name) { - $this->registerLegacyClosure($fq_classlike_name, $callable); - } - } elseif (is_subclass_of($class, MethodReturnTypeProviderInterface::class, true)) { + if (is_subclass_of($class, MethodReturnTypeProviderInterface::class, true)) { $callable = Closure::fromCallable([$class, 'getMethodReturnType']); foreach ($class::getClassLikeNames() as $fq_classlike_name) { @@ -88,29 +65,9 @@ public function registerClosure(string $fq_classlike_name, Closure $c): void self::$handlers[strtolower($fq_classlike_name)][] = $c; } - /** - * @param Closure( - * StatementsSource, - * string, - * lowercase-string, - * list, - * Context, - * CodeLocation, - * ?array=, - * ?string=, - * ?lowercase-string= - * ): ?Union $c - * - */ - public function registerLegacyClosure(string $fq_classlike_name, Closure $c): void - { - self::$legacy_handlers[strtolower($fq_classlike_name)][] = $c; - } - public function has(string $fq_classlike_name): bool { - return isset(self::$handlers[strtolower($fq_classlike_name)]) || - isset(self::$legacy_handlers[strtolower($fq_classlike_name)]); + return isset(self::$handlers[strtolower($fq_classlike_name)]); } /** @@ -128,24 +85,6 @@ public function getReturnType( ?string $called_fq_classlike_name = null, ?string $called_method_name = null ): ?Union { - foreach (self::$legacy_handlers[strtolower($fq_classlike_name)] ?? [] as $class_handler) { - $result = $class_handler( - $statements_source, - $fq_classlike_name, - strtolower($method_name), - $stmt->getArgs(), - $context, - $code_location, - $template_type_parameters, - $called_fq_classlike_name, - $called_method_name ? strtolower($called_method_name) : null - ); - - if ($result) { - return $result; - } - } - foreach (self::$handlers[strtolower($fq_classlike_name)] ?? [] as $class_handler) { $event = new MethodReturnTypeProviderEvent( $statements_source, diff --git a/src/Psalm/Internal/Provider/MethodVisibilityProvider.php b/src/Psalm/Internal/Provider/MethodVisibilityProvider.php index e9a876bc638..e090f38739f 100644 --- a/src/Psalm/Internal/Provider/MethodVisibilityProvider.php +++ b/src/Psalm/Internal/Provider/MethodVisibilityProvider.php @@ -7,12 +7,14 @@ use Psalm\Context; use Psalm\Plugin\EventHandler\Event\MethodVisibilityProviderEvent; use Psalm\Plugin\EventHandler\MethodVisibilityProviderInterface; -use Psalm\Plugin\Hook\MethodVisibilityProviderInterface as LegacyMethodVisibilityProviderInterface; use Psalm\StatementsSource; use function is_subclass_of; use function strtolower; +/** + * @internal + */ class MethodVisibilityProvider { /** @@ -23,24 +25,9 @@ class MethodVisibilityProvider */ private static $handlers = []; - /** - * @var array< - * lowercase-string, - * array - * > - */ - private static $legacy_handlers = []; - public function __construct() { self::$handlers = []; - self::$legacy_handlers = []; } /** @@ -49,13 +36,7 @@ public function __construct() */ public function registerClass(string $class): void { - if (is_subclass_of($class, LegacyMethodVisibilityProviderInterface::class, true)) { - $callable = Closure::fromCallable([$class, 'isMethodVisible']); - - foreach ($class::getClassLikeNames() as $fq_classlike_name) { - $this->registerLegacyClosure($fq_classlike_name, $callable); - } - } elseif (is_subclass_of($class, MethodVisibilityProviderInterface::class, true)) { + if (is_subclass_of($class, MethodVisibilityProviderInterface::class, true)) { $callable = Closure::fromCallable([$class, 'isMethodVisible']); foreach ($class::getClassLikeNames() as $fq_classlike_name) { @@ -72,24 +53,9 @@ public function registerClosure(string $fq_classlike_name, Closure $c): void self::$handlers[strtolower($fq_classlike_name)][] = $c; } - /** - * @param Closure( - * StatementsSource, - * string, - * string, - * Context, - * ?CodeLocation - * ): ?bool $c - */ - public function registerLegacyClosure(string $fq_classlike_name, Closure $c): void - { - self::$legacy_handlers[strtolower($fq_classlike_name)][] = $c; - } - public function has(string $fq_classlike_name): bool { - return isset(self::$handlers[strtolower($fq_classlike_name)]) || - isset(self::$legacy_handlers[strtolower($fq_classlike_name)]); + return isset(self::$handlers[strtolower($fq_classlike_name)]); } public function isMethodVisible( @@ -99,20 +65,6 @@ public function isMethodVisible( Context $context, ?CodeLocation $code_location = null ): ?bool { - foreach (self::$legacy_handlers[strtolower($fq_classlike_name)] ?? [] as $method_handler) { - $method_visible = $method_handler( - $source, - $fq_classlike_name, - $method_name, - $context, - $code_location - ); - - if ($method_visible !== null) { - return $method_visible; - } - } - foreach (self::$handlers[strtolower($fq_classlike_name)] ?? [] as $method_handler) { $event = new MethodVisibilityProviderEvent( $source, diff --git a/src/Psalm/Internal/Provider/NodeDataProvider.php b/src/Psalm/Internal/Provider/NodeDataProvider.php index 3987a231ffd..c642d3788f4 100644 --- a/src/Psalm/Internal/Provider/NodeDataProvider.php +++ b/src/Psalm/Internal/Provider/NodeDataProvider.php @@ -16,6 +16,9 @@ use Psalm\Type\Union; use SplObjectStorage; +/** + * @internal + */ class NodeDataProvider implements NodeTypeProvider { /** @var SplObjectStorage */ diff --git a/src/Psalm/Internal/Provider/ParserCacheProvider.php b/src/Psalm/Internal/Provider/ParserCacheProvider.php index 85c0c06c42c..1918279554e 100644 --- a/src/Psalm/Internal/Provider/ParserCacheProvider.php +++ b/src/Psalm/Internal/Provider/ParserCacheProvider.php @@ -2,6 +2,7 @@ namespace Psalm\Internal\Provider; +use JsonException; use PhpParser; use PhpParser\Node\Stmt; use Psalm\Config; @@ -31,6 +32,7 @@ use const DIRECTORY_SEPARATOR; use const E_USER_ERROR; +use const JSON_THROW_ON_ERROR; use const SCANDIR_SORT_NONE; /** @@ -193,7 +195,14 @@ private function getExistingFileContentHashes(): array return []; } - $hashes_decoded = json_decode($hashes_encoded, true); + try { + $hashes_decoded = json_decode($hashes_encoded, true, 512, JSON_THROW_ON_ERROR); + } catch (JsonException $e) { + error_log('Failed to parse hashes: ' . $e->getMessage()); + $this->existing_file_content_hashes = []; + + return []; + } if (!is_array($hashes_decoded)) { error_log('Unexpected value ' . gettype($hashes_decoded)); @@ -281,7 +290,7 @@ public function saveFileContentHashes(): void file_put_contents( $file_hashes_path, - json_encode($file_content_hashes) + json_encode($file_content_hashes, JSON_THROW_ON_ERROR) ); } diff --git a/src/Psalm/Internal/Provider/ProjectCacheProvider.php b/src/Psalm/Internal/Provider/ProjectCacheProvider.php index 886c467bc8f..3e41c5b2371 100644 --- a/src/Psalm/Internal/Provider/ProjectCacheProvider.php +++ b/src/Psalm/Internal/Provider/ProjectCacheProvider.php @@ -17,6 +17,8 @@ /** * Used to determine which files reference other files, necessary for using the --diff * option from the command line. + * + * @internal */ class ProjectCacheProvider { diff --git a/src/Psalm/Internal/Provider/PropertyExistenceProvider.php b/src/Psalm/Internal/Provider/PropertyExistenceProvider.php index 6af68fef25e..9065b3fb99e 100644 --- a/src/Psalm/Internal/Provider/PropertyExistenceProvider.php +++ b/src/Psalm/Internal/Provider/PropertyExistenceProvider.php @@ -7,12 +7,14 @@ use Psalm\Context; use Psalm\Plugin\EventHandler\Event\PropertyExistenceProviderEvent; use Psalm\Plugin\EventHandler\PropertyExistenceProviderInterface; -use Psalm\Plugin\Hook\PropertyExistenceProviderInterface as LegacyPropertyExistenceProviderInterface; use Psalm\StatementsSource; use function is_subclass_of; use function strtolower; +/** + * @internal + */ class PropertyExistenceProvider { /** @@ -23,25 +25,9 @@ class PropertyExistenceProvider */ private static $handlers = []; - /** - * @var array< - * lowercase-string, - * array - * > - */ - private static $legacy_handlers = []; - public function __construct() { self::$handlers = []; - self::$legacy_handlers = []; } /** @@ -50,13 +36,7 @@ public function __construct() */ public function registerClass(string $class): void { - if (is_subclass_of($class, LegacyPropertyExistenceProviderInterface::class, true)) { - $callable = Closure::fromCallable([$class, 'doesPropertyExist']); - - foreach ($class::getClassLikeNames() as $fq_classlike_name) { - $this->registerLegacyClosure($fq_classlike_name, $callable); - } - } elseif (is_subclass_of($class, PropertyExistenceProviderInterface::class, true)) { + if (is_subclass_of($class, PropertyExistenceProviderInterface::class, true)) { $callable = Closure::fromCallable([$class, 'doesPropertyExist']); foreach ($class::getClassLikeNames() as $fq_classlike_name) { @@ -73,25 +53,9 @@ public function registerClosure(string $fq_classlike_name, Closure $c): void self::$handlers[strtolower($fq_classlike_name)][] = $c; } - /** - * @param Closure( - * string, - * string, - * bool, - * ?StatementsSource=, - * ?Context=, - * ?CodeLocation= - * ): ?bool $c - */ - public function registerLegacyClosure(string $fq_classlike_name, Closure $c): void - { - self::$legacy_handlers[strtolower($fq_classlike_name)][] = $c; - } - public function has(string $fq_classlike_name): bool { - return isset(self::$handlers[strtolower($fq_classlike_name)]) || - isset(self::$legacy_handlers[strtolower($fq_classlike_name)]); + return isset(self::$handlers[strtolower($fq_classlike_name)]); } public function doesPropertyExist( @@ -102,21 +66,6 @@ public function doesPropertyExist( ?Context $context = null, ?CodeLocation $code_location = null ): ?bool { - foreach (self::$legacy_handlers[strtolower($fq_classlike_name)] ?? [] as $property_handler) { - $property_exists = $property_handler( - $fq_classlike_name, - $property_name, - $read_mode, - $source, - $context, - $code_location - ); - - if ($property_exists !== null) { - return $property_exists; - } - } - foreach (self::$handlers[strtolower($fq_classlike_name)] ?? [] as $property_handler) { $event = new PropertyExistenceProviderEvent( $fq_classlike_name, diff --git a/src/Psalm/Internal/Provider/PropertyTypeProvider.php b/src/Psalm/Internal/Provider/PropertyTypeProvider.php index 2e3d1dbee00..e24509291e0 100644 --- a/src/Psalm/Internal/Provider/PropertyTypeProvider.php +++ b/src/Psalm/Internal/Provider/PropertyTypeProvider.php @@ -7,13 +7,15 @@ use Psalm\Internal\Provider\PropertyTypeProvider\DomDocumentPropertyTypeProvider; use Psalm\Plugin\EventHandler\Event\PropertyTypeProviderEvent; use Psalm\Plugin\EventHandler\PropertyTypeProviderInterface; -use Psalm\Plugin\Hook\PropertyTypeProviderInterface as LegacyPropertyTypeProviderInterface; use Psalm\StatementsSource; use Psalm\Type\Union; use function is_subclass_of; use function strtolower; +/** + * @internal + */ class PropertyTypeProvider { /** @@ -24,24 +26,9 @@ class PropertyTypeProvider */ private static $handlers = []; - /** - * @var array< - * lowercase-string, - * array - * > - */ - private static $legacy_handlers = []; - public function __construct() { self::$handlers = []; - self::$legacy_handlers = []; $this->registerClass(DomDocumentPropertyTypeProvider::class); } @@ -51,13 +38,7 @@ public function __construct() */ public function registerClass(string $class): void { - if (is_subclass_of($class, LegacyPropertyTypeProviderInterface::class, true)) { - $callable = Closure::fromCallable([$class, 'getPropertyType']); - - foreach ($class::getClassLikeNames() as $fq_classlike_name) { - $this->registerLegacyClosure($fq_classlike_name, $callable); - } - } elseif (is_subclass_of($class, PropertyTypeProviderInterface::class, true)) { + if (is_subclass_of($class, PropertyTypeProviderInterface::class, true)) { $callable = Closure::fromCallable([$class, 'getPropertyType']); foreach ($class::getClassLikeNames() as $fq_classlike_name) { @@ -74,24 +55,9 @@ public function registerClosure(string $fq_classlike_name, Closure $c): void self::$handlers[strtolower($fq_classlike_name)][] = $c; } - /** - * @param Closure( - * string, - * string, - * bool, - * ?StatementsSource=, - * ?Context= - * ): ?Union $c - */ - public function registerLegacyClosure(string $fq_classlike_name, Closure $c): void - { - self::$legacy_handlers[strtolower($fq_classlike_name)][] = $c; - } - public function has(string $fq_classlike_name): bool { - return isset(self::$handlers[strtolower($fq_classlike_name)]) || - isset(self::$legacy_handlers[strtolower($fq_classlike_name)]); + return isset(self::$handlers[strtolower($fq_classlike_name)]); } public function getPropertyType( @@ -106,20 +72,6 @@ public function getPropertyType( $source->addSuppressedIssues(['NonInvariantDocblockPropertyType']); } - foreach (self::$legacy_handlers[strtolower($fq_classlike_name)] ?? [] as $property_handler) { - $property_type = $property_handler( - $fq_classlike_name, - $property_name, - $read_mode, - $source, - $context - ); - - if ($property_type !== null) { - return $property_type; - } - } - foreach (self::$handlers[strtolower($fq_classlike_name)] ?? [] as $property_handler) { $event = new PropertyTypeProviderEvent( $fq_classlike_name, diff --git a/src/Psalm/Internal/Provider/PropertyTypeProvider/DomDocumentPropertyTypeProvider.php b/src/Psalm/Internal/Provider/PropertyTypeProvider/DomDocumentPropertyTypeProvider.php index 2b127672ac0..eb2f6652dcd 100644 --- a/src/Psalm/Internal/Provider/PropertyTypeProvider/DomDocumentPropertyTypeProvider.php +++ b/src/Psalm/Internal/Provider/PropertyTypeProvider/DomDocumentPropertyTypeProvider.php @@ -12,6 +12,9 @@ use function strtolower; +/** + * @internal + */ class DomDocumentPropertyTypeProvider implements PropertyTypeProviderInterface { public static function getPropertyType(PropertyTypeProviderEvent $event): ?Union diff --git a/src/Psalm/Internal/Provider/PropertyVisibilityProvider.php b/src/Psalm/Internal/Provider/PropertyVisibilityProvider.php index 4ad57941662..de2ba125ad2 100644 --- a/src/Psalm/Internal/Provider/PropertyVisibilityProvider.php +++ b/src/Psalm/Internal/Provider/PropertyVisibilityProvider.php @@ -7,12 +7,13 @@ use Psalm\Context; use Psalm\Plugin\EventHandler\Event\PropertyVisibilityProviderEvent; use Psalm\Plugin\EventHandler\PropertyVisibilityProviderInterface; -use Psalm\Plugin\Hook\PropertyVisibilityProviderInterface as LegacyPropertyVisibilityProviderInterface; use Psalm\StatementsSource; -use function is_subclass_of; use function strtolower; +/** + * @internal + */ class PropertyVisibilityProvider { /** @@ -23,45 +24,20 @@ class PropertyVisibilityProvider */ private static $handlers = []; - /** - * @var array< - * lowercase-string, - * array - * > - */ - private static $legacy_handlers = []; - public function __construct() { self::$handlers = []; - self::$legacy_handlers = []; } /** - * @param class-string - * |class-string $class + * @param class-string $class */ public function registerClass(string $class): void { - if (is_subclass_of($class, LegacyPropertyVisibilityProviderInterface::class, true)) { - $callable = Closure::fromCallable([$class, 'isPropertyVisible']); - - foreach ($class::getClassLikeNames() as $fq_classlike_name) { - $this->registerLegacyClosure($fq_classlike_name, $callable); - } - } elseif (is_subclass_of($class, PropertyVisibilityProviderInterface::class, true)) { - $callable = Closure::fromCallable([$class, 'isPropertyVisible']); + $callable = Closure::fromCallable([$class, 'isPropertyVisible']); - foreach ($class::getClassLikeNames() as $fq_classlike_name) { - $this->registerClosure($fq_classlike_name, $callable); - } + foreach ($class::getClassLikeNames() as $fq_classlike_name) { + $this->registerClosure($fq_classlike_name, $callable); } } @@ -73,25 +49,9 @@ public function registerClosure(string $fq_classlike_name, Closure $c): void self::$handlers[strtolower($fq_classlike_name)][] = $c; } - /** - * @param Closure( - * StatementsSource, - * string, - * string, - * bool, - * Context, - * CodeLocation - * ): ?bool $c - */ - public function registerLegacyClosure(string $fq_classlike_name, Closure $c): void - { - self::$legacy_handlers[strtolower($fq_classlike_name)][] = $c; - } - public function has(string $fq_classlike_name): bool { - return isset(self::$handlers[strtolower($fq_classlike_name)]) || - isset(self::$legacy_handlers[strtolower($fq_classlike_name)]); + return isset(self::$handlers[strtolower($fq_classlike_name)]); } public function isPropertyVisible( @@ -102,21 +62,6 @@ public function isPropertyVisible( Context $context, CodeLocation $code_location ): ?bool { - foreach (self::$legacy_handlers[strtolower($fq_classlike_name)] ?? [] as $property_handler) { - $property_visible = $property_handler( - $source, - $fq_classlike_name, - $property_name, - $read_mode, - $context, - $code_location - ); - - if ($property_visible !== null) { - return $property_visible; - } - } - foreach (self::$handlers[strtolower($fq_classlike_name)] ?? [] as $property_handler) { $event = new PropertyVisibilityProviderEvent( $source, diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayChunkReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayChunkReturnTypeProvider.php index 86877094546..1a1fc376f99 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayChunkReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayChunkReturnTypeProvider.php @@ -15,6 +15,9 @@ use function count; +/** + * @internal + */ class ArrayChunkReturnTypeProvider implements FunctionReturnTypeProviderInterface { /** diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayColumnReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayColumnReturnTypeProvider.php index 36e15cb9bd8..1225be7458c 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayColumnReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayColumnReturnTypeProvider.php @@ -16,6 +16,9 @@ use function count; use function reset; +/** + * @internal + */ class ArrayColumnReturnTypeProvider implements FunctionReturnTypeProviderInterface { /** diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayFillReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayFillReturnTypeProvider.php index 9a16f980eee..03dc378d46a 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayFillReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayFillReturnTypeProvider.php @@ -13,6 +13,9 @@ use Psalm\Type\Atomic\TNonEmptyList; use Psalm\Type\Union; +/** + * @internal + */ class ArrayFillReturnTypeProvider implements FunctionReturnTypeProviderInterface { /** diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayFilterReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayFilterReturnTypeProvider.php index b8612e18025..385f7fda3d3 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayFilterReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayFilterReturnTypeProvider.php @@ -33,6 +33,9 @@ use function reset; use function spl_object_id; +/** + * @internal + */ class ArrayFilterReturnTypeProvider implements FunctionReturnTypeProviderInterface { /** @@ -108,9 +111,7 @@ static function ($keyed_type) use ($statements_source, $context) { }, $first_arg_array->properties ), - static function ($keyed_type) { - return !$keyed_type->isEmpty(); - } + static fn($keyed_type) => !$keyed_type->isNever() ); if (!$new_properties) { diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMapReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMapReturnTypeProvider.php index 47c786e2802..882acf8b6a0 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMapReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMapReturnTypeProvider.php @@ -44,6 +44,9 @@ use function strpos; use function substr; +/** + * @internal + */ class ArrayMapReturnTypeProvider implements FunctionReturnTypeProviderInterface { /** @@ -194,9 +197,7 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev /** * @return Union */ - function (Union $_) use ($mapping_return_type): Union { - return clone $mapping_return_type; - }, + fn(Union $_): Union => clone $mapping_return_type, $array_arg_atomic_type->properties ) ); diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMergeReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMergeReturnTypeProvider.php index 578560b635c..5ffc0a1e626 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMergeReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMergeReturnTypeProvider.php @@ -24,6 +24,9 @@ use function is_string; use function max; +/** + * @internal + */ class ArrayMergeReturnTypeProvider implements FunctionReturnTypeProviderInterface { /** @@ -154,7 +157,7 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev } } } else { - if (!$unpacked_type_part->type_params[0]->isEmpty()) { + if (!$unpacked_type_part->isEmptyArray()) { foreach ($generic_properties as $key => $keyed_type) { $generic_properties[$key] = Type::combineUnionTypes( $keyed_type, @@ -169,7 +172,7 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev } if ($unpacked_type_part instanceof TArray) { - if ($unpacked_type_part->type_params[1]->isEmpty()) { + if ($unpacked_type_part->isEmptyArray()) { continue; } @@ -243,7 +246,7 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev ]); } - $inner_key_type = $inner_key_type ?? Type::getArrayKey(); + $inner_key_type ??= Type::getArrayKey(); if ($any_nonempty) { return new Union([ diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayPadReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayPadReturnTypeProvider.php index f5859c174c5..4ad56bdd452 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayPadReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayPadReturnTypeProvider.php @@ -16,6 +16,9 @@ use function count; +/** + * @internal + */ class ArrayPadReturnTypeProvider implements FunctionReturnTypeProviderInterface { /** diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayPointerAdjustmentReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayPointerAdjustmentReturnTypeProvider.php index 66d39525603..5ac91d639da 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayPointerAdjustmentReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayPointerAdjustmentReturnTypeProvider.php @@ -20,6 +20,9 @@ use function array_merge; use function array_shift; +/** + * @internal + */ class ArrayPointerAdjustmentReturnTypeProvider implements FunctionReturnTypeProviderInterface { /** @@ -80,7 +83,7 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev throw new UnexpectedValueException('This should never happen'); } - if ($value_type->isEmpty()) { + if ($value_type->isNever()) { $value_type = Type::getFalse(); } elseif (($function_id !== 'reset' && $function_id !== 'end') || !$definitely_has_items) { $value_type->addType(new TFalse); diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayPopReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayPopReturnTypeProvider.php index 25dd2bd2ccc..0ea580949d7 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayPopReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayPopReturnTypeProvider.php @@ -14,6 +14,9 @@ use Psalm\Type\Atomic\TNull; use Psalm\Type\Union; +/** + * @internal + */ class ArrayPopReturnTypeProvider implements FunctionReturnTypeProviderInterface { /** @@ -55,7 +58,7 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev if ($first_arg_array instanceof TArray) { $value_type = clone $first_arg_array->type_params[1]; - if ($value_type->isEmpty()) { + if ($first_arg_array->isEmptyArray()) { return Type::getNull(); } diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayRandReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayRandReturnTypeProvider.php index 8c7062b31aa..41830ca965d 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayRandReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayRandReturnTypeProvider.php @@ -12,6 +12,9 @@ use Psalm\Type\Atomic\TList; use Psalm\Type\Union; +/** + * @internal + */ class ArrayRandReturnTypeProvider implements FunctionReturnTypeProviderInterface { /** diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayReduceReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayReduceReturnTypeProvider.php index a9d27ba8774..c8958f982b3 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayReduceReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayReduceReturnTypeProvider.php @@ -27,6 +27,9 @@ use function strtolower; use function substr; +/** + * @internal + */ class ArrayReduceReturnTypeProvider implements FunctionReturnTypeProviderInterface { /** diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayReverseReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayReverseReturnTypeProvider.php index 0fab0664810..4c2d2705df3 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayReverseReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayReverseReturnTypeProvider.php @@ -11,6 +11,9 @@ use Psalm\Type\Atomic\TList; use Psalm\Type\Union; +/** + * @internal + */ class ArrayReverseReturnTypeProvider implements FunctionReturnTypeProviderInterface { /** diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArraySliceReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArraySliceReturnTypeProvider.php index 399bb259319..e0adab140c0 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArraySliceReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArraySliceReturnTypeProvider.php @@ -16,6 +16,9 @@ use function array_merge; use function array_shift; +/** + * @internal + */ class ArraySliceReturnTypeProvider implements FunctionReturnTypeProviderInterface { /** diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArraySpliceReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArraySpliceReturnTypeProvider.php index 6f4d0625da1..125c42ee2c9 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArraySpliceReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArraySpliceReturnTypeProvider.php @@ -11,6 +11,9 @@ use Psalm\Type\Atomic\TList; use Psalm\Type\Union; +/** + * @internal + */ class ArraySpliceReturnTypeProvider implements FunctionReturnTypeProviderInterface { /** diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayUniqueReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayUniqueReturnTypeProvider.php index 5499ad275d2..eb6ef09c806 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayUniqueReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayUniqueReturnTypeProvider.php @@ -13,6 +13,9 @@ use Psalm\Type\Atomic\TNonEmptyList; use Psalm\Type\Union; +/** + * @internal + */ class ArrayUniqueReturnTypeProvider implements FunctionReturnTypeProviderInterface { /** diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayValuesReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayValuesReturnTypeProvider.php index 0544510f8c6..3ec26808007 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayValuesReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayValuesReturnTypeProvider.php @@ -18,6 +18,9 @@ use function array_merge; use function array_shift; +/** + * @internal + */ class ArrayValuesReturnTypeProvider implements FunctionReturnTypeProviderInterface { /** diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ClosureFromCallableReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ClosureFromCallableReturnTypeProvider.php index a94e5817a43..ef2fe97d0c8 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ClosureFromCallableReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ClosureFromCallableReturnTypeProvider.php @@ -11,6 +11,9 @@ use Psalm\Type\Atomic\TClosure; use Psalm\Type\Union; +/** + * @internal + */ class ClosureFromCallableReturnTypeProvider implements MethodReturnTypeProviderInterface { public static function getClassLikeNames(): array diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/DomNodeAppendChild.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/DomNodeAppendChild.php index 6d7723bfb0d..c08a570762d 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/DomNodeAppendChild.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/DomNodeAppendChild.php @@ -8,6 +8,9 @@ use Psalm\Type; use Psalm\Type\Union; +/** + * @internal + */ class DomNodeAppendChild implements MethodReturnTypeProviderInterface { public static function getClassLikeNames(): array diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ExplodeReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ExplodeReturnTypeProvider.php index 81001ff95fa..af6beb4c8a7 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ExplodeReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ExplodeReturnTypeProvider.php @@ -17,6 +17,9 @@ use function count; +/** + * @internal + */ class ExplodeReturnTypeProvider implements FunctionReturnTypeProviderInterface { /** diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/FilterVarReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/FilterVarReturnTypeProvider.php index 0e267608a7a..f35fdc28293 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/FilterVarReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/FilterVarReturnTypeProvider.php @@ -27,6 +27,9 @@ use const FILTER_VALIDATE_REGEXP; use const FILTER_VALIDATE_URL; +/** + * @internal + */ class FilterVarReturnTypeProvider implements FunctionReturnTypeProviderInterface { /** diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/FirstArgStringReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/FirstArgStringReturnTypeProvider.php index 4f99fdddb2d..00f633bd858 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/FirstArgStringReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/FirstArgStringReturnTypeProvider.php @@ -9,6 +9,9 @@ use Psalm\Type\Atomic\TNull; use Psalm\Type\Union; +/** + * @internal + */ class FirstArgStringReturnTypeProvider implements FunctionReturnTypeProviderInterface { /** diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/GetClassMethodsReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/GetClassMethodsReturnTypeProvider.php index 4c7d78655a4..a5f07266a36 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/GetClassMethodsReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/GetClassMethodsReturnTypeProvider.php @@ -8,6 +8,9 @@ use Psalm\Type; use Psalm\Type\Union; +/** + * @internal + */ class GetClassMethodsReturnTypeProvider implements FunctionReturnTypeProviderInterface { /** diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/GetObjectVarsReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/GetObjectVarsReturnTypeProvider.php index 888280cd346..20c43ab9251 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/GetObjectVarsReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/GetObjectVarsReturnTypeProvider.php @@ -19,6 +19,9 @@ use function reset; use function strtolower; +/** + * @internal + */ class GetObjectVarsReturnTypeProvider implements FunctionReturnTypeProviderInterface { /** diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/HexdecReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/HexdecReturnTypeProvider.php index b6c6078813f..0cbd821ab00 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/HexdecReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/HexdecReturnTypeProvider.php @@ -8,6 +8,9 @@ use Psalm\Type; use Psalm\Type\Union; +/** + * @internal + */ class HexdecReturnTypeProvider implements FunctionReturnTypeProviderInterface { /** diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ImagickPixelColorReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ImagickPixelColorReturnTypeProvider.php index c97048aefd1..6884757d358 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ImagickPixelColorReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ImagickPixelColorReturnTypeProvider.php @@ -14,6 +14,9 @@ use function assert; use function in_array; +/** + * @internal + */ class ImagickPixelColorReturnTypeProvider implements MethodReturnTypeProviderInterface { public static function getClassLikeNames(): array diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/InArrayReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/InArrayReturnTypeProvider.php index 519984f2f52..4c4a61c6e65 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/InArrayReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/InArrayReturnTypeProvider.php @@ -11,6 +11,9 @@ use Psalm\Type\Atomic\TList; use Psalm\Type\Union; +/** + * @internal + */ class InArrayReturnTypeProvider implements FunctionReturnTypeProviderInterface { /** diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/IteratorToArrayReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/IteratorToArrayReturnTypeProvider.php index 52d26475169..a5176b1a1ef 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/IteratorToArrayReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/IteratorToArrayReturnTypeProvider.php @@ -20,6 +20,9 @@ use function array_shift; use function assert; +/** + * @internal + */ class IteratorToArrayReturnTypeProvider implements FunctionReturnTypeProviderInterface { /** diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/MinMaxReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/MinMaxReturnTypeProvider.php index 1a24e22b30b..d8eba498963 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/MinMaxReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/MinMaxReturnTypeProvider.php @@ -23,6 +23,9 @@ use function max; use function min; +/** + * @internal + */ class MinMaxReturnTypeProvider implements FunctionReturnTypeProviderInterface { /** @@ -88,18 +91,14 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev if ($event->getFunctionId() === 'min') { assert(count($min_bounds) !== 0); //null values in $max_bounds doesn't make sense for min() so we remove them - $max_bounds = array_filter($max_bounds, function ($v) { - return $v !== null; - }) ?: [null]; + $max_bounds = array_filter($max_bounds, fn($v) => $v !== null) ?: [null]; $min_potential_int = in_array(null, $min_bounds, true) ? null : min($min_bounds); $max_potential_int = in_array(null, $max_bounds, true) ? null : min($max_bounds); } else { assert(count($max_bounds) !== 0); //null values in $min_bounds doesn't make sense for max() so we remove them - $min_bounds = array_filter($min_bounds, function ($v) { - return $v !== null; - }) ?: [null]; + $min_bounds = array_filter($min_bounds, fn($v) => $v !== null) ?: [null]; $min_potential_int = in_array(null, $min_bounds, true) ? null : max($min_bounds); $max_potential_int = in_array(null, $max_bounds, true) ? null : max($max_bounds); diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/MktimeReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/MktimeReturnTypeProvider.php index 4b9ea038957..a8d37a92465 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/MktimeReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/MktimeReturnTypeProvider.php @@ -10,6 +10,9 @@ use Psalm\Type\Atomic\TInt; use Psalm\Type\Union; +/** + * @internal + */ class MktimeReturnTypeProvider implements FunctionReturnTypeProviderInterface { /** diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ParseUrlReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ParseUrlReturnTypeProvider.php index 6d51c7d2d9d..02641f87248 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ParseUrlReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ParseUrlReturnTypeProvider.php @@ -24,6 +24,9 @@ use const PHP_URL_SCHEME; use const PHP_URL_USER; +/** + * @internal + */ class ParseUrlReturnTypeProvider implements FunctionReturnTypeProviderInterface { /** diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/PdoStatementReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/PdoStatementReturnTypeProvider.php index 86ee127178a..1d2521ea003 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/PdoStatementReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/PdoStatementReturnTypeProvider.php @@ -17,6 +17,9 @@ use function class_exists; +/** + * @internal + */ class PdoStatementReturnTypeProvider implements MethodReturnTypeProviderInterface { public static function getClassLikeNames(): array diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/PdoStatementSetFetchMode.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/PdoStatementSetFetchMode.php index e352ffd48e8..cd88b142770 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/PdoStatementSetFetchMode.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/PdoStatementSetFetchMode.php @@ -10,6 +10,9 @@ use Psalm\Storage\FunctionLikeParameter; use Psalm\Type; +/** + * @internal + */ class PdoStatementSetFetchMode implements MethodParamsProviderInterface { public static function getClassLikeNames(): array diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/RandReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/RandReturnTypeProvider.php index 32530db9bef..7d72ceb941f 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/RandReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/RandReturnTypeProvider.php @@ -14,6 +14,9 @@ use function count; +/** + * @internal + */ class RandReturnTypeProvider implements FunctionReturnTypeProviderInterface { /** diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/SimpleXmlElementAsXml.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/SimpleXmlElementAsXml.php index 88e71c3840d..612ebdb845a 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/SimpleXmlElementAsXml.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/SimpleXmlElementAsXml.php @@ -9,6 +9,9 @@ use function count; +/** + * @internal + */ class SimpleXmlElementAsXml implements MethodReturnTypeProviderInterface { public static function getClassLikeNames(): array diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/StrReplaceReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/StrReplaceReturnTypeProvider.php index d32da31e979..6061ddf0d34 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/StrReplaceReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/StrReplaceReturnTypeProvider.php @@ -12,6 +12,9 @@ use function count; use function in_array; +/** + * @internal + */ class StrReplaceReturnTypeProvider implements FunctionReturnTypeProviderInterface { /** diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/StrTrReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/StrTrReturnTypeProvider.php index cb3b01958d8..6e73cff96d3 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/StrTrReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/StrTrReturnTypeProvider.php @@ -12,6 +12,9 @@ use function in_array; +/** + * @internal + */ class StrTrReturnTypeProvider implements FunctionReturnTypeProviderInterface { /** diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/TriggerErrorReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/TriggerErrorReturnTypeProvider.php index 385c6b61b5d..d8da5f1766b 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/TriggerErrorReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/TriggerErrorReturnTypeProvider.php @@ -21,6 +21,9 @@ use const E_USER_NOTICE; use const E_USER_WARNING; +/** + * @internal + */ class TriggerErrorReturnTypeProvider implements FunctionReturnTypeProviderInterface { /** diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/VersionCompareReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/VersionCompareReturnTypeProvider.php index cab2b99971f..82151bd1369 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/VersionCompareReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/VersionCompareReturnTypeProvider.php @@ -15,6 +15,9 @@ use function count; +/** + * @internal + */ class VersionCompareReturnTypeProvider implements FunctionReturnTypeProviderInterface { /** diff --git a/src/Psalm/Internal/Provider/StatementsProvider.php b/src/Psalm/Internal/Provider/StatementsProvider.php index 3a5cb57fd80..03df6b2f113 100644 --- a/src/Psalm/Internal/Provider/StatementsProvider.php +++ b/src/Psalm/Internal/Provider/StatementsProvider.php @@ -6,6 +6,7 @@ use PhpParser\ErrorHandler\Collecting; use PhpParser\Node\Stmt; use Psalm\CodeLocation\ParseErrorLocation; +use Psalm\Codebase; use Psalm\Config; use Psalm\Internal\Diff\FileDiffer; use Psalm\Internal\Diff\FileStatementsDiffer; @@ -109,8 +110,11 @@ public function __construct( /** * @return list */ - public function getStatementsForFile(string $file_path, string $php_version, ?Progress $progress = null): array - { + public function getStatementsForFile( + string $file_path, + int $analysis_php_version_id, + ?Progress $progress = null + ): array { unset($this->errors[$file_path]); if ($progress === null) { @@ -133,7 +137,7 @@ public function getStatementsForFile(string $file_path, string $php_version, ?Pr $has_errors = false; - $stmts = self::parseStatements($file_contents, $php_version, $has_errors, $file_path); + $stmts = self::parseStatements($file_contents, $analysis_php_version_id, $has_errors, $file_path); return $stmts ?: []; } @@ -176,7 +180,7 @@ public function getStatementsForFile(string $file_path, string $php_version, ?Pr if ($existing_statements && $existing_file_contents - && abs(strlen($existing_file_contents) - strlen($file_contents)) < 5000 + && abs(strlen($existing_file_contents) - strlen($file_contents)) < 5_000 ) { $file_changes = FileDiffer::getDiff($existing_file_contents, $file_contents); @@ -195,7 +199,7 @@ public function getStatementsForFile(string $file_path, string $php_version, ?Pr $stmts = self::parseStatements( $file_contents, - $php_version, + $analysis_php_version_id, $has_errors, $file_path, $existing_file_contents, @@ -213,16 +217,12 @@ public function getStatementsForFile(string $file_path, string $php_version, ?Pr ); $unchanged_members = array_map( - function (int $_): bool { - return true; - }, + fn(int $_): bool => true, array_flip($unchanged_members) ); $unchanged_signature_members = array_map( - function (int $_): bool { - return true; - }, + fn(int $_): bool => true, array_flip($unchanged_signature_members) ); @@ -245,9 +245,7 @@ function (string $key) use ($file_path_hash): string { * * @return bool */ - function ($_): bool { - return true; - }, + fn($_): bool => true, array_flip($changed_members) ); @@ -416,22 +414,24 @@ public function resetDiffs(): void * @return list */ public static function parseStatements( - string $file_contents, - string $php_version, - bool &$has_errors, + string $file_contents, + int $analysis_php_version_id, + bool &$has_errors, ?string $file_path = null, ?string $existing_file_contents = null, - ?array $existing_statements = null, - ?array $file_changes = null + ?array $existing_statements = null, + ?array $file_changes = null ): array { $attributes = [ 'comments', 'startLine', 'startFilePos', 'endFilePos', ]; if (!self::$lexer) { + $major_version = Codebase::transformPhpVersionId($analysis_php_version_id, 10_000); + $minor_version = Codebase::transformPhpVersionId($analysis_php_version_id % 10_000, 100); self::$lexer = new PhpParser\Lexer\Emulative([ 'usedAttributes' => $attributes, - 'phpVersion' => $php_version, + 'phpVersion' => $major_version . '.' . $minor_version, ]); } diff --git a/src/Psalm/Internal/RuntimeCaches.php b/src/Psalm/Internal/RuntimeCaches.php index b34fa2c3470..694b49e41c1 100644 --- a/src/Psalm/Internal/RuntimeCaches.php +++ b/src/Psalm/Internal/RuntimeCaches.php @@ -18,6 +18,9 @@ use Psalm\Internal\Type\TypeTokenizer; use Psalm\IssueBuffer; +/** + * @internal + */ abstract class RuntimeCaches { public static function clearAll(): void diff --git a/src/Psalm/Internal/Scanner/DocblockParser.php b/src/Psalm/Internal/Scanner/DocblockParser.php index 416f487e5a2..7c854b7c57f 100644 --- a/src/Psalm/Internal/Scanner/DocblockParser.php +++ b/src/Psalm/Internal/Scanner/DocblockParser.php @@ -19,6 +19,8 @@ /** * This class will parse Docblocks in order to extract known tags from them + * + * @internal */ class DocblockParser { diff --git a/src/Psalm/Internal/Scanner/FileScanner.php b/src/Psalm/Internal/Scanner/FileScanner.php index 1cc9b6fffe9..b77a8794556 100644 --- a/src/Psalm/Internal/Scanner/FileScanner.php +++ b/src/Psalm/Internal/Scanner/FileScanner.php @@ -59,7 +59,7 @@ public function scan( $stmts = $codebase->statements_provider->getStatementsForFile( $file_storage->file_path, - $codebase->php_major_version . '.' . $codebase->php_minor_version, + $codebase->analysis_php_version_id, $progress ); diff --git a/src/Psalm/Internal/Scanner/ParsedDocblock.php b/src/Psalm/Internal/Scanner/ParsedDocblock.php index 26c5b01363c..8f5b87192ec 100644 --- a/src/Psalm/Internal/Scanner/ParsedDocblock.php +++ b/src/Psalm/Internal/Scanner/ParsedDocblock.php @@ -5,6 +5,9 @@ use function explode; use function trim; +/** + * @internal + */ class ParsedDocblock { /** @var string */ diff --git a/src/Psalm/Internal/Scanner/UnresolvedConstant/ArrayOffsetFetch.php b/src/Psalm/Internal/Scanner/UnresolvedConstant/ArrayOffsetFetch.php index fde629653e6..bdb2c16f51e 100644 --- a/src/Psalm/Internal/Scanner/UnresolvedConstant/ArrayOffsetFetch.php +++ b/src/Psalm/Internal/Scanner/UnresolvedConstant/ArrayOffsetFetch.php @@ -6,6 +6,8 @@ /** * @psalm-immutable + * + * @internal */ class ArrayOffsetFetch extends UnresolvedConstantComponent { diff --git a/src/Psalm/Internal/Scanner/UnresolvedConstant/ArraySpread.php b/src/Psalm/Internal/Scanner/UnresolvedConstant/ArraySpread.php index 1a1ec7bdf19..9bd4d069a61 100644 --- a/src/Psalm/Internal/Scanner/UnresolvedConstant/ArraySpread.php +++ b/src/Psalm/Internal/Scanner/UnresolvedConstant/ArraySpread.php @@ -6,6 +6,8 @@ /** * @psalm-immutable + * + * @internal */ class ArraySpread extends UnresolvedConstantComponent { diff --git a/src/Psalm/Internal/Scanner/UnresolvedConstant/ArrayValue.php b/src/Psalm/Internal/Scanner/UnresolvedConstant/ArrayValue.php index e42888f29b6..94a246ac31d 100644 --- a/src/Psalm/Internal/Scanner/UnresolvedConstant/ArrayValue.php +++ b/src/Psalm/Internal/Scanner/UnresolvedConstant/ArrayValue.php @@ -6,6 +6,8 @@ /** * @psalm-immutable + * + * @internal */ class ArrayValue extends UnresolvedConstantComponent { diff --git a/src/Psalm/Internal/Scanner/UnresolvedConstant/ClassConstant.php b/src/Psalm/Internal/Scanner/UnresolvedConstant/ClassConstant.php index 7f789b9ff43..041f02fa2df 100644 --- a/src/Psalm/Internal/Scanner/UnresolvedConstant/ClassConstant.php +++ b/src/Psalm/Internal/Scanner/UnresolvedConstant/ClassConstant.php @@ -6,6 +6,8 @@ /** * @psalm-immutable + * + * @internal */ class ClassConstant extends UnresolvedConstantComponent { diff --git a/src/Psalm/Internal/Scanner/UnresolvedConstant/Constant.php b/src/Psalm/Internal/Scanner/UnresolvedConstant/Constant.php index a222fb038e0..5e7d2950872 100644 --- a/src/Psalm/Internal/Scanner/UnresolvedConstant/Constant.php +++ b/src/Psalm/Internal/Scanner/UnresolvedConstant/Constant.php @@ -6,6 +6,8 @@ /** * @psalm-immutable + * + * @internal */ class Constant extends UnresolvedConstantComponent { diff --git a/src/Psalm/Internal/Scanner/UnresolvedConstant/KeyValuePair.php b/src/Psalm/Internal/Scanner/UnresolvedConstant/KeyValuePair.php index 7ba4d8d3243..e75c68dae22 100644 --- a/src/Psalm/Internal/Scanner/UnresolvedConstant/KeyValuePair.php +++ b/src/Psalm/Internal/Scanner/UnresolvedConstant/KeyValuePair.php @@ -6,6 +6,8 @@ /** * @psalm-immutable + * + * @internal */ class KeyValuePair extends UnresolvedConstantComponent { diff --git a/src/Psalm/Internal/Scanner/UnresolvedConstant/ScalarValue.php b/src/Psalm/Internal/Scanner/UnresolvedConstant/ScalarValue.php index 6fa9727ab2e..757bcdcef0d 100644 --- a/src/Psalm/Internal/Scanner/UnresolvedConstant/ScalarValue.php +++ b/src/Psalm/Internal/Scanner/UnresolvedConstant/ScalarValue.php @@ -6,6 +6,8 @@ /** * @psalm-immutable + * + * @internal */ class ScalarValue extends UnresolvedConstantComponent { diff --git a/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedAdditionOp.php b/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedAdditionOp.php index 90c2f9e9c20..565f76a9309 100644 --- a/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedAdditionOp.php +++ b/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedAdditionOp.php @@ -4,6 +4,8 @@ /** * @psalm-immutable + * + * @internal */ class UnresolvedAdditionOp extends UnresolvedBinaryOp { diff --git a/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedBinaryOp.php b/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedBinaryOp.php index 3662cc6ed84..adaa1b10ba0 100644 --- a/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedBinaryOp.php +++ b/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedBinaryOp.php @@ -6,6 +6,8 @@ /** * @psalm-immutable + * + * @internal */ abstract class UnresolvedBinaryOp extends UnresolvedConstantComponent { diff --git a/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedBitwiseAnd.php b/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedBitwiseAnd.php index c4fb77a3553..2b59f99a33a 100644 --- a/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedBitwiseAnd.php +++ b/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedBitwiseAnd.php @@ -4,6 +4,8 @@ /** * @psalm-immutable + * + * @internal */ class UnresolvedBitwiseAnd extends UnresolvedBinaryOp { diff --git a/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedBitwiseOr.php b/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedBitwiseOr.php index 751fc5eebb4..e6f2c08a626 100644 --- a/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedBitwiseOr.php +++ b/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedBitwiseOr.php @@ -4,6 +4,8 @@ /** * @psalm-immutable + * + * @internal */ class UnresolvedBitwiseOr extends UnresolvedBinaryOp { diff --git a/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedBitwiseXor.php b/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedBitwiseXor.php index cf72dacd6a9..c9ff455bfda 100644 --- a/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedBitwiseXor.php +++ b/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedBitwiseXor.php @@ -4,6 +4,8 @@ /** * @psalm-immutable + * + * @internal */ class UnresolvedBitwiseXor extends UnresolvedBinaryOp { diff --git a/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedConcatOp.php b/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedConcatOp.php index 72c871811c2..29899ab3b8f 100644 --- a/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedConcatOp.php +++ b/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedConcatOp.php @@ -4,6 +4,8 @@ /** * @psalm-immutable + * + * @internal */ class UnresolvedConcatOp extends UnresolvedBinaryOp { diff --git a/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedDivisionOp.php b/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedDivisionOp.php index b990265cf95..9efa28eb7c4 100644 --- a/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedDivisionOp.php +++ b/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedDivisionOp.php @@ -4,6 +4,8 @@ /** * @psalm-immutable + * + * @internal */ class UnresolvedDivisionOp extends UnresolvedBinaryOp { diff --git a/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedMultiplicationOp.php b/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedMultiplicationOp.php index 76905bd6c5c..d2572f9344a 100644 --- a/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedMultiplicationOp.php +++ b/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedMultiplicationOp.php @@ -4,6 +4,8 @@ /** * @psalm-immutable + * + * @internal */ class UnresolvedMultiplicationOp extends UnresolvedBinaryOp { diff --git a/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedSubtractionOp.php b/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedSubtractionOp.php index 76a06cc6a91..87ceb46c9c0 100644 --- a/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedSubtractionOp.php +++ b/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedSubtractionOp.php @@ -4,6 +4,8 @@ /** * @psalm-immutable + * + * @internal */ class UnresolvedSubtractionOp extends UnresolvedBinaryOp { diff --git a/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedTernary.php b/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedTernary.php index f9618f0ffa4..ede42e27232 100644 --- a/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedTernary.php +++ b/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedTernary.php @@ -6,6 +6,8 @@ /** * @psalm-immutable + * + * @internal */ class UnresolvedTernary extends UnresolvedConstantComponent { diff --git a/src/Psalm/Internal/Scanner/UnresolvedConstantComponent.php b/src/Psalm/Internal/Scanner/UnresolvedConstantComponent.php index 5f96f7d1ac6..5d7bf494b03 100644 --- a/src/Psalm/Internal/Scanner/UnresolvedConstantComponent.php +++ b/src/Psalm/Internal/Scanner/UnresolvedConstantComponent.php @@ -4,6 +4,8 @@ /** * @psalm-immutable + * + * @internal */ abstract class UnresolvedConstantComponent { diff --git a/src/Psalm/Internal/Stubs/Generator/ClassLikeStubGenerator.php b/src/Psalm/Internal/Stubs/Generator/ClassLikeStubGenerator.php index 6bc81302b96..d6efd715654 100644 --- a/src/Psalm/Internal/Stubs/Generator/ClassLikeStubGenerator.php +++ b/src/Psalm/Internal/Stubs/Generator/ClassLikeStubGenerator.php @@ -25,6 +25,9 @@ use function array_slice; use function rtrim; +/** + * @internal + */ class ClassLikeStubGenerator { /** diff --git a/src/Psalm/Internal/Stubs/Generator/StubsGenerator.php b/src/Psalm/Internal/Stubs/Generator/StubsGenerator.php index 23b1ca16ef0..9504c12ff74 100644 --- a/src/Psalm/Internal/Stubs/Generator/StubsGenerator.php +++ b/src/Psalm/Internal/Stubs/Generator/StubsGenerator.php @@ -27,7 +27,6 @@ use Psalm\Type\Atomic\TDependentGetDebugType; use Psalm\Type\Atomic\TDependentGetType; use Psalm\Type\Atomic\TDependentListKey; -use Psalm\Type\Atomic\TEmpty; use Psalm\Type\Atomic\TEmptyMixed; use Psalm\Type\Atomic\TEmptyNumeric; use Psalm\Type\Atomic\TEmptyScalar; @@ -108,6 +107,9 @@ use function rtrim; use function strpos; +/** + * @internal + */ class StubsGenerator { public static function getAll( @@ -384,7 +386,7 @@ public static function getParserTypeFromPsalmType(Union $type): ?PhpParser\NodeA || $atomic_type instanceof TArray || $atomic_type instanceof TIterable ) { - $identifier_string = $atomic_type->toPhpString(null, [], null, 8, 0); + $identifier_string = $atomic_type->toPhpString(null, [], null, 8_00_00); if ($identifier_string === null) { throw new UnexpectedValueException( diff --git a/src/Psalm/Internal/Type/AssertionReconciler.php b/src/Psalm/Internal/Type/AssertionReconciler.php index e7e6478c600..a6ed2272950 100644 --- a/src/Psalm/Internal/Type/AssertionReconciler.php +++ b/src/Psalm/Internal/Type/AssertionReconciler.php @@ -51,6 +51,9 @@ use function strpos; use function substr; +/** + * @internal + */ class AssertionReconciler extends Reconciler { /** @@ -383,7 +386,6 @@ private static function refine( if ($existing_var_type_part instanceof TNamedObject || $existing_var_type_part instanceof TTemplateParam ) { - $new_type_part->addIntersectionType($existing_var_type_part); $acceptable_atomic_types[] = clone $existing_var_type_part; } else { if (AtomicTypeComparator::isContainedBy( @@ -469,32 +471,6 @@ private static function refine( return new Union($acceptable_atomic_types); } } elseif (!$new_type->hasMixed()) { - $has_match = true; - - if ($key - && $code_location - && $new_type->getId() === $existing_var_type->getId() - //even if two objects are the same, equality is not guaranteed - // example: (ErrorException and TypeError are both Throwable but not equal) - && !$new_type->hasNamedObjectType() - && !$is_equality - && !($original_assertion === 'loaded-class-string' && $old_var_type_string === 'class-string') - && (!($statements_analyzer->getSource()->getSource() instanceof TraitAnalyzer) - || ($key !== '$this' - && !($existing_var_type->hasLiteralClassString() && $new_type->hasLiteralClassString()))) - ) { - self::triggerIssueForImpossible( - $existing_var_type, - $old_var_type_string, - $key, - $original_assertion, - true, - $negated, - $code_location, - $suppressed_issues - ); - } - $any_scalar_type_match_found = false; if ($code_location @@ -504,7 +480,7 @@ private static function refine( && !$new_type_has_interface && (!($statements_analyzer->getSource()->getSource() instanceof TraitAnalyzer) || ($key !== '$this' - && !($existing_var_type->hasLiteralClassString() && $new_type->hasLiteralClassString()))) + && strpos($original_assertion, 'isa-') !== 0)) && UnionTypeComparator::isContainedBy( $codebase, $existing_var_type, @@ -528,17 +504,15 @@ private static function refine( ); } - $new_type = self::filterTypeWithAnother( + $intersection_type = self::filterTypeWithAnother( $codebase, $existing_var_type, $new_type, - $template_type_map, - $has_match, $any_scalar_type_match_found ); if ($code_location - && !$has_match + && !$intersection_type && (!$is_loose_equality || !$any_scalar_type_match_found) ) { if ($assertion === 'null') { @@ -592,6 +566,10 @@ private static function refine( $failed_reconciliation = Reconciler::RECONCILIATION_EMPTY; } + + if ($intersection_type) { + $new_type = $intersection_type; + } } return $new_type; @@ -602,361 +580,334 @@ private static function refine( * precise version. For example: new is `array` old is `list` so the result is `list` * * @param array> $template_type_map - * - * @psalm-suppress ComplexMethod we'd probably want to extract specific handling blocks at the end and also allow - * early return once a specific case has been handled */ private static function filterTypeWithAnother( Codebase $codebase, Union $existing_type, Union $new_type, - array $template_type_map, - bool &$has_match = false, bool &$any_scalar_type_match_found = false - ): Union { + ): ?Union { $matching_atomic_types = []; - $has_cloned_type = false; + $new_type = clone $new_type; foreach ($new_type->getAtomicTypes() as $new_type_part) { - $has_local_match = false; - - foreach ($existing_type->getAtomicTypes() as $key => $existing_type_part) { - // special workaround because PHP allows floats to contain ints, but we don’t want this - // behaviour here - if ($existing_type_part instanceof TFloat - && $new_type_part instanceof TInt - ) { - $any_scalar_type_match_found = true; - continue; - } - - $atomic_comparison_results = new TypeComparisonResult(); - - if ($existing_type_part instanceof TNamedObject) { - $existing_type_part->was_static = false; - } - - $atomic_contained_by = AtomicTypeComparator::isContainedBy( - $codebase, - $new_type_part, - $existing_type_part, - true, - false, - $atomic_comparison_results - ); - - if ($atomic_contained_by) { - $has_local_match = true; - - if ($atomic_comparison_results->type_coerced - && get_class($new_type_part) === TNamedObject::class - && $existing_type_part instanceof TGenericObject - ) { - // this is a hack - it's not actually rigorous, as the params may be different - $matching_atomic_types[] = new TGenericObject( - $new_type_part->value, - $existing_type_part->type_params - ); - } elseif ($new_type_part instanceof TNamedObject - && $existing_type_part instanceof TTemplateParam - && $existing_type_part->as->hasObjectType() - ) { - $existing_type_part = clone $existing_type_part; - $existing_type_part->as = self::filterTypeWithAnother( - $codebase, - $existing_type_part->as, - new Union([$new_type_part]), - $template_type_map - ); - - $matching_atomic_types[] = $existing_type_part; - } else { - $matching_atomic_types[] = clone $new_type_part; - } - - continue; - } - - if (AtomicTypeComparator::isContainedBy( - $codebase, + foreach ($existing_type->getAtomicTypes() as $existing_type_part) { + $matching_atomic_type = self::filterAtomicWithAnother( $existing_type_part, $new_type_part, - false, - false, - null - )) { - $has_local_match = true; - $matching_atomic_types[] = $existing_type_part; - - continue; - } + $codebase, + $any_scalar_type_match_found + ); - if ($existing_type_part instanceof TNamedObject - && $new_type_part instanceof TNamedObject - && ($codebase->interfaceExists($existing_type_part->value) - || $codebase->interfaceExists($new_type_part->value)) - ) { - $matching_atomic_type = clone $new_type_part; - $matching_atomic_type->extra_types[$existing_type_part->getKey()] = $existing_type_part; + if ($matching_atomic_type) { $matching_atomic_types[] = $matching_atomic_type; - $has_local_match = true; - - continue; } + } + } - if ($new_type_part instanceof TKeyedArray - && $existing_type_part instanceof TList - ) { - $new_type_key = $new_type_part->getGenericKeyType(); - $new_type_value = $new_type_part->getGenericValueType(); - - if (!$new_type_key->hasString()) { - $has_param_match = false; + if ($matching_atomic_types) { + $existing_type->bustCache(); + return new Union($matching_atomic_types); + } - $new_type_value = self::filterTypeWithAnother( - $codebase, - $existing_type_part->type_param, - $new_type_value, - $template_type_map, - $has_param_match, - $any_scalar_type_match_found - ); + return null; + } - $hybrid_type_part = new TKeyedArray($new_type_part->properties); - $hybrid_type_part->previous_key_type = Type::getInt(); - $hybrid_type_part->previous_value_type = $new_type_value; - $hybrid_type_part->is_list = true; + private static function filterAtomicWithAnother( + Atomic $type_1_atomic, + Atomic $type_2_atomic, + Codebase $codebase, + bool &$any_scalar_type_match_found + ): ?Atomic { + if ($type_1_atomic instanceof TFloat + && $type_2_atomic instanceof TInt + ) { + $any_scalar_type_match_found = true; + return $type_2_atomic; + } - if (!$has_cloned_type) { - $new_type = clone $new_type; - $has_cloned_type = true; - } + if ($type_1_atomic instanceof TNamedObject) { + $type_1_atomic->is_static = false; + } - $has_local_match = true; + $atomic_comparison_results = new TypeComparisonResult(); - $new_type->removeType($key); - $new_type->addType($hybrid_type_part); + $atomic_contained_by = AtomicTypeComparator::isContainedBy( + $codebase, + $type_2_atomic, + $type_1_atomic, + !($type_1_atomic instanceof TNamedObject && $type_2_atomic instanceof TNamedObject), + false, + $atomic_comparison_results + ); - continue; - } - } + if ($atomic_contained_by) { + return self::refineContainedAtomicWithAnother( + $type_1_atomic, + $type_2_atomic, + $codebase, + $atomic_comparison_results->type_coerced ?? false + ); + } - if ($new_type_part instanceof TTemplateParam - && $existing_type_part instanceof TTemplateParam - && $new_type_part->param_name !== $existing_type_part->param_name - && $new_type_part->as->hasObject() - && $existing_type_part->as->hasObject() - ) { - $matching_atomic_type = clone $new_type_part; + $atomic_comparison_results = new TypeComparisonResult(); - $matching_atomic_type->extra_types[$existing_type_part->getKey()] = $existing_type_part; - $matching_atomic_types[] = $matching_atomic_type; - $has_local_match = true; + $atomic_contained_by = AtomicTypeComparator::isContainedBy( + $codebase, + $type_1_atomic, + $type_2_atomic, + !($type_1_atomic instanceof TNamedObject && $type_2_atomic instanceof TNamedObject), + false, + $atomic_comparison_results + ); - continue; - } + if ($atomic_contained_by) { + return self::refineContainedAtomicWithAnother( + $type_2_atomic, + $type_1_atomic, + $codebase, + $atomic_comparison_results->type_coerced ?? false + ); + } - //we filter both types of standard iterables - if (($new_type_part instanceof TGenericObject - || $new_type_part instanceof TArray - || $new_type_part instanceof TIterable) - && ($existing_type_part instanceof TGenericObject - || $existing_type_part instanceof TArray - || $existing_type_part instanceof TIterable) - && count($new_type_part->type_params) === count($existing_type_part->type_params) - ) { - $has_any_param_match = false; + $matching_atomic_type = null; - foreach ($new_type_part->type_params as $i => $new_param) { - $existing_param = $existing_type_part->type_params[$i]; + if ($type_1_atomic instanceof TNamedObject + && $type_2_atomic instanceof TNamedObject + && ($codebase->interfaceExists($type_1_atomic->value) + || $codebase->interfaceExists($type_2_atomic->value)) + ) { + $matching_atomic_type = clone $type_2_atomic; + $matching_atomic_type->extra_types[$type_1_atomic->getKey()] = $type_1_atomic; - $has_param_match = true; + return $matching_atomic_type; + } - $new_param_id = $new_param->getId(); + if ($type_2_atomic instanceof TKeyedArray + && $type_1_atomic instanceof TList + ) { + $type_2_key = $type_2_atomic->getGenericKeyType(); + $type_2_value = $type_2_atomic->getGenericValueType(); - $new_param = self::filterTypeWithAnother( - $codebase, - $existing_param, - $new_param, - $template_type_map, - $has_param_match, - $any_scalar_type_match_found - ); + if (!$type_2_key->hasString()) { + $type_2_value = self::filterTypeWithAnother( + $codebase, + $type_1_atomic->type_param, + $type_2_value, + $any_scalar_type_match_found + ); - if ($template_type_map) { - TemplateInferredTypeReplacer::replace( - $new_param, - new TemplateResult([], $template_type_map), - $codebase - ); - } + if ($type_2_value === null) { + return null; + } - $existing_type->bustCache(); + $hybrid_type_part = new TKeyedArray($type_2_atomic->properties); + $hybrid_type_part->previous_key_type = Type::getInt(); + $hybrid_type_part->previous_value_type = $type_2_value; + $hybrid_type_part->is_list = true; - if ($has_param_match - && $existing_type_part->type_params[$i]->getId() !== $new_param_id - ) { - /** @psalm-suppress PropertyTypeCoercion */ - $existing_type_part->type_params[$i] = $new_param; + return $hybrid_type_part; + } + } elseif ($type_1_atomic instanceof TKeyedArray + && $type_2_atomic instanceof TList + ) { + $type_1_key = $type_1_atomic->getGenericKeyType(); + $type_1_value = $type_1_atomic->getGenericValueType(); - if (!$has_local_match) { - $has_any_param_match = true; - } - } - } + if (!$type_1_key->hasString()) { + $type_1_value = self::filterTypeWithAnother( + $codebase, + $type_2_atomic->type_param, + $type_1_value, + $any_scalar_type_match_found + ); - if ($has_any_param_match) { - $has_local_match = true; - $matching_atomic_types[] = $existing_type_part; - $atomic_comparison_results->type_coerced = true; - } + if ($type_1_value === null) { + return null; } - //we filter the second part of a list with the second part of standard iterables - if (($new_type_part instanceof TArray - || $new_type_part instanceof TIterable) - && $existing_type_part instanceof TList - ) { - $has_any_param_match = false; + $hybrid_type_part = new TKeyedArray($type_1_atomic->properties); + $hybrid_type_part->previous_key_type = Type::getInt(); + $hybrid_type_part->previous_value_type = $type_1_value; + $hybrid_type_part->is_list = true; - $new_param = $new_type_part->type_params[1]; - $existing_param = $existing_type_part->type_param; + return $hybrid_type_part; + } + } - $has_param_match = true; + if ($type_2_atomic instanceof TTemplateParam + && $type_1_atomic instanceof TTemplateParam + && $type_2_atomic->param_name !== $type_1_atomic->param_name + && $type_2_atomic->as->hasObject() + && $type_1_atomic->as->hasObject() + ) { + $matching_atomic_type = clone $type_2_atomic; - $new_param = self::filterTypeWithAnother( - $codebase, - $existing_param, - $new_param, - $template_type_map, - $has_param_match, - $any_scalar_type_match_found - ); + $matching_atomic_type->extra_types[$type_1_atomic->getKey()] = $type_1_atomic; - if ($template_type_map) { - TemplateInferredTypeReplacer::replace( - $new_param, - new TemplateResult([], $template_type_map), - $codebase - ); - } + return $matching_atomic_type; + } - $existing_type->bustCache(); + //we filter both types of standard iterables + if (($type_2_atomic instanceof TGenericObject + || $type_2_atomic instanceof TArray + || $type_2_atomic instanceof TIterable) + && ($type_1_atomic instanceof TGenericObject + || $type_1_atomic instanceof TArray + || $type_1_atomic instanceof TIterable) + && count($type_2_atomic->type_params) === count($type_1_atomic->type_params) + ) { + foreach ($type_2_atomic->type_params as $i => $type_2_param) { + $type_1_param = $type_1_atomic->type_params[$i]; - if ($has_param_match - && $existing_type_part->type_param->getId() !== $new_param->getId() - ) { - $existing_type_part->type_param = $new_param; + $type_2_param_id = $type_2_param->getId(); - if (!$has_local_match) { - $has_any_param_match = true; - } - } + $type_2_param = self::filterTypeWithAnother( + $codebase, + $type_1_param, + $type_2_param, + $any_scalar_type_match_found + ); - if ($has_any_param_match) { - $has_local_match = true; - $matching_atomic_types[] = $existing_type_part; - $atomic_comparison_results->type_coerced = true; - } + if ($type_2_param === null) { + return null; } - //we filter each property of a Keyed Array with the second part of standard iterables - if (($new_type_part instanceof TArray - || $new_type_part instanceof TIterable) - && $existing_type_part instanceof TKeyedArray - ) { - $has_any_param_match = false; + if ($type_1_atomic->type_params[$i]->getId() !== $type_2_param_id) { + /** @psalm-suppress PropertyTypeCoercion */ + $type_1_atomic->type_params[$i] = $type_2_param; + } + } - $new_param = $new_type_part->type_params[1]; - foreach ($existing_type_part->properties as $property_key => $existing_param) { - $has_param_match = true; + $matching_atomic_type = $type_1_atomic; + $atomic_comparison_results->type_coerced = true; + } - $new_param = self::filterTypeWithAnother( - $codebase, - $existing_param, - $new_param, - $template_type_map, - $has_param_match, - $any_scalar_type_match_found - ); + //we filter the second part of a list with the second part of standard iterables + if (($type_2_atomic instanceof TArray + || $type_2_atomic instanceof TIterable) + && $type_1_atomic instanceof TList + ) { + $type_2_param = $type_2_atomic->type_params[1]; + $type_1_param = $type_1_atomic->type_param; - if ($template_type_map) { - TemplateInferredTypeReplacer::replace( - $new_param, - new TemplateResult([], $template_type_map), - $codebase - ); - } + $type_2_param = self::filterTypeWithAnother( + $codebase, + $type_1_param, + $type_2_param, + $any_scalar_type_match_found + ); - if ($has_param_match - && $existing_type_part->properties[$property_key]->getId() !== $new_param->getId() - ) { - $existing_type_part->properties[$property_key] = $new_param; + if ($type_2_param === null) { + return null; + } - if (!$has_local_match) { - $has_any_param_match = true; - } - } - } + if ($type_1_atomic->type_param->getId() !== $type_2_param->getId()) { + $type_1_atomic->type_param = $type_2_param; + } - $existing_type->bustCache(); + $matching_atomic_type = $type_1_atomic; + $atomic_comparison_results->type_coerced = true; + } - if ($has_any_param_match) { - $has_local_match = true; - $matching_atomic_types[] = $existing_type_part; - $atomic_comparison_results->type_coerced = true; - } - } + //we filter each property of a Keyed Array with the second part of standard iterables + if (($type_2_atomic instanceof TArray + || $type_2_atomic instanceof TIterable) + && $type_1_atomic instanceof TKeyedArray + ) { + $type_2_param = $type_2_atomic->type_params[1]; + foreach ($type_1_atomic->properties as $property_key => $type_1_param) { + $type_2_param = self::filterTypeWithAnother( + $codebase, + $type_1_param, + $type_2_param, + $any_scalar_type_match_found + ); - //These partial match wouldn't have been handled by AtomicTypeComparator - $new_range = null; - if ($new_type_part instanceof TIntRange && $existing_type_part instanceof TPositiveInt) { - $new_range = TIntRange::intersectIntRanges( - TIntRange::convertToIntRange($existing_type_part), - $new_type_part - ); - } elseif ($existing_type_part instanceof TIntRange - && $new_type_part instanceof TPositiveInt - ) { - $new_range = TIntRange::intersectIntRanges( - $existing_type_part, - TIntRange::convertToIntRange($new_type_part) - ); - } elseif ($new_type_part instanceof TIntRange - && $existing_type_part instanceof TIntRange - ) { - $new_range = TIntRange::intersectIntRanges( - $existing_type_part, - $new_type_part - ); + if ($type_2_param === null) { + return null; } - if ($new_range !== null) { - $has_local_match = true; - $matching_atomic_types[] = $new_range; + if ($type_1_atomic->properties[$property_key]->getId() !== $type_2_param->getId()) { + $type_1_atomic->properties[$property_key] = $type_2_param; } + } - if ($atomic_comparison_results->type_coerced) { - continue; - } + $matching_atomic_type = $type_1_atomic; + $atomic_comparison_results->type_coerced = true; + } - if ($atomic_comparison_results->scalar_type_match_found) { - $any_scalar_type_match_found = true; - } - } + //These partial match wouldn't have been handled by AtomicTypeComparator + $new_range = null; + if ($type_2_atomic instanceof TIntRange && $type_1_atomic instanceof TPositiveInt) { + $new_range = TIntRange::intersectIntRanges( + TIntRange::convertToIntRange($type_1_atomic), + $type_2_atomic + ); + } elseif ($type_1_atomic instanceof TIntRange + && $type_2_atomic instanceof TPositiveInt + ) { + $new_range = TIntRange::intersectIntRanges( + $type_1_atomic, + TIntRange::convertToIntRange($type_2_atomic) + ); + } elseif ($type_2_atomic instanceof TIntRange + && $type_1_atomic instanceof TIntRange + ) { + $new_range = TIntRange::intersectIntRanges( + $type_1_atomic, + $type_2_atomic + ); + } - if (!$has_local_match) { - $has_match = false; - break; - } + if ($new_range !== null) { + $matching_atomic_type = $new_range; } - if ($matching_atomic_types) { - return new Union($matching_atomic_types); + if (!$atomic_comparison_results->type_coerced && $atomic_comparison_results->scalar_type_match_found) { + $any_scalar_type_match_found = true; } - return $new_type; + return $matching_atomic_type; + } + + private static function refineContainedAtomicWithAnother( + Atomic $type_1_atomic, + Atomic $type_2_atomic, + Codebase $codebase, + bool $type_coerced + ): ?Atomic { + if ($type_coerced + && get_class($type_2_atomic) === TNamedObject::class + && $type_1_atomic instanceof TGenericObject + ) { + // this is a hack - it's not actually rigorous, as the params may be different + return new TGenericObject( + $type_2_atomic->value, + $type_1_atomic->type_params + ); + } elseif ($type_2_atomic instanceof TNamedObject + && $type_1_atomic instanceof TTemplateParam + && $type_1_atomic->as->hasObjectType() + ) { + $type_1_atomic = clone $type_1_atomic; + $type_1_as = self::filterTypeWithAnother( + $codebase, + $type_1_atomic->as, + new Union([$type_2_atomic]) + ); + + if ($type_1_as === null) { + return null; + } + + $type_1_atomic->as = $type_1_as; + + return $type_1_atomic; + } else { + return clone $type_2_atomic; + } } /** @@ -1299,31 +1250,20 @@ private static function handleLiteralEqualityWithInt( true ); - if ($expanded instanceof Atomic) { - $compatible_int_type = self::getCompatibleIntType($existing_var_type, $value, $is_loose_equality); + foreach ($expanded as $expanded_type) { + $compatible_int_type = self::getCompatibleIntType( + $existing_var_type, + $value, + $is_loose_equality + ); + if ($compatible_int_type !== null) { return $compatible_int_type; } - if ($expanded instanceof TInt) { + if ($expanded_type instanceof TInt) { $has_int = true; } - } else { - foreach ($expanded as $expanded_type) { - $compatible_int_type = self::getCompatibleIntType( - $existing_var_type, - $value, - $is_loose_equality - ); - - if ($compatible_int_type !== null) { - return $compatible_int_type; - } - - if ($expanded_type instanceof TInt) { - $has_int = true; - } - } } } } diff --git a/src/Psalm/Internal/Type/Comparator/ArrayTypeComparator.php b/src/Psalm/Internal/Type/Comparator/ArrayTypeComparator.php index d1b31b89578..ce43e4cb664 100644 --- a/src/Psalm/Internal/Type/Comparator/ArrayTypeComparator.php +++ b/src/Psalm/Internal/Type/Comparator/ArrayTypeComparator.php @@ -7,11 +7,11 @@ use Psalm\Type\Atomic; use Psalm\Type\Atomic\TArray; use Psalm\Type\Atomic\TClassStringMap; -use Psalm\Type\Atomic\TEmpty; use Psalm\Type\Atomic\TKeyedArray; use Psalm\Type\Atomic\TList; use Psalm\Type\Atomic\TLiteralInt; use Psalm\Type\Atomic\TLiteralString; +use Psalm\Type\Atomic\TNever; use Psalm\Type\Atomic\TNonEmptyArray; use Psalm\Type\Atomic\TNonEmptyList; use Psalm\Type\Union; @@ -39,8 +39,8 @@ public static function isContainedBy( $is_empty_array = $input_type_part->equals( new TArray([ - new Union([new TEmpty()]), - new Union([new TEmpty()]) + new Union([new TNever()]), + new Union([new TNever()]) ]), false ); @@ -102,7 +102,7 @@ public static function isContainedBy( if ($container_type_part instanceof TList && $input_type_part instanceof TArray - && $input_type_part->type_params[1]->isEmpty() + && $input_type_part->isEmptyArray() ) { return !$container_type_part instanceof TNonEmptyList; } @@ -192,9 +192,7 @@ public static function isContainedBy( // if the array has a known size < 10, make sure the array keys are literal ints if ($input_type_part->count !== null && $input_type_part->count < 10) { $literal_ints = array_map( - function ($i) { - return new TLiteralInt($i); - }, + fn($i) => new TLiteralInt($i), range(0, $input_type_part->count - 1) ); @@ -225,7 +223,7 @@ function ($i) { continue; } - if ($input_param->isEmpty() + if ($input_param->isNever() && $container_type_part instanceof TNonEmptyArray ) { return false; @@ -233,7 +231,7 @@ function ($i) { $param_comparison_result = new TypeComparisonResult(); - if (!$input_param->isEmpty()) { + if (!$input_param->isNever()) { if (!UnionTypeComparator::isContainedBy( $codebase, $input_param, diff --git a/src/Psalm/Internal/Type/Comparator/AtomicTypeComparator.php b/src/Psalm/Internal/Type/Comparator/AtomicTypeComparator.php index 6f35b1ff3a3..4d0f1e5f80d 100644 --- a/src/Psalm/Internal/Type/Comparator/AtomicTypeComparator.php +++ b/src/Psalm/Internal/Type/Comparator/AtomicTypeComparator.php @@ -15,7 +15,6 @@ use Psalm\Type\Atomic\TClassStringMap; use Psalm\Type\Atomic\TClosure; use Psalm\Type\Atomic\TConditional; -use Psalm\Type\Atomic\TEmpty; use Psalm\Type\Atomic\TEmptyMixed; use Psalm\Type\Atomic\TEnumCase; use Psalm\Type\Atomic\TGenericObject; @@ -94,7 +93,7 @@ public static function isContainedBy( return true; } - if ($input_type_part instanceof TNever || $input_type_part instanceof TEmpty) { + if ($input_type_part instanceof TNever) { return true; } @@ -302,8 +301,8 @@ public static function isContainedBy( if ($container_type_part instanceof TNamedObject && $input_type_part instanceof TNamedObject - && $container_type_part->was_static - && !$input_type_part->was_static + && $container_type_part->is_static + && !$input_type_part->is_static ) { if ($atomic_comparison_result) { $atomic_comparison_result->type_coerced = true; @@ -472,7 +471,7 @@ public static function isContainedBy( $array_comparison_result = new TypeComparisonResult(); - if (!$input_param->isEmpty()) { + if (!$input_param->isNever()) { if (!UnionTypeComparator::isContainedBy( $codebase, $input_param, @@ -509,7 +508,7 @@ public static function isContainedBy( if ($input_type_part instanceof TNamedObject) { // check whether the object has a __toString method if ($codebase->classOrInterfaceExists($input_type_part->value)) { - if ($codebase->php_major_version >= 8 + if ($codebase->analysis_php_version_id >= 8_00_00 && ($input_type_part->value === 'Stringable' || ($codebase->classlikes->classExists($input_type_part->value) && $codebase->classlikes->classImplements($input_type_part->value, 'Stringable')) @@ -597,8 +596,8 @@ public static function isContainedBy( if ($container_type_part instanceof TNamedObject && $input_type_part instanceof TNamedObject - && $container_type_part->was_static - && !$input_type_part->was_static + && $container_type_part->is_static + && !$input_type_part->is_static ) { if ($atomic_comparison_result) { $atomic_comparison_result->type_coerced = true; diff --git a/src/Psalm/Internal/Type/Comparator/GenericTypeComparator.php b/src/Psalm/Internal/Type/Comparator/GenericTypeComparator.php index dd7e648c3fd..8758bd701a8 100644 --- a/src/Psalm/Internal/Type/Comparator/GenericTypeComparator.php +++ b/src/Psalm/Internal/Type/Comparator/GenericTypeComparator.php @@ -94,7 +94,7 @@ public static function isContainedBy( $container_param = $container_type_part->type_params[$i]; - if ($input_param->isEmpty()) { + if ($input_param->isNever()) { if ($atomic_comparison_result) { if (!$atomic_comparison_result->replacement_atomic_type) { $atomic_comparison_result->replacement_atomic_type = clone $input_type_part; @@ -192,17 +192,8 @@ public static function isContainedBy( $allow_interface_equality ) || $param_comparison_result->type_coerced ) { - if ($container_param->hasFormerStaticObject() - && $input_param->isFormerStaticObject() - && UnionTypeComparator::isContainedBy( - $codebase, - $input_param, - $container_param, - $container_param->ignore_nullable_issues, - $container_param->ignore_falsable_issues, - $param_comparison_result, - $allow_interface_equality - ) + if ($container_param->hasStaticObject() + && $input_param->isStaticObject() ) { // do nothing } else { diff --git a/src/Psalm/Internal/Type/Comparator/KeyedArrayComparator.php b/src/Psalm/Internal/Type/Comparator/KeyedArrayComparator.php index bb42838b7e8..7c372e438b8 100644 --- a/src/Psalm/Internal/Type/Comparator/KeyedArrayComparator.php +++ b/src/Psalm/Internal/Type/Comparator/KeyedArrayComparator.php @@ -42,7 +42,7 @@ public static function isContainedBy( $property_type_comparison = new TypeComparisonResult(); - if (!$input_property_type->isEmpty()) { + if (!$input_property_type->isNever()) { if (!UnionTypeComparator::isContainedBy( $codebase, $input_property_type, @@ -127,7 +127,7 @@ public static function isContainedByObjectWithProperties( $property_type_comparison = new TypeComparisonResult(); - if (!$input_property_type->isEmpty() + if (!$input_property_type->isNever() && !UnionTypeComparator::isContainedBy( $codebase, $input_property_type, diff --git a/src/Psalm/Internal/Type/Comparator/ObjectComparator.php b/src/Psalm/Internal/Type/Comparator/ObjectComparator.php index b7b4ff301db..5e3aff80828 100644 --- a/src/Psalm/Internal/Type/Comparator/ObjectComparator.php +++ b/src/Psalm/Internal/Type/Comparator/ObjectComparator.php @@ -7,12 +7,10 @@ use Psalm\Type\Atomic; use Psalm\Type\Atomic\TIterable; use Psalm\Type\Atomic\TNamedObject; -use Psalm\Type\Atomic\TNull; -use Psalm\Type\Atomic\TObject; use Psalm\Type\Atomic\TObjectWithProperties; use Psalm\Type\Atomic\TTemplateParam; +use Psalm\Type\Union; -use function array_merge; use function in_array; use function strpos; use function strtolower; @@ -34,35 +32,10 @@ public static function isShallowlyContainedBy( bool $allow_interface_equality, ?TypeComparisonResult $atomic_comparison_result ): bool { - $intersection_input_types = $input_type_part->extra_types ?: []; - $intersection_input_types[$input_type_part->getKey(false)] = $input_type_part; - - if ($input_type_part instanceof TTemplateParam) { - foreach ($input_type_part->as->getAtomicTypes() as $g) { - if ($g instanceof TNamedObject && $g->extra_types) { - $intersection_input_types = array_merge( - $intersection_input_types, - $g->extra_types - ); - } - } - } - - $intersection_container_types = $container_type_part->extra_types ?: []; - $intersection_container_types[$container_type_part->getKey(false)] = $container_type_part; - - if ($container_type_part instanceof TTemplateParam) { - foreach ($container_type_part->as->getAtomicTypes() as $g) { - if ($g instanceof TNamedObject && $g->extra_types) { - $intersection_container_types = array_merge( - $intersection_container_types, - $g->extra_types - ); - } - } - } + $intersection_input_types = self::getIntersectionTypes($input_type_part); + $intersection_container_types = self::getIntersectionTypes($container_type_part); - foreach ($intersection_container_types as $container_type_key => $intersection_container_type) { + foreach ($intersection_container_types as $intersection_container_type) { $container_was_static = false; if ($intersection_container_type instanceof TIterable) { @@ -70,75 +43,9 @@ public static function isShallowlyContainedBy( } elseif ($intersection_container_type instanceof TObjectWithProperties) { $intersection_container_type_lower = 'object'; } elseif ($intersection_container_type instanceof TTemplateParam) { - if (!$allow_interface_equality) { - if (isset($intersection_input_types[$container_type_key])) { - continue; - } - - foreach ($intersection_input_types as $intersection_input_type) { - if ($intersection_input_type instanceof TTemplateParam - && (strpos($intersection_container_type->defining_class, 'fn-') === 0 - || strpos($intersection_input_type->defining_class, 'fn-') === 0) - ) { - if (strpos($intersection_input_type->defining_class, 'fn-') === 0 - && strpos($intersection_container_type->defining_class, 'fn-') === 0 - && $intersection_input_type->defining_class - !== $intersection_container_type->defining_class - ) { - continue 2; - } - - foreach ($intersection_input_type->as->getAtomicTypes() as $input_as_atomic) { - if ($input_as_atomic->equals($intersection_container_type, false)) { - continue 3; - } - } - } elseif ($intersection_input_type instanceof TTemplateParam) { - $container_param = $intersection_container_type->param_name; - $container_class = $intersection_container_type->defining_class; - $input_class_like = $codebase->classlikes - ->getStorageFor($intersection_input_type->defining_class); - - if ($codebase->classlikes->traitExists($container_class) - && $input_class_like !== null - && isset( - $input_class_like->template_extended_params[$container_class][$container_param] - )) { - continue 2; - } - } - } - - return false; - } - - if ($intersection_container_type->as->isMixed()) { - continue; - } - $intersection_container_type_lower = null; - - foreach ($intersection_container_type->as->getAtomicTypes() as $g) { - if ($g instanceof TNull) { - continue; - } - - if ($g instanceof TObject) { - continue 2; - } - - if (!$g instanceof TNamedObject) { - continue 2; - } - - $intersection_container_type_lower = strtolower($g->value); - } - - if ($intersection_container_type_lower === null) { - return false; - } } else { - $container_was_static = $intersection_container_type->was_static; + $container_was_static = $intersection_container_type->is_static; $intersection_container_type_lower = strtolower( $codebase->classlikes->getUnAliasedName( @@ -147,165 +54,268 @@ public static function isShallowlyContainedBy( ); } - foreach ($intersection_input_types as $intersection_input_key => $intersection_input_type) { - $input_was_static = false; + $any_inputs_contained = false; - if ($intersection_input_type instanceof TIterable) { - $intersection_input_type_lower = 'iterable'; - } elseif ($intersection_input_type instanceof TObjectWithProperties) { - $intersection_input_type_lower = 'object'; - } elseif ($intersection_input_type instanceof TTemplateParam) { - if ($intersection_input_type->as->isMixed()) { - continue; - } - - $intersection_input_type_lower = null; + $container_type_is_interface = $intersection_container_type_lower + && $codebase->interfaceExists($intersection_container_type_lower); - foreach ($intersection_input_type->as->getAtomicTypes() as $g) { - if ($g instanceof TNull) { - continue; - } + foreach ($intersection_input_types as $input_type_key => $intersection_input_type) { + if ($allow_interface_equality + && $container_type_is_interface + && !isset($intersection_container_types[$input_type_key]) + ) { + $any_inputs_contained = true; + } elseif (self::isIntersectionShallowlyContainedBy( + $codebase, + $intersection_input_type, + $intersection_container_type, + $intersection_container_type_lower, + $container_was_static, + $allow_interface_equality, + $atomic_comparison_result + )) { + $any_inputs_contained = true; + } + } - if (!$g instanceof TNamedObject) { - continue 2; - } + if (!$any_inputs_contained) { + return false; + } + } - $intersection_input_type_lower = strtolower($g->value); - } + return true; + } - if ($intersection_input_type_lower === null) { - return false; + /** + * @param TNamedObject|TTemplateParam|TIterable $type_part + * @return array + */ + private static function getIntersectionTypes(Atomic $type_part): array + { + if (!$type_part->extra_types) { + if ($type_part instanceof TTemplateParam) { + $intersection_types = []; + + foreach ($type_part->as->getAtomicTypes() as $as_atomic_type) { + // T1 as T2 as object becomes (T1 as object) & (T2 as object) + if ($as_atomic_type instanceof TTemplateParam) { + $intersection_types += self::getIntersectionTypes($as_atomic_type); + $type_part = clone $type_part; + $type_part->as = $as_atomic_type->as; + $intersection_types[$type_part->getKey()] = $type_part; + + return $intersection_types; } - } else { - $input_was_static = $intersection_input_type->was_static; - - $intersection_input_type_lower = strtolower( - $codebase->classlikes->getUnAliasedName( - $intersection_input_type->value - ) - ); } + } - if ($intersection_container_type instanceof TTemplateParam - && $intersection_input_type instanceof TTemplateParam + return [$type_part->getKey() => $type_part]; + } + + $type_part = clone $type_part; + + $extra_types = $type_part->extra_types; + $type_part->extra_types = null; + + $extra_types[$type_part->getKey()] = $type_part; + + return $extra_types; + } + + /** + * @param TNamedObject|TTemplateParam|TIterable|TObjectWithProperties $intersection_input_type + * @param TNamedObject|TTemplateParam|TIterable|TObjectWithProperties $intersection_container_type + * + */ + private static function isIntersectionShallowlyContainedBy( + Codebase $codebase, + Atomic $intersection_input_type, + Atomic $intersection_container_type, + ?string $intersection_container_type_lower, + bool $container_was_static, + bool $allow_interface_equality, + ?TypeComparisonResult $atomic_comparison_result + ): bool { + if ($intersection_container_type instanceof TTemplateParam + && $intersection_input_type instanceof TTemplateParam + ) { + if (!$allow_interface_equality) { + if (strpos($intersection_container_type->defining_class, 'fn-') === 0 + || strpos($intersection_input_type->defining_class, 'fn-') === 0 ) { - if ($intersection_container_type->param_name !== $intersection_input_type->param_name - || ($intersection_container_type->defining_class - !== $intersection_input_type->defining_class - && strpos($intersection_input_type->defining_class, 'fn-') !== 0 - && strpos($intersection_container_type->defining_class, 'fn-') !== 0) + if (strpos($intersection_input_type->defining_class, 'fn-') === 0 + && strpos($intersection_container_type->defining_class, 'fn-') === 0 + && $intersection_input_type->defining_class + !== $intersection_container_type->defining_class ) { - if (strpos($intersection_input_type->defining_class, 'fn-') !== 0) { - $input_class_storage = $codebase->classlike_storage_provider->get( - $intersection_input_type->defining_class - ); - - if (isset($input_class_storage->template_extended_params - [$intersection_container_type->defining_class] - [$intersection_container_type->param_name]) - ) { - continue; - } - } + return true; + } - return false; + foreach ($intersection_input_type->as->getAtomicTypes() as $input_as_atomic) { + if ($input_as_atomic->equals($intersection_container_type, false)) { + return true; + } } } + } - if (!$intersection_container_type instanceof TTemplateParam - || $intersection_input_type instanceof TTemplateParam + if ($intersection_container_type->param_name === $intersection_input_type->param_name + && $intersection_container_type->defining_class === $intersection_input_type->defining_class + ) { + return true; + } + + if ($intersection_container_type->param_name !== $intersection_input_type->param_name + || ($intersection_container_type->defining_class + !== $intersection_input_type->defining_class + && strpos($intersection_input_type->defining_class, 'fn-') !== 0 + && strpos($intersection_container_type->defining_class, 'fn-') !== 0) + ) { + if (strpos($intersection_input_type->defining_class, 'fn-') === 0 + || strpos($intersection_container_type->defining_class, 'fn-') === 0 ) { - if ($intersection_container_type_lower === $intersection_input_type_lower) { - if ($container_was_static - && !$input_was_static - && !$intersection_input_type instanceof TTemplateParam - ) { - if ($atomic_comparison_result) { - $atomic_comparison_result->type_coerced = true; - } - - continue; - } + return false; + } - continue 2; - } + $input_class_storage = $codebase->classlike_storage_provider->get( + $intersection_input_type->defining_class + ); - if ($intersection_input_type_lower === 'generator' - && in_array($intersection_container_type_lower, ['iterator', 'traversable', 'iterable'], true) - ) { - continue 2; - } + if (isset($input_class_storage->template_extended_params + [$intersection_container_type->defining_class] + [$intersection_container_type->param_name]) + ) { + return true; + } + } - if ($intersection_container_type_lower === 'iterable') { - if ($intersection_input_type_lower === 'traversable' - || ($codebase->classlikes->classExists($intersection_input_type_lower) - && $codebase->classlikes->classImplements( - $intersection_input_type_lower, - 'Traversable' - )) - || ($codebase->classlikes->interfaceExists($intersection_input_type_lower) - && $codebase->classlikes->interfaceExtends( - $intersection_input_type_lower, - 'Traversable' - )) - ) { - continue 2; - } - } + return false; + } - if ($intersection_input_type_lower === 'traversable' - && $intersection_container_type_lower === 'iterable' - ) { - continue 2; - } + if ($intersection_container_type instanceof TTemplateParam + || $intersection_container_type_lower === null + ) { + return false; + } - $input_type_is_interface = $codebase->interfaceExists($intersection_input_type_lower); - $container_type_is_interface = $codebase->interfaceExists($intersection_container_type_lower); + if ($intersection_input_type instanceof TTemplateParam) { + $intersection_container_type = clone $intersection_container_type; - if ($allow_interface_equality - && $container_type_is_interface - && ($input_type_is_interface || !isset($intersection_container_types[$intersection_input_key])) - ) { - continue 2; - } + if ($intersection_container_type instanceof TNamedObject) { + // this is extra check is redundant since we're comparing to a template as type + $intersection_container_type->is_static = false; + } - if (($codebase->classExists($intersection_input_type_lower) - || $codebase->classlikes->enumExists($intersection_input_type_lower)) - && $codebase->classOrInterfaceExists($intersection_container_type_lower) - && $codebase->classExtendsOrImplements( - $intersection_input_type_lower, - $intersection_container_type_lower - ) - ) { - if ($container_was_static && !$input_was_static) { - if ($atomic_comparison_result) { - $atomic_comparison_result->type_coerced = true; - } + return UnionTypeComparator::isContainedBy( + $codebase, + $intersection_input_type->as, + new Union([$intersection_container_type]), + false, + false, + $atomic_comparison_result, + $allow_interface_equality + ); + } - continue; - } + $input_was_static = false; - continue 2; - } + if ($intersection_input_type instanceof TIterable) { + $intersection_input_type_lower = 'iterable'; + } elseif ($intersection_input_type instanceof TObjectWithProperties) { + $intersection_input_type_lower = 'object'; + } else { + $input_was_static = $intersection_input_type->is_static; - if ($input_type_is_interface - && $codebase->interfaceExtends( - $intersection_input_type_lower, - $intersection_container_type_lower - ) - ) { - continue 2; - } + $intersection_input_type_lower = strtolower( + $codebase->classlikes->getUnAliasedName( + $intersection_input_type->value + ) + ); + } + + if ($intersection_container_type_lower === $intersection_input_type_lower) { + if ($container_was_static && !$input_was_static) { + if ($atomic_comparison_result) { + $atomic_comparison_result->type_coerced = true; } - if (ExpressionAnalyzer::isMock($intersection_input_type_lower)) { - return true; + return false; + } + + return true; + } + + if ($intersection_input_type_lower === 'generator' + && in_array($intersection_container_type_lower, ['iterator', 'traversable', 'iterable'], true) + ) { + return true; + } + + if ($intersection_container_type_lower === 'iterable') { + if ($intersection_input_type_lower === 'traversable' + || ($codebase->classlikes->classExists($intersection_input_type_lower) + && $codebase->classlikes->classImplements( + $intersection_input_type_lower, + 'Traversable' + )) + || ($codebase->classlikes->interfaceExists($intersection_input_type_lower) + && $codebase->classlikes->interfaceExtends( + $intersection_input_type_lower, + 'Traversable' + )) + ) { + return true; + } + } + + if ($intersection_input_type_lower === 'traversable' + && $intersection_container_type_lower === 'iterable' + ) { + return true; + } + + $input_type_is_interface = $codebase->interfaceExists($intersection_input_type_lower); + $container_type_is_interface = $codebase->interfaceExists($intersection_container_type_lower); + + if ($allow_interface_equality + && $container_type_is_interface + && $input_type_is_interface + ) { + return true; + } + + if (($codebase->classExists($intersection_input_type_lower) + || $codebase->classlikes->enumExists($intersection_input_type_lower)) + && $codebase->classOrInterfaceExists($intersection_container_type_lower) + && $codebase->classExtendsOrImplements( + $intersection_input_type_lower, + $intersection_container_type_lower + ) + ) { + if ($container_was_static && !$input_was_static) { + if ($atomic_comparison_result) { + $atomic_comparison_result->type_coerced = true; } + + return false; } - return false; + return true; } - return true; + if ($input_type_is_interface + && $codebase->interfaceExtends( + $intersection_input_type_lower, + $intersection_container_type_lower + ) + ) { + return true; + } + + if (ExpressionAnalyzer::isMock($intersection_input_type_lower)) { + return true; + } + + return false; } } diff --git a/src/Psalm/Internal/Type/Comparator/ScalarTypeComparator.php b/src/Psalm/Internal/Type/Comparator/ScalarTypeComparator.php index 12b1dd951da..c387835375e 100644 --- a/src/Psalm/Internal/Type/Comparator/ScalarTypeComparator.php +++ b/src/Psalm/Internal/Type/Comparator/ScalarTypeComparator.php @@ -16,7 +16,6 @@ use Psalm\Type\Atomic\TDependentListKey; use Psalm\Type\Atomic\TFalse; use Psalm\Type\Atomic\TFloat; -use Psalm\Type\Atomic\THtmlEscapedString; use Psalm\Type\Atomic\TInt; use Psalm\Type\Atomic\TIntRange; use Psalm\Type\Atomic\TLiteralClassString; @@ -27,6 +26,7 @@ use Psalm\Type\Atomic\TNamedObject; use Psalm\Type\Atomic\TNonEmptyLowercaseString; use Psalm\Type\Atomic\TNonEmptyNonspecificLiteralString; +use Psalm\Type\Atomic\TNonEmptyScalar; use Psalm\Type\Atomic\TNonEmptyString; use Psalm\Type\Atomic\TNonFalsyString; use Psalm\Type\Atomic\TNonspecificLiteralInt; @@ -297,7 +297,7 @@ public static function isContainedBy( if ($atomic_comparison_result) { $atomic_comparison_result->type_coerced = true; $atomic_comparison_result->type_coerced_from_mixed = true; - $atomic_comparison_result->scalar_type_match_found = true; + $atomic_comparison_result->scalar_type_match_found = !$container_type_part->from_docblock; } return false; @@ -535,8 +535,7 @@ public static function isContainedBy( } if ($container_type_part instanceof TString - && ($input_type_part instanceof TNumericString - || $input_type_part instanceof THtmlEscapedString) + && $input_type_part instanceof TNumericString ) { if ($container_type_part instanceof TLiteralString) { if (is_numeric($container_type_part->value) && $atomic_comparison_result) { @@ -550,8 +549,7 @@ public static function isContainedBy( } if ($input_type_part instanceof TString - && ($container_type_part instanceof TNumericString - || $container_type_part instanceof THtmlEscapedString) + && $container_type_part instanceof TNumericString ) { if ($input_type_part instanceof TLiteralString) { return is_numeric($input_type_part->value); @@ -619,7 +617,8 @@ public static function isContainedBy( if ($input_type_part instanceof TNumeric) { if ($container_type_part->isNumericType()) { if ($atomic_comparison_result) { - $atomic_comparison_result->scalar_type_match_found = true; + $atomic_comparison_result->type_coerced = true; + $atomic_comparison_result->scalar_type_match_found = !$container_type_part->from_docblock; } } } @@ -630,7 +629,10 @@ public static function isContainedBy( && !$container_type_part instanceof TLiteralFloat ) { if ($atomic_comparison_result) { - $atomic_comparison_result->scalar_type_match_found = true; + $atomic_comparison_result->type_coerced + = $atomic_comparison_result->type_coerced_from_scalar + = ($input_type_part instanceof TScalar || $input_type_part instanceof TNonEmptyScalar); + $atomic_comparison_result->scalar_type_match_found = !$container_type_part->from_docblock; } } } diff --git a/src/Psalm/Internal/Type/Comparator/TypeComparisonResult.php b/src/Psalm/Internal/Type/Comparator/TypeComparisonResult.php index 5fbe29af824..05bdb46c9f8 100644 --- a/src/Psalm/Internal/Type/Comparator/TypeComparisonResult.php +++ b/src/Psalm/Internal/Type/Comparator/TypeComparisonResult.php @@ -5,9 +5,17 @@ use Psalm\Type\Atomic; use Psalm\Type\Union; +/** + * @internal + */ class TypeComparisonResult { - /** @var ?bool */ + /** + * This is used to trigger `InvalidScalarArgument` in situations where we know PHP + * will try to coerce one scalar type to another. + * + * @var ?bool + */ public $scalar_type_match_found; /** @var ?bool */ diff --git a/src/Psalm/Internal/Type/Comparator/UnionTypeComparator.php b/src/Psalm/Internal/Type/Comparator/UnionTypeComparator.php index c997199d650..288f3e25845 100644 --- a/src/Psalm/Internal/Type/Comparator/UnionTypeComparator.php +++ b/src/Psalm/Internal/Type/Comparator/UnionTypeComparator.php @@ -484,12 +484,6 @@ private static function getTypeParts( true, true ); - if ($expanded instanceof Atomic) { - if (!$expanded instanceof TTypeAlias && !$expanded instanceof TClassConstant) { - $atomic_types[] = $expanded; - } - continue; - } array_push($atomic_types, ...$expanded); } diff --git a/src/Psalm/Internal/Type/NegatedAssertionReconciler.php b/src/Psalm/Internal/Type/NegatedAssertionReconciler.php index de15a545e88..921ba039893 100644 --- a/src/Psalm/Internal/Type/NegatedAssertionReconciler.php +++ b/src/Psalm/Internal/Type/NegatedAssertionReconciler.php @@ -29,6 +29,9 @@ use function strtolower; use function substr; +/** + * @internal + */ class NegatedAssertionReconciler extends Reconciler { /** @@ -77,7 +80,6 @@ public static function reconcile( return $existing_var_type; } - $existing_var_atomic_types = $existing_var_type->getAtomicTypes(); if ($assertion === 'false' && isset($existing_var_atomic_types['bool'])) { @@ -174,39 +176,37 @@ public static function reconcile( // if there wasn't a direct hit, go deeper, eliminating subtypes if (!$existing_var_type->removeType($assertion)) { - foreach ($existing_var_type->getAtomicTypes() as $part_name => $existing_var_type_part) { - if (!$existing_var_type_part->isObjectType() || strpos($assertion, '-')) { - continue; - } - + if (!strpos($assertion, '-')) { $assertion_type = Type::parseString($assertion, null, $template_type_map); - if (!$assertion_type->isSingle()) { - continue; - } - - $new_type_part = $assertion_type->getSingleAtomic(); - - if (!$new_type_part instanceof TNamedObject) { - continue; - } - - if (AtomicTypeComparator::isContainedBy( - $codebase, - $existing_var_type_part, - $new_type_part, - false, - false - )) { - $existing_var_type->removeType($part_name); - } elseif (AtomicTypeComparator::isContainedBy( - $codebase, - $new_type_part, - $existing_var_type_part, - false, - false - )) { - $existing_var_type->different = true; + if ($assertion_type->isSingle()) { + $new_type_part = $assertion_type->getSingleAtomic(); + + if ($new_type_part instanceof TNamedObject) { + foreach ($existing_var_type->getAtomicTypes() as $part_name => $existing_var_type_part) { + if (!$existing_var_type_part->isObjectType()) { + continue; + } + + if (AtomicTypeComparator::isContainedBy( + $codebase, + $existing_var_type_part, + $new_type_part, + false, + false + )) { + $existing_var_type->removeType($part_name); + } elseif (AtomicTypeComparator::isContainedBy( + $codebase, + $new_type_part, + $existing_var_type_part, + false, + false + )) { + $existing_var_type->different = true; + } + } + } } } } diff --git a/src/Psalm/Internal/Type/ParseTreeCreator.php b/src/Psalm/Internal/Type/ParseTreeCreator.php index edad53cb928..0ee5d9b329f 100644 --- a/src/Psalm/Internal/Type/ParseTreeCreator.php +++ b/src/Psalm/Internal/Type/ParseTreeCreator.php @@ -751,6 +751,16 @@ private function handleValue(array $type_token): void $new_parent ); ++$this->t; + + $nexter_token = $this->t + 1 < $this->type_token_count ? $this->type_tokens[$this->t + 1] : null; + + if ($nexter_token !== null && $nexter_token[0] === '}') { + $new_leaf->terminated = true; + ++$this->t; + } elseif ($nexter_token === null) { + throw new TypeParseTreeException('Unclosed bracket in keyed array'); + } + break; case '(': diff --git a/src/Psalm/Internal/Type/SimpleAssertionReconciler.php b/src/Psalm/Internal/Type/SimpleAssertionReconciler.php index 9d75c590687..bdba8913e1e 100644 --- a/src/Psalm/Internal/Type/SimpleAssertionReconciler.php +++ b/src/Psalm/Internal/Type/SimpleAssertionReconciler.php @@ -19,7 +19,6 @@ use Psalm\Type\Atomic\TCallableObject; use Psalm\Type\Atomic\TCallableString; use Psalm\Type\Atomic\TClassString; -use Psalm\Type\Atomic\TEmpty; use Psalm\Type\Atomic\TEmptyMixed; use Psalm\Type\Atomic\TEmptyNumeric; use Psalm\Type\Atomic\TEmptyScalar; @@ -68,6 +67,8 @@ * This class receives a known type and an assertion (probably coming from AssertionFinder). The goal is to refine * the known type using the assertion. For example: old type is `int` assertion is `>5` result is `int<6, max>`. * Complex reconciliation takes part in AssertionReconciler if this class couldn't handle the reconciliation + * + * @internal */ class SimpleAssertionReconciler extends Reconciler { @@ -521,13 +522,15 @@ private static function reconcileIsset( if ($existing_var_type->isUnionEmpty()) { $failed_reconciliation = Reconciler::RECONCILIATION_EMPTY; - return Type::getEmpty(); + return Type::getNever(); } } - if ($existing_var_type->hasType('empty')) { - $existing_var_type->removeType('empty'); - $existing_var_type->addType(new TMixed($inside_loop)); + if ($inside_loop) { + if ($existing_var_type->hasType('never')) { + $existing_var_type->removeType('never'); + $existing_var_type->addType(new TMixed(true)); + } } $existing_var_type->from_property = false; @@ -563,7 +566,7 @@ private static function reconcileNonEmptyCountable( if (!$array_atomic_type instanceof TNonEmptyArray || ($array_atomic_type->count < $min_count) ) { - if ($array_atomic_type->getId() === 'array') { + if ($array_atomic_type->isEmptyArray()) { $existing_var_type->removeType('array'); } else { $non_empty_array = new TNonEmptyArray( @@ -736,7 +739,7 @@ private static function reconcilePositiveNumeric( $failed_reconciliation = Reconciler::RECONCILIATION_EMPTY; - return Type::getEmpty(); + return Type::getNever(); } /** @@ -935,7 +938,7 @@ private static function reconcileString( return $existing_var_type->from_docblock ? Type::getMixed() - : Type::getEmpty(); + : Type::getNever(); } /** @@ -1033,7 +1036,7 @@ private static function reconcileInt( return $existing_var_type->from_docblock ? Type::getMixed() - : Type::getEmpty(); + : Type::getNever(); } /** @@ -1112,7 +1115,7 @@ private static function reconcileBool( return $existing_var_type->from_docblock ? Type::getMixed() - : Type::getEmpty(); + : Type::getNever(); } /** @@ -1187,7 +1190,7 @@ private static function reconcileScalar( return $existing_var_type->from_docblock ? Type::getMixed() - : Type::getEmpty(); + : Type::getNever(); } /** @@ -1279,7 +1282,7 @@ private static function reconcileNumeric( return $existing_var_type->from_docblock ? Type::getMixed() - : Type::getEmpty(); + : Type::getNever(); } /** @@ -1372,7 +1375,7 @@ private static function reconcileObject( return $existing_var_type->from_docblock ? Type::getMixed() - : Type::getEmpty(); + : Type::getNever(); } /** @@ -1429,7 +1432,7 @@ private static function reconcileResource( return $existing_var_type->from_docblock ? Type::getMixed() - : Type::getEmpty(); + : Type::getNever(); } /** @@ -1904,7 +1907,7 @@ private static function reconcileTraversable( return $existing_var_type->from_docblock ? Type::getMixed() - : Type::getEmpty(); + : Type::getNever(); } /** @@ -1999,7 +2002,7 @@ private static function reconcileArray( return $existing_var_type->from_docblock ? Type::getMixed() - : Type::getEmpty(); + : Type::getNever(); } /** @@ -2052,9 +2055,7 @@ private static function reconcileList( } } - if ($type->type_params[0]->isEmpty() - || $type->type_params[1]->isEmpty() - ) { + if ($type->isEmptyArray()) { //we allow an empty array to pass as a list. We keep the type as empty array though (more precise) $array_types[] = $type; } @@ -2104,7 +2105,7 @@ private static function reconcileList( return $existing_var_type->from_docblock ? Type::getMixed() - : Type::getEmpty(); + : Type::getNever(); } /** @@ -2392,7 +2393,7 @@ private static function reconcileFalsyOrEmpty( $failed_reconciliation = 2; - return Type::getEmpty(); + return Type::getNever(); } if (!$did_remove_type) { @@ -2424,8 +2425,8 @@ private static function reconcileFalsyOrEmpty( $existing_var_type->removeType('array'); $existing_var_type->addType(new TArray( [ - new Union([new TEmpty()]), - new Union([new TEmpty()]), + new Union([new TNever()]), + new Union([new TNever()]), ] )); } diff --git a/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php b/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php index 71859b271da..0cb4b04a771 100644 --- a/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php +++ b/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php @@ -18,7 +18,6 @@ use Psalm\Type\Atomic\TCallableArray; use Psalm\Type\Atomic\TCallableObject; use Psalm\Type\Atomic\TCallableString; -use Psalm\Type\Atomic\TEmpty; use Psalm\Type\Atomic\TFloat; use Psalm\Type\Atomic\TGenericObject; use Psalm\Type\Atomic\TInt; @@ -56,6 +55,9 @@ use function strpos; use function substr; +/** + * @internal + */ class SimpleNegatedAssertionReconciler extends Reconciler { /** @@ -78,7 +80,7 @@ public static function reconcile( if ($assertion === 'isset') { if ($existing_var_type->possibly_undefined) { - return Type::getEmpty(); + return Type::getNever(); } if (!$existing_var_type->isNullable() @@ -131,14 +133,14 @@ public static function reconcile( return $existing_var_type->from_docblock ? Type::getNull() - : Type::getEmpty(); + : Type::getNever(); } return Type::getNull(); } if ($assertion === 'array-key-exists') { - return Type::getEmpty(); + return Type::getNever(); } if (strpos($assertion, 'in-array-') === 0) { @@ -496,14 +498,14 @@ private static function reconcileNonEmptyCountable( $did_remove_type = true; $existing_var_type->removeType('array'); - } elseif ($array_atomic_type->getId() !== 'array') { + } elseif (!($array_atomic_type instanceof TArray && $array_atomic_type->isEmptyArray())) { $did_remove_type = true; if (!$min_count) { $existing_var_type->addType(new TArray( [ - new Union([new TEmpty]), - new Union([new TEmpty]), + new Union([new TNever()]), + new Union([new TNever()]), ] )); } @@ -725,7 +727,7 @@ private static function reconcileFalsyOrEmpty( $failed_reconciliation = 2; - return Type::getEmpty(); + return Type::getNever(); } if (!$did_remove_type) { diff --git a/src/Psalm/Internal/Type/TemplateBound.php b/src/Psalm/Internal/Type/TemplateBound.php index da8da461acf..f454200589d 100644 --- a/src/Psalm/Internal/Type/TemplateBound.php +++ b/src/Psalm/Internal/Type/TemplateBound.php @@ -4,6 +4,9 @@ use Psalm\Type\Union; +/** + * @internal + */ class TemplateBound { /** diff --git a/src/Psalm/Internal/Type/TemplateInferredTypeReplacer.php b/src/Psalm/Internal/Type/TemplateInferredTypeReplacer.php index d0aff71b2f6..38e542d926e 100644 --- a/src/Psalm/Internal/Type/TemplateInferredTypeReplacer.php +++ b/src/Psalm/Internal/Type/TemplateInferredTypeReplacer.php @@ -30,6 +30,9 @@ use function array_values; use function strpos; +/** + * @internal + */ class TemplateInferredTypeReplacer { /** diff --git a/src/Psalm/Internal/Type/TemplateResult.php b/src/Psalm/Internal/Type/TemplateResult.php index c213eb59c08..b4d2c17986e 100644 --- a/src/Psalm/Internal/Type/TemplateResult.php +++ b/src/Psalm/Internal/Type/TemplateResult.php @@ -20,6 +20,8 @@ * parameters — given a parameter type `callable(): T2` and an argument typed as * `callable(): string`, `string` will be added as a _lower_ bound for the template * param `T2`. + * + * @internal */ class TemplateResult { @@ -59,14 +61,10 @@ public function __construct(array $template_types, array $lower_bounds) $this->template_types = $template_types; $this->lower_bounds = array_map( - function ($type_map) { - return array_map( - function ($type) { - return [new TemplateBound($type)]; - }, - $type_map - ); - }, + fn($type_map) => array_map( + fn($type) => [new TemplateBound($type)], + $type_map + ), $lower_bounds ); } diff --git a/src/Psalm/Internal/Type/TemplateStandinTypeReplacer.php b/src/Psalm/Internal/Type/TemplateStandinTypeReplacer.php index 3791dc03e4a..1903999ac1a 100644 --- a/src/Psalm/Internal/Type/TemplateStandinTypeReplacer.php +++ b/src/Psalm/Internal/Type/TemplateStandinTypeReplacer.php @@ -45,6 +45,9 @@ use function substr; use function usort; +/** + * @internal + */ class TemplateStandinTypeReplacer { /** @@ -1084,9 +1087,7 @@ public static function getMostSpecificTypeFromBounds(array $lower_bounds, ?Codeb usort( $lower_bounds, - function (TemplateBound $bound_a, TemplateBound $bound_b) { - return $bound_b->appearance_depth <=> $bound_a->appearance_depth; - } + fn(TemplateBound $bound_a, TemplateBound $bound_b) => $bound_b->appearance_depth <=> $bound_a->appearance_depth ); $current_depth = null; @@ -1098,7 +1099,7 @@ function (TemplateBound $bound_a, TemplateBound $bound_b) { if ($current_depth === null) { $current_depth = $template_bound->appearance_depth; } elseif ($current_depth !== $template_bound->appearance_depth && $current_type) { - if (!$current_type->isEmpty() + if (!$current_type->isNever() && ($had_invariant || $last_arg_offset === $template_bound->arg_offset) ) { // escape switches when matching on invariant generic params diff --git a/src/Psalm/Internal/Type/TypeAlias/ClassTypeAlias.php b/src/Psalm/Internal/Type/TypeAlias/ClassTypeAlias.php index 5f5146e98a9..6b7842ebc16 100644 --- a/src/Psalm/Internal/Type/TypeAlias/ClassTypeAlias.php +++ b/src/Psalm/Internal/Type/TypeAlias/ClassTypeAlias.php @@ -5,6 +5,9 @@ use Psalm\Internal\Type\TypeAlias; use Psalm\Type\Atomic; +/** + * @internal + */ class ClassTypeAlias implements TypeAlias { /** diff --git a/src/Psalm/Internal/Type/TypeAlias/InlineTypeAlias.php b/src/Psalm/Internal/Type/TypeAlias/InlineTypeAlias.php index 9ed473c6f3c..1e1f99fe320 100644 --- a/src/Psalm/Internal/Type/TypeAlias/InlineTypeAlias.php +++ b/src/Psalm/Internal/Type/TypeAlias/InlineTypeAlias.php @@ -6,6 +6,8 @@ /** * @psalm-immutable + * + * @internal */ class InlineTypeAlias implements TypeAlias { diff --git a/src/Psalm/Internal/Type/TypeAlias/LinkableTypeAlias.php b/src/Psalm/Internal/Type/TypeAlias/LinkableTypeAlias.php index a0c309dd556..bdd13819e6c 100644 --- a/src/Psalm/Internal/Type/TypeAlias/LinkableTypeAlias.php +++ b/src/Psalm/Internal/Type/TypeAlias/LinkableTypeAlias.php @@ -6,6 +6,8 @@ /** * @psalm-immutable + * + * @internal */ class LinkableTypeAlias implements TypeAlias { diff --git a/src/Psalm/Internal/Type/TypeCombiner.php b/src/Psalm/Internal/Type/TypeCombiner.php index 99fbeffff30..1e4269478c0 100644 --- a/src/Psalm/Internal/Type/TypeCombiner.php +++ b/src/Psalm/Internal/Type/TypeCombiner.php @@ -17,7 +17,6 @@ use Psalm\Type\Atomic\TCallableString; use Psalm\Type\Atomic\TClassString; use Psalm\Type\Atomic\TClassStringMap; -use Psalm\Type\Atomic\TEmpty; use Psalm\Type\Atomic\TEmptyMixed; use Psalm\Type\Atomic\TFalse; use Psalm\Type\Atomic\TFloat; @@ -71,6 +70,9 @@ use function strtolower; use function substr; +/** + * @internal + */ class TypeCombiner { /** @@ -78,8 +80,8 @@ class TypeCombiner * - so `int + string = int|string` * - so `array + array = array` * - and `array + string = array|string` - * - and `array + array = array` - * - and `array + array = array` + * - and `array + array = array` + * - and `array + array = array` * - and `array + array = array` * * @param non-empty-list $types @@ -180,8 +182,7 @@ public static function combine( && (isset($combination->named_object_types['Traversable']) || isset($combination->builtin_type_params['Traversable'])) && ( - ($codebase && $codebase->config->allow_phpstorm_generics) - || isset($combination->builtin_type_params['Traversable']) + isset($combination->builtin_type_params['Traversable']) || (isset($combination->named_object_types['Traversable']) && $combination->named_object_types['Traversable']->from_docblock) ) @@ -269,7 +270,7 @@ public static function combine( $generic_object = new TGenericObject($generic_type, $generic_type_params); if ($combination->object_static[$generic_type] ?? false) { - $generic_object->was_static = true; + $generic_object->is_static = true; } /** @psalm-suppress PropertyTypeCoercion */ @@ -336,20 +337,17 @@ public static function combine( $combination->value_types += $combination->named_object_types; } - $has_empty = (int) isset($combination->value_types['empty']); - $has_never = false; + $has_never = isset($combination->value_types['never']); foreach ($combination->value_types as $type) { if ($type instanceof TMixed && $combination->mixed_from_loop_isset - && (count($combination->value_types) > (1 + $has_empty) || count($new_types) > $has_empty) + && (count($combination->value_types) > (1 + (int) $has_never) || count($new_types) > (int) $has_never) ) { continue; } - if (($type instanceof TEmpty || $type instanceof TNever) - && (count($combination->value_types) > 1 || count($new_types)) - ) { + if ($type instanceof TNever && (count($combination->value_types) > 1 || count($new_types))) { $has_never = true; continue; } @@ -515,11 +513,11 @@ private static function scrapeTypeProperties( if ($type instanceof TNamedObject) { if (array_key_exists($type->value, $combination->object_static)) { - if ($combination->object_static[$type->value] && !$type->was_static) { + if ($combination->object_static[$type->value] && !$type->is_static) { $combination->object_static[$type->value] = false; } } else { - $combination->object_static[$type->value] = $type->was_static; + $combination->object_static[$type->value] = $type->is_static; } } @@ -552,7 +550,7 @@ private static function scrapeTypeProperties( $combination->array_always_filled = false; } - if (!$type->type_params[1]->isEmpty()) { + if (!$type->isEmptyArray()) { $combination->all_arrays_lists = false; $combination->all_arrays_class_string_maps = false; } @@ -1129,9 +1127,7 @@ private static function scrapeIntProperties( $all_nonnegative = !array_filter( $combination->ints, - function ($int): bool { - return $int->value < 0; - } + fn($int): bool => $int->value < 0 ); if (isset($combination->value_types['int'])) { @@ -1169,9 +1165,7 @@ function ($int): bool { if ($combination->ints) { $all_nonnegative = !array_filter( $combination->ints, - function ($int): bool { - return $int->value < 0; - } + fn($int): bool => $int->value < 0 ); if ($all_nonnegative) { @@ -1330,11 +1324,11 @@ private static function handleKeyedArrayEntries( } if (!$combination->array_type_params - || $combination->array_type_params[1]->isEmpty() + || $combination->array_type_params[1]->isNever() ) { if (!$overwrite_empty_array && ($combination->array_type_params - && ($combination->array_type_params[1]->isEmpty() + && ($combination->array_type_params[1]->isNever() || $combination->array_type_params[1]->isMixed())) ) { foreach ($combination->objectlike_entries as $objectlike_entry) { @@ -1347,9 +1341,7 @@ private static function handleKeyedArrayEntries( ) { $combination->objectlike_entries = array_filter( $combination->objectlike_entries, - function (Union $type): bool { - return !$type->possibly_undefined; - } + fn(Union $type): bool => !$type->possibly_undefined ); } diff --git a/src/Psalm/Internal/Type/TypeExpander.php b/src/Psalm/Internal/Type/TypeExpander.php index 3709534e47e..a26d37dcd32 100644 --- a/src/Psalm/Internal/Type/TypeExpander.php +++ b/src/Psalm/Internal/Type/TypeExpander.php @@ -14,7 +14,6 @@ use Psalm\Type\Atomic\TClassString; use Psalm\Type\Atomic\TClosure; use Psalm\Type\Atomic\TConditional; -use Psalm\Type\Atomic\TEmpty; use Psalm\Type\Atomic\TGenericObject; use Psalm\Type\Atomic\TInt; use Psalm\Type\Atomic\TIntMask; @@ -43,7 +42,6 @@ use function array_values; use function count; use function get_class; -use function is_array; use function is_string; use function reset; use function strpos; @@ -75,7 +73,7 @@ public static function expandUnion( $new_return_type_parts = []; - $has_array_output = false; + $had_split_values = false; foreach ($return_type->getAtomicTypes() as $return_type_part) { $parts = self::expandAtomic( @@ -91,15 +89,14 @@ public static function expandUnion( $expand_templates ); - if (is_array($parts)) { - $new_return_type_parts = array_merge($new_return_type_parts, $parts); - $has_array_output = true; - } else { - $new_return_type_parts[] = $parts; + if ($return_type_part instanceof TTypeAlias || count($parts) > 1) { + $had_split_values = true; } + + $new_return_type_parts = array_merge($new_return_type_parts, $parts); } - if ($has_array_output) { + if ($had_split_values) { $fleshed_out_type = TypeCombiner::combine( $new_return_type_parts, $codebase @@ -126,7 +123,7 @@ public static function expandUnion( /** * @param string|TNamedObject|TTemplateParam|null $static_class_type * - * @return Atomic|non-empty-list + * @return non-empty-list */ public static function expandAtomic( Codebase $codebase, @@ -139,7 +136,7 @@ public static function expandAtomic( bool $final = false, bool $expand_generic = false, bool $expand_templates = false - ) { + ): array { if ($return_type instanceof TNamedObject || $return_type instanceof TTemplateParam ) { @@ -240,7 +237,7 @@ public static function expandAtomic( if ($evaluate_class_constants && $codebase->classOrInterfaceOrEnumExists($return_type->fq_classlike_name)) { if (strtolower($return_type->const_name) === 'class') { - return new TLiteralClassString($return_type->fq_classlike_name); + return [new TLiteralClassString($return_type->fq_classlike_name)]; } $class_storage = $codebase->classlike_storage_provider->get($return_type->fq_classlike_name); @@ -256,10 +253,8 @@ public static function expandAtomic( if ($const_name_part) { $matching_constants = array_filter( $matching_constants, - function ($constant_name) use ($const_name_part): bool { - return $constant_name !== $const_name_part - && strpos($constant_name, $const_name_part) === 0; - } + fn($constant_name): bool => $constant_name !== $const_name_part + && strpos($constant_name, $const_name_part) === 0 ); } } else { @@ -296,7 +291,7 @@ function ($constant_name) use ($const_name_part): bool { } } - return $return_type; + return [$return_type]; } if ($return_type instanceof TTypeAlias) { @@ -320,7 +315,7 @@ function ($constant_name) use ($const_name_part): bool { $recursively_fleshed_out_types = []; foreach ($replacement_atomic_types as $replacement_atomic_type) { - $recursively_fleshed_out_type = self::expandAtomic( + $more_recursively_fleshed_out_types = self::expandAtomic( $codebase, $replacement_atomic_type, $self_class, @@ -333,14 +328,10 @@ function ($constant_name) use ($const_name_part): bool { $expand_templates ); - if (is_array($recursively_fleshed_out_type)) { - $recursively_fleshed_out_types = array_merge( - $recursively_fleshed_out_type, - $recursively_fleshed_out_types - ); - } else { - $recursively_fleshed_out_types[] = $recursively_fleshed_out_type; - } + $recursively_fleshed_out_types = array_merge( + $more_recursively_fleshed_out_types, + $recursively_fleshed_out_types + ); } return $recursively_fleshed_out_types; @@ -348,7 +339,7 @@ function ($constant_name) use ($const_name_part): bool { } } - return $return_type; + return [$return_type]; } if ($return_type instanceof TKeyOfClassConstant @@ -388,12 +379,12 @@ function ($constant_name) use ($const_name_part): bool { } } - return $return_type; + return [$return_type]; } if ($return_type instanceof TIntMask) { if (!$evaluate_class_constants) { - return new TInt(); + return [new TInt()]; } $potential_ints = []; @@ -412,12 +403,10 @@ function ($constant_name) use ($const_name_part): bool { $expand_templates ); - if (is_array($new_value_type)) { - $new_value_type = reset($new_value_type); - } + $new_value_type = reset($new_value_type); if (!$new_value_type instanceof TLiteralInt) { - return new TInt(); + return [new TInt()]; } $potential_ints[] = $new_value_type->value; @@ -428,7 +417,7 @@ function ($constant_name) use ($const_name_part): bool { if ($return_type instanceof TIntMaskOf) { if (!$evaluate_class_constants) { - return new TInt(); + return [new TInt()]; } $value_type = $return_type->value; @@ -446,15 +435,11 @@ function ($constant_name) use ($const_name_part): bool { $expand_templates ); - if (!is_array($new_value_types)) { - return new TInt(); - } - $potential_ints = []; foreach ($new_value_types as $new_value_type) { if (!$new_value_type instanceof TLiteralInt) { - return new TInt(); + return [new TInt()]; } $potential_ints[] = $new_value_type->value; @@ -581,7 +566,7 @@ function ($constant_name) use ($const_name_part): bool { ); } - return $return_type; + return [$return_type]; } /** @@ -610,18 +595,14 @@ private static function expandNamedObject( if ($container_class_storage->template_types && array_filter( $container_class_storage->template_types, - function ($type_map) { - return !reset($type_map)->hasMixed(); - } + fn($type_map) => !reset($type_map)->hasMixed() ) ) { $return_type = new TGenericObject( $return_type->value, array_values( array_map( - function ($type_map) { - return clone reset($type_map); - }, + fn($type_map) => clone reset($type_map), $container_class_storage->template_types ) ) @@ -648,9 +629,9 @@ function ($type_map) { } if (!$final && $return_type instanceof TNamedObject) { - $return_type->was_static = true; + $return_type->is_static = true; } - } elseif ($return_type->was_static + } elseif ($return_type->is_static && ($static_class_type instanceof TNamedObject || $static_class_type instanceof TTemplateParam) ) { @@ -668,9 +649,9 @@ function ($type_map) { $return_type->extra_types[$extra_static_type->getKey()] = clone $extra_static_type; } } - } elseif ($return_type->was_static && is_string($static_class_type) && $final) { + } elseif ($return_type->is_static && is_string($static_class_type) && $final) { $return_type->value = $static_class_type; - $return_type->was_static = false; + $return_type->is_static = false; } elseif ($self_class && $return_type_lc === 'self') { $return_type->value = $self_class; } elseif ($parent_class && $return_type_lc === 'parent') { @@ -685,7 +666,7 @@ function ($type_map) { /** * @param string|TNamedObject|TTemplateParam|null $static_class_type * - * @return Atomic|non-empty-list + * @return non-empty-list */ private static function expandConditional( Codebase $codebase, @@ -698,7 +679,7 @@ private static function expandConditional( bool $final = false, bool $expand_generic = false, bool $expand_templates = false - ) { + ): array { $new_as_type = self::expandUnion( $codebase, $return_type->as_type, @@ -732,8 +713,8 @@ private static function expandConditional( $expand_templates ); - if (!is_array($candidate)) { - $assertion = $candidate->getAssertionString(); + if (count($candidate) === 1) { + $assertion = $candidate[0]->getAssertionString(); } } } @@ -741,7 +722,7 @@ private static function expandConditional( $if_conditional_return_types = []; foreach ($return_type->if_type->getAtomicTypes() as $if_atomic_type) { - $candidate = self::expandAtomic( + $candidate_types = self::expandAtomic( $codebase, $if_atomic_type, $self_class, @@ -754,8 +735,6 @@ private static function expandConditional( $expand_templates ); - $candidate_types = is_array($candidate) ? $candidate : [$candidate]; - $if_conditional_return_types = array_merge( $if_conditional_return_types, $candidate_types @@ -765,7 +744,7 @@ private static function expandConditional( $else_conditional_return_types = []; foreach ($return_type->else_type->getAtomicTypes() as $else_atomic_type) { - $candidate = self::expandAtomic( + $candidate_types = self::expandAtomic( $codebase, $else_atomic_type, $self_class, @@ -778,8 +757,6 @@ private static function expandConditional( $expand_templates ); - $candidate_types = is_array($candidate) ? $candidate : [$candidate]; - $else_conditional_return_types = array_merge( $else_conditional_return_types, $candidate_types @@ -827,14 +804,11 @@ private static function expandConditional( ); $number_of_types = count($all_conditional_return_types); - // we filter TNever and TEmpty that have no bearing on the return type + // we filter TNever that have no bearing on the return type if ($number_of_types > 1) { $all_conditional_return_types = array_filter( $all_conditional_return_types, - static function (Atomic $atomic_type): bool { - return !($atomic_type instanceof TEmpty - || $atomic_type instanceof TNever); - } + static fn(Atomic $atomic_type): bool => !$atomic_type instanceof TNever ); } @@ -843,9 +817,7 @@ static function (Atomic $atomic_type): bool { if ($number_of_types > 1) { $all_conditional_return_types = array_filter( $all_conditional_return_types, - static function (Atomic $atomic_type): bool { - return !$atomic_type instanceof TVoid; - } + static fn(Atomic $atomic_type): bool => !$atomic_type instanceof TVoid ); if (count($all_conditional_return_types) !== $number_of_types) { @@ -904,6 +876,6 @@ static function (Atomic $atomic_type): bool { $expand_templates ); - return $return_type; + return [$return_type]; } } diff --git a/src/Psalm/Internal/Type/TypeParser.php b/src/Psalm/Internal/Type/TypeParser.php index 20c9308b2bd..2506fc2dce8 100644 --- a/src/Psalm/Internal/Type/TypeParser.php +++ b/src/Psalm/Internal/Type/TypeParser.php @@ -92,6 +92,9 @@ use function strtolower; use function substr; +/** + * @internal + */ class TypeParser { /** @@ -105,7 +108,7 @@ class TypeParser */ public static function parseTokens( array $type_tokens, - ?array $php_version = null, + ?int $analysis_php_version_id = null, array $template_type_map = [], array $type_aliases = [] ): Union { @@ -121,9 +124,9 @@ public static function parseTokens( throw new TypeParseTreeException("Invalid type '$only_token[0]'"); } } else { - $only_token[0] = TypeTokenizer::fixScalarTerms($only_token[0], $php_version); + $only_token[0] = TypeTokenizer::fixScalarTerms($only_token[0], $analysis_php_version_id); - $atomic = Atomic::create($only_token[0], $php_version, $template_type_map, $type_aliases); + $atomic = Atomic::create($only_token[0], $analysis_php_version_id, $template_type_map, $type_aliases); $atomic->offset_start = 0; $atomic->offset_end = strlen($only_token[0]); $atomic->text = isset($only_token[2]) && $only_token[2] !== $only_token[0] ? $only_token[2] : null; @@ -137,7 +140,7 @@ public static function parseTokens( $parsed_type = self::getTypeFromTree( $parse_tree, $codebase, - $php_version, + $analysis_php_version_id, $template_type_map, $type_aliases ); @@ -150,18 +153,17 @@ public static function parseTokens( } /** - * @param array{int,int}|null $php_version * @param array> $template_type_map - * @param array $type_aliases + * @param array $type_aliases * * @return Atomic|Union */ public static function getTypeFromTree( ParseTree $parse_tree, - Codebase $codebase, - ?array $php_version = null, - array $template_type_map = [], - array $type_aliases = [] + Codebase $codebase, + ?int $analysis_php_version_id = null, + array $template_type_map = [], + array $type_aliases = [] ): TypeNode { if ($parse_tree instanceof GenericTree) { return self::getTypeFromGenericTree( @@ -378,9 +380,9 @@ public static function getTypeFromTree( throw new TypeParseTreeException('Invalid type \'' . $parse_tree->value . '\''); } - $atomic_type_string = TypeTokenizer::fixScalarTerms($parse_tree->value, $php_version); + $atomic_type_string = TypeTokenizer::fixScalarTerms($parse_tree->value, $analysis_php_version_id); - $atomic_type = Atomic::create($atomic_type_string, $php_version, $template_type_map, $type_aliases); + $atomic_type = Atomic::create($atomic_type_string, $analysis_php_version_id, $template_type_map, $type_aliases); $atomic_type->offset_start = $parse_tree->offset_start; $atomic_type->offset_end = $parse_tree->offset_end; @@ -494,9 +496,7 @@ public static function getComputedIntsFromMask(array $potential_ints): array $potential_values = array_unique($potential_values); return array_map( - function ($int) { - return new TLiteralInt($int); - }, + fn($int) => new TLiteralInt($int), array_values($potential_values) ); } @@ -1084,7 +1084,7 @@ function (ParseTree $child_tree) use ($codebase, $template_type_map, $type_alias if ($intersect_static && $first_type instanceof TNamedObject ) { - $first_type->was_static = true; + $first_type->is_static = true; } if ($keyed_intersection_types) { @@ -1233,7 +1233,7 @@ private static function getTypeFromIndexAccessTree( /** * @param array> $template_type_map * @param array $type_aliases - * @return TCallableKeyedArray|TKeyedArray|TObjectWithProperties + * @return TCallableKeyedArray|TKeyedArray|TObjectWithProperties|TArray * @throws TypeParseTreeException */ private static function getTypeFromKeyedArrayTree( @@ -1312,7 +1312,7 @@ private static function getTypeFromKeyedArrayTree( } if (!$properties) { - throw new TypeParseTreeException('No properties supplied for TKeyedArray'); + return new TArray([Type::getNever(), Type::getNever()]); } if ($type === 'object') { diff --git a/src/Psalm/Internal/Type/TypeTokenizer.php b/src/Psalm/Internal/Type/TypeTokenizer.php index 020a3b270ca..a3e96874d3c 100644 --- a/src/Psalm/Internal/Type/TypeTokenizer.php +++ b/src/Psalm/Internal/Type/TypeTokenizer.php @@ -19,6 +19,9 @@ use function strpos; use function strtolower; +/** + * @internal + */ class TypeTokenizer { /** @@ -51,8 +54,6 @@ class TypeTokenizer 'stringable-object' => true, 'pure-callable' => true, 'pure-Closure' => true, - 'mysql-escaped-string' => true, // deprecated - 'html-escaped-string' => true, // deprecated 'literal-string' => true, 'non-empty-literal-string' => true, 'lowercase-string' => true, @@ -300,14 +301,11 @@ public static function tokenize(string $string_type, bool $ignore_space = true): } /** - * @param array{int,int}|null $php_version - * - * * @psalm-pure */ public static function fixScalarTerms( string $type_string, - ?array $php_version = null + ?int $analysis_php_version_id = null ): string { $type_string_lc = strtolower($type_string); @@ -330,14 +328,14 @@ public static function fixScalarTerms( switch ($type_string) { case 'boolean': - return $php_version !== null ? $type_string : 'bool'; + return $analysis_php_version_id !== null ? $type_string : 'bool'; case 'integer': - return $php_version !== null ? $type_string : 'int'; + return $analysis_php_version_id !== null ? $type_string : 'int'; case 'double': case 'real': - return $php_version !== null ? $type_string : 'float'; + return $analysis_php_version_id !== null ? $type_string : 'float'; } return $type_string; diff --git a/src/Psalm/Internal/TypeVisitor/ContainsClassLikeVisitor.php b/src/Psalm/Internal/TypeVisitor/ContainsClassLikeVisitor.php index 94d55b17ac7..fb57c5c2059 100644 --- a/src/Psalm/Internal/TypeVisitor/ContainsClassLikeVisitor.php +++ b/src/Psalm/Internal/TypeVisitor/ContainsClassLikeVisitor.php @@ -10,6 +10,9 @@ use function strtolower; +/** + * @internal + */ class ContainsClassLikeVisitor extends NodeVisitor { /** diff --git a/src/Psalm/Internal/TypeVisitor/ContainsLiteralVisitor.php b/src/Psalm/Internal/TypeVisitor/ContainsLiteralVisitor.php index e7d3f066031..8fb36b79c2b 100644 --- a/src/Psalm/Internal/TypeVisitor/ContainsLiteralVisitor.php +++ b/src/Psalm/Internal/TypeVisitor/ContainsLiteralVisitor.php @@ -11,6 +11,9 @@ use Psalm\Type\NodeVisitor; use Psalm\Type\TypeNode; +/** + * @internal + */ class ContainsLiteralVisitor extends NodeVisitor { /** @@ -30,7 +33,7 @@ protected function enterNode(TypeNode $type): ?int return NodeVisitor::STOP_TRAVERSAL; } - if ($type instanceof TArray && $type->type_params[1]->isEmpty()) { + if ($type instanceof TArray && $type->isEmptyArray()) { $this->contains_literal = true; return NodeVisitor::STOP_TRAVERSAL; } diff --git a/src/Psalm/Internal/TypeVisitor/FromDocblockSetter.php b/src/Psalm/Internal/TypeVisitor/FromDocblockSetter.php index 3846e766119..2f4a7b7cff0 100644 --- a/src/Psalm/Internal/TypeVisitor/FromDocblockSetter.php +++ b/src/Psalm/Internal/TypeVisitor/FromDocblockSetter.php @@ -8,6 +8,9 @@ use Psalm\Type\TypeNode; use Psalm\Type\Union; +/** + * @internal + */ class FromDocblockSetter extends NodeVisitor { /** diff --git a/src/Psalm/Internal/TypeVisitor/TemplateTypeCollector.php b/src/Psalm/Internal/TypeVisitor/TemplateTypeCollector.php index 1b3d365d69c..5806638342d 100644 --- a/src/Psalm/Internal/TypeVisitor/TemplateTypeCollector.php +++ b/src/Psalm/Internal/TypeVisitor/TemplateTypeCollector.php @@ -10,6 +10,9 @@ use Psalm\Type\TypeNode; use Psalm\Type\Union; +/** + * @internal + */ class TemplateTypeCollector extends NodeVisitor { /** diff --git a/src/Psalm/Internal/TypeVisitor/TypeChecker.php b/src/Psalm/Internal/TypeVisitor/TypeChecker.php index 7cb18b13d56..4eaaeb18987 100644 --- a/src/Psalm/Internal/TypeVisitor/TypeChecker.php +++ b/src/Psalm/Internal/TypeVisitor/TypeChecker.php @@ -34,11 +34,13 @@ use function array_keys; use function array_search; use function count; -use function is_array; use function md5; use function strpos; use function strtolower; +/** + * @internal + */ class TypeChecker extends NodeVisitor { /** @@ -321,7 +323,7 @@ public function checkScalarClassConstant(TClassConstant $atomic): void $const_name = $atomic->const_name; if (strpos($const_name, '*') !== false) { - $expanded = TypeExpander::expandAtomic( + TypeExpander::expandAtomic( $this->source->getCodebase(), $atomic, $fq_classlike_name, @@ -331,7 +333,7 @@ public function checkScalarClassConstant(TClassConstant $atomic): void true ); - $is_defined = is_array($expanded) && count($expanded) > 0; + $is_defined = true; } else { $class_constant_type = $this->source->getCodebase()->classlikes->getClassConstantType( $fq_classlike_name, diff --git a/src/Psalm/Internal/TypeVisitor/TypeScanner.php b/src/Psalm/Internal/TypeVisitor/TypeScanner.php index 3e742886991..48f6796ef28 100644 --- a/src/Psalm/Internal/TypeVisitor/TypeScanner.php +++ b/src/Psalm/Internal/TypeVisitor/TypeScanner.php @@ -12,6 +12,9 @@ use function strtolower; +/** + * @internal + */ class TypeScanner extends NodeVisitor { private $scanner; diff --git a/src/Psalm/Issue/CodeIssue.php b/src/Psalm/Issue/CodeIssue.php index 00bb8b2fef9..c6d55215edd 100644 --- a/src/Psalm/Issue/CodeIssue.php +++ b/src/Psalm/Issue/CodeIssue.php @@ -38,15 +38,6 @@ public function __construct( $this->message = $message; } - /** - * @deprecated going to be removed in Psalm 5 - * @psalm-suppress PossiblyUnusedMethod - */ - public function getLocation(): CodeLocation - { - return $this->code_location; - } - public function getShortLocationWithPrevious(): string { $previous_text = ''; @@ -69,24 +60,6 @@ public function getFilePath(): string return $this->code_location->file_path; } - /** - * @deprecated going to be removed in Psalm 5 - * @psalm-suppress PossiblyUnusedMethod for convenience - */ - public function getFileName(): string - { - return $this->code_location->file_name; - } - - /** - * @deprecated going to be removed in Psalm 5 - * @psalm-suppress PossiblyUnusedMethod - */ - public function getMessage(): string - { - return $this->message; - } - public static function getIssueType(): string { $fqcn_parts = explode('\\', static::class); diff --git a/src/Psalm/IssueBuffer.php b/src/Psalm/IssueBuffer.php index b9c5308d872..49defb85280 100644 --- a/src/Psalm/IssueBuffer.php +++ b/src/Psalm/IssueBuffer.php @@ -635,9 +635,7 @@ function (IssueData $d1, IssueData $d2): int { } - if ($codebase->config->eventDispatcher->after_analysis - || $codebase->config->eventDispatcher->legacy_after_analysis - ) { + if ($codebase->config->eventDispatcher->after_analysis) { $source_control_info = null; $build_info = (new BuildInfoCollector(self::$server))->collect(); @@ -728,7 +726,7 @@ function (IssueData $d1, IssueData $d2): int { if ($start_time) { echo 'Checks took ' . number_format(microtime(true) - $start_time, 2) . ' seconds'; - echo ' and used ' . number_format(memory_get_peak_usage() / (1024 * 1024), 3) . 'MB of memory' . "\n"; + echo ' and used ' . number_format(memory_get_peak_usage() / (1_024 * 1_024), 3) . 'MB of memory' . "\n"; $analysis_summary = $codebase->analyzer->getTypeInferenceSummary($codebase); echo $analysis_summary . "\n"; @@ -755,7 +753,7 @@ function (IssueData $d1, IssueData $d2): int { break; } - echo $function_id . ': ' . round(1000 * $time, 2) . 'ms per node' . "\n"; + echo $function_id . ': ' . round(1_000 * $time, 2) . 'ms per node' . "\n"; } echo "\n"; diff --git a/src/Psalm/Plugin/Hook/AfterAnalysisInterface.php b/src/Psalm/Plugin/Hook/AfterAnalysisInterface.php deleted file mode 100644 index 2e399d4d32d..00000000000 --- a/src/Psalm/Plugin/Hook/AfterAnalysisInterface.php +++ /dev/null @@ -1,23 +0,0 @@ -> $issues - */ - public static function afterAnalysis( - Codebase $codebase, - array $issues, - array $build_info, - ?SourceControlInfo $source_control_info = null - ): void; -} diff --git a/src/Psalm/Plugin/Hook/AfterClassLikeAnalysisInterface.php b/src/Psalm/Plugin/Hook/AfterClassLikeAnalysisInterface.php deleted file mode 100644 index c910400e0ac..00000000000 --- a/src/Psalm/Plugin/Hook/AfterClassLikeAnalysisInterface.php +++ /dev/null @@ -1,29 +0,0 @@ - - */ - public static function getFunctionIds(): array; - - /** - * Use this hook for informing whether or not a global function exists. If you know the function does - * not exist, return false. If you aren't sure if it exists or not, return null and the default analysis - * will continue to determine if the function actually exists. - * - */ - public static function doesFunctionExist( - StatementsSource $statements_source, - string $function_id - ): ?bool; -} diff --git a/src/Psalm/Plugin/Hook/FunctionParamsProviderInterface.php b/src/Psalm/Plugin/Hook/FunctionParamsProviderInterface.php deleted file mode 100644 index 8b23d82968c..00000000000 --- a/src/Psalm/Plugin/Hook/FunctionParamsProviderInterface.php +++ /dev/null @@ -1,31 +0,0 @@ - - */ - public static function getFunctionIds(): array; - - /** - * @param list $call_args - * - * @return ?array - */ - public static function getFunctionParams( - StatementsSource $statements_source, - string $function_id, - array $call_args, - ?Context $context = null, - ?CodeLocation $code_location = null - ): ?array; -} diff --git a/src/Psalm/Plugin/Hook/FunctionReturnTypeProviderInterface.php b/src/Psalm/Plugin/Hook/FunctionReturnTypeProviderInterface.php deleted file mode 100644 index 125d779f69a..00000000000 --- a/src/Psalm/Plugin/Hook/FunctionReturnTypeProviderInterface.php +++ /dev/null @@ -1,33 +0,0 @@ - - */ - public static function getFunctionIds(): array; - - /** - * Use this hook for providing custom return type logic. If this plugin does not know what a function should - * return but another plugin may be able to determine the type, return null. Otherwise return a mixed union type - * if something should be returned, but can't be more specific. - * - * @param list $call_args - */ - public static function getFunctionReturnType( - StatementsSource $statements_source, - string $function_id, - array $call_args, - Context $context, - CodeLocation $code_location - ): ?Union; -} diff --git a/src/Psalm/Plugin/Hook/MethodExistenceProviderInterface.php b/src/Psalm/Plugin/Hook/MethodExistenceProviderInterface.php deleted file mode 100644 index 2964fe60445..00000000000 --- a/src/Psalm/Plugin/Hook/MethodExistenceProviderInterface.php +++ /dev/null @@ -1,27 +0,0 @@ - - */ - public static function getClassLikeNames(): array; - - /** - * Use this hook for informing whether or not a method exists on a given object. If you know the method does - * not exist, return false. If you aren't sure if it exists or not, return null and the default analysis will - * continue to determine if the method actually exists. - */ - public static function doesMethodExist( - string $fq_classlike_name, - string $method_name_lowercase, - ?StatementsSource $source = null, - ?CodeLocation $code_location = null - ): ?bool; -} diff --git a/src/Psalm/Plugin/Hook/MethodParamsProviderInterface.php b/src/Psalm/Plugin/Hook/MethodParamsProviderInterface.php deleted file mode 100644 index 680c60a8ff7..00000000000 --- a/src/Psalm/Plugin/Hook/MethodParamsProviderInterface.php +++ /dev/null @@ -1,32 +0,0 @@ - - */ - public static function getClassLikeNames(): array; - - /** - * @param list $call_args - * - * @return ?array - */ - public static function getMethodParams( - string $fq_classlike_name, - string $method_name_lowercase, - ?array $call_args = null, - ?StatementsSource $statements_source = null, - ?Context $context = null, - ?CodeLocation $code_location = null - ): ?array; -} diff --git a/src/Psalm/Plugin/Hook/MethodReturnTypeProviderInterface.php b/src/Psalm/Plugin/Hook/MethodReturnTypeProviderInterface.php deleted file mode 100644 index c39a61ce89f..00000000000 --- a/src/Psalm/Plugin/Hook/MethodReturnTypeProviderInterface.php +++ /dev/null @@ -1,40 +0,0 @@ - - */ - public static function getClassLikeNames(): array; - - /** - * Use this hook for providing custom return type logic. If this plugin does not know what a method should return - * but another plugin may be able to determine the type, return null. Otherwise return a mixed union type if - * something should be returned, but can't be more specific. - * - * @param list $call_args - * @param ?array $template_type_parameters - * @param lowercase-string $method_name_lowercase - * @param lowercase-string $called_method_name_lowercase - */ - public static function getMethodReturnType( - StatementsSource $source, - string $fq_classlike_name, - string $method_name_lowercase, - array $call_args, - Context $context, - CodeLocation $code_location, - ?array $template_type_parameters = null, - ?string $called_fq_classlike_name = null, - ?string $called_method_name_lowercase = null - ): ?Union; -} diff --git a/src/Psalm/Plugin/Hook/MethodVisibilityProviderInterface.php b/src/Psalm/Plugin/Hook/MethodVisibilityProviderInterface.php deleted file mode 100644 index bf6113521e0..00000000000 --- a/src/Psalm/Plugin/Hook/MethodVisibilityProviderInterface.php +++ /dev/null @@ -1,24 +0,0 @@ - - */ - public static function getClassLikeNames(): array; - - public static function isMethodVisible( - StatementsSource $source, - string $fq_classlike_name, - string $method_name_lowercase, - Context $context, - ?CodeLocation $code_location = null - ): ?bool; -} diff --git a/src/Psalm/Plugin/Hook/PropertyExistenceProviderInterface.php b/src/Psalm/Plugin/Hook/PropertyExistenceProviderInterface.php deleted file mode 100644 index 6debbb96b4a..00000000000 --- a/src/Psalm/Plugin/Hook/PropertyExistenceProviderInterface.php +++ /dev/null @@ -1,31 +0,0 @@ - - */ - public static function getClassLikeNames(): array; - - /** - * Use this hook for informing whether or not a property exists on a given object. If you know the property does - * not exist, return false. If you aren't sure if it exists or not, return null and the default analysis will - * continue to determine if the property actually exists. - * - */ - public static function doesPropertyExist( - string $fq_classlike_name, - string $property_name, - bool $read_mode, - ?StatementsSource $source = null, - ?Context $context = null, - ?CodeLocation $code_location = null - ): ?bool; -} diff --git a/src/Psalm/Plugin/Hook/PropertyTypeProviderInterface.php b/src/Psalm/Plugin/Hook/PropertyTypeProviderInterface.php deleted file mode 100644 index 0e3f18fc280..00000000000 --- a/src/Psalm/Plugin/Hook/PropertyTypeProviderInterface.php +++ /dev/null @@ -1,24 +0,0 @@ - - */ - public static function getClassLikeNames(): array; - - public static function getPropertyType( - string $fq_classlike_name, - string $property_name, - bool $read_mode, - ?StatementsSource $source = null, - ?Context $context = null - ): ?Union; -} diff --git a/src/Psalm/Plugin/Hook/PropertyVisibilityProviderInterface.php b/src/Psalm/Plugin/Hook/PropertyVisibilityProviderInterface.php deleted file mode 100644 index 650b9447e1f..00000000000 --- a/src/Psalm/Plugin/Hook/PropertyVisibilityProviderInterface.php +++ /dev/null @@ -1,25 +0,0 @@ - - */ - public static function getClassLikeNames(): array; - - public static function isPropertyVisible( - StatementsSource $source, - string $fq_classlike_name, - string $property_name, - bool $read_mode, - Context $context, - CodeLocation $code_location - ): ?bool; -} diff --git a/src/Psalm/Plugin/Hook/StringInterpreterInterface.php b/src/Psalm/Plugin/Hook/StringInterpreterInterface.php deleted file mode 100644 index 976918ee5b4..00000000000 --- a/src/Psalm/Plugin/Hook/StringInterpreterInterface.php +++ /dev/null @@ -1,16 +0,0 @@ -severity === 'error'; - } + static fn(IssueData $i): bool => $i->severity === 'error' ); $data = [ @@ -76,7 +75,7 @@ static function (IssueData $i): bool { 'level' => Config::getInstance()->level ]; - $payload = json_encode($data); + $payload = json_encode($data, JSON_THROW_ON_ERROR); $base_address = $codebase->config->shepherd_host; diff --git a/src/Psalm/PluginRegistrationSocket.php b/src/Psalm/PluginRegistrationSocket.php index 70d259d2e26..6a24935ee93 100644 --- a/src/Psalm/PluginRegistrationSocket.php +++ b/src/Psalm/PluginRegistrationSocket.php @@ -16,16 +16,6 @@ use Psalm\Plugin\EventHandler\PropertyExistenceProviderInterface; use Psalm\Plugin\EventHandler\PropertyTypeProviderInterface; use Psalm\Plugin\EventHandler\PropertyVisibilityProviderInterface; -use Psalm\Plugin\Hook\FunctionExistenceProviderInterface as LegacyFunctionExistenceProviderInterface; -use Psalm\Plugin\Hook\FunctionParamsProviderInterface as LegacyFunctionParamsProviderInterface; -use Psalm\Plugin\Hook\FunctionReturnTypeProviderInterface as LegacyFunctionReturnTypeProviderInterface; -use Psalm\Plugin\Hook\MethodExistenceProviderInterface as LegacyMethodExistenceProviderInterface; -use Psalm\Plugin\Hook\MethodParamsProviderInterface as LegacyMethodParamsProviderInterface; -use Psalm\Plugin\Hook\MethodReturnTypeProviderInterface as LegacyMethodReturnTypeProviderInterface; -use Psalm\Plugin\Hook\MethodVisibilityProviderInterface as LegacyMethodVisibilityProviderInterface; -use Psalm\Plugin\Hook\PropertyExistenceProviderInterface as LegacyPropertyExistenceProviderInterface; -use Psalm\Plugin\Hook\PropertyTypeProviderInterface as LegacyPropertyTypeProviderInterface; -use Psalm\Plugin\Hook\PropertyVisibilityProviderInterface as LegacyPropertyVisibilityProviderInterface; use Psalm\Plugin\RegistrationInterface; use function class_exists; @@ -79,63 +69,43 @@ public function registerHooksFromClass(string $handler): void $this->config->eventDispatcher->registerClass($handler); - if (is_subclass_of($handler, LegacyPropertyExistenceProviderInterface::class) || - is_subclass_of($handler, PropertyExistenceProviderInterface::class) - ) { + if (is_subclass_of($handler, PropertyExistenceProviderInterface::class)) { $this->codebase->properties->property_existence_provider->registerClass($handler); } - if (is_subclass_of($handler, LegacyPropertyVisibilityProviderInterface::class) || - is_subclass_of($handler, PropertyVisibilityProviderInterface::class) - ) { + if (is_subclass_of($handler, PropertyVisibilityProviderInterface::class)) { $this->codebase->properties->property_visibility_provider->registerClass($handler); } - if (is_subclass_of($handler, LegacyPropertyTypeProviderInterface::class) || - is_subclass_of($handler, PropertyTypeProviderInterface::class) - ) { + if (is_subclass_of($handler, PropertyTypeProviderInterface::class)) { $this->codebase->properties->property_type_provider->registerClass($handler); } - if (is_subclass_of($handler, LegacyMethodExistenceProviderInterface::class) || - is_subclass_of($handler, MethodExistenceProviderInterface::class) - ) { + if (is_subclass_of($handler, MethodExistenceProviderInterface::class)) { $this->codebase->methods->existence_provider->registerClass($handler); } - if (is_subclass_of($handler, LegacyMethodVisibilityProviderInterface::class) || - is_subclass_of($handler, MethodVisibilityProviderInterface::class) - ) { + if (is_subclass_of($handler, MethodVisibilityProviderInterface::class)) { $this->codebase->methods->visibility_provider->registerClass($handler); } - if (is_subclass_of($handler, LegacyMethodReturnTypeProviderInterface::class) || - is_subclass_of($handler, MethodReturnTypeProviderInterface::class) - ) { + if (is_subclass_of($handler, MethodReturnTypeProviderInterface::class)) { $this->codebase->methods->return_type_provider->registerClass($handler); } - if (is_subclass_of($handler, LegacyMethodParamsProviderInterface::class) || - is_subclass_of($handler, MethodParamsProviderInterface::class) - ) { + if (is_subclass_of($handler, MethodParamsProviderInterface::class)) { $this->codebase->methods->params_provider->registerClass($handler); } - if (is_subclass_of($handler, LegacyFunctionExistenceProviderInterface::class) || - is_subclass_of($handler, FunctionExistenceProviderInterface::class) - ) { + if (is_subclass_of($handler, FunctionExistenceProviderInterface::class)) { $this->codebase->functions->existence_provider->registerClass($handler); } - if (is_subclass_of($handler, LegacyFunctionParamsProviderInterface::class) || - is_subclass_of($handler, FunctionParamsProviderInterface::class) - ) { + if (is_subclass_of($handler, FunctionParamsProviderInterface::class)) { $this->codebase->functions->params_provider->registerClass($handler); } - if (is_subclass_of($handler, LegacyFunctionReturnTypeProviderInterface::class) || - is_subclass_of($handler, FunctionReturnTypeProviderInterface::class) - ) { + if (is_subclass_of($handler, FunctionReturnTypeProviderInterface::class)) { $this->codebase->functions->return_type_provider->registerClass($handler); } } @@ -153,7 +123,7 @@ public function addFileTypeScanner(string $fileExtension, string $className): vo $className, FileScanner::class ), - 1622727271 + 1_622_727_271 ); } if (!empty($this->config->getFiletypeScanners()[$fileExtension]) @@ -161,7 +131,7 @@ public function addFileTypeScanner(string $fileExtension, string $className): vo ) { throw new LogicException( sprintf('Cannot redeclare scanner for file-type %s', $fileExtension), - 1622727272 + 1_622_727_272 ); } $this->additionalFileTypeScanners[$fileExtension] = $className; @@ -189,7 +159,7 @@ public function addFileTypeAnalyzer(string $fileExtension, string $className): v $className, FileAnalyzer::class ), - 1622727281 + 1_622_727_281 ); } if (!empty($this->config->getFiletypeAnalyzers()[$fileExtension]) @@ -197,7 +167,7 @@ public function addFileTypeAnalyzer(string $fileExtension, string $className): v ) { throw new LogicException( sprintf('Cannot redeclare analyzer for file-type %s', $fileExtension), - 1622727282 + 1_622_727_282 ); } $this->additionalFileTypeAnalyzers[$fileExtension] = $className; diff --git a/src/Psalm/Progress/DefaultProgress.php b/src/Psalm/Progress/DefaultProgress.php index 76f91e98297..2bacfb0758f 100644 --- a/src/Psalm/Progress/DefaultProgress.php +++ b/src/Psalm/Progress/DefaultProgress.php @@ -9,7 +9,7 @@ class DefaultProgress extends LongProgress { - private const TOO_MANY_FILES = 1500; + private const TOO_MANY_FILES = 1_500; // Update the progress bar at most once per 0.1 seconds. // This reduces flickering and reduces the amount of time spent writing to STDERR and updating the terminal. diff --git a/src/Psalm/Report.php b/src/Psalm/Report.php index 6ad69c849fc..e8441107321 100644 --- a/src/Psalm/Report.php +++ b/src/Psalm/Report.php @@ -90,9 +90,7 @@ public function __construct( if (!$report_options->show_info) { $this->issues_data = array_filter( $issues_data, - function ($issue_data): bool { - return $issue_data->severity !== Config::REPORT_INFO; - } + fn($issue_data): bool => $issue_data->severity !== Config::REPORT_INFO ); } else { $this->issues_data = $issues_data; diff --git a/src/Psalm/Report/CodeClimateReport.php b/src/Psalm/Report/CodeClimateReport.php index cdd1458b720..27523ab3627 100644 --- a/src/Psalm/Report/CodeClimateReport.php +++ b/src/Psalm/Report/CodeClimateReport.php @@ -27,7 +27,7 @@ public function create(): string $options = $this->pretty ? Json::PRETTY : Json::DEFAULT; $issues_data = array_map( - function (IssueData $issue): array { + fn(IssueData $issue): array => /** * map fields to new structure. * Expected fields: @@ -43,7 +43,7 @@ function (IssueData $issue): array { * * Fields with * are the one used by Gitlab for Code Quality */ - return [ + [ 'type' => 'issue', 'check_name' => $issue->type, 'description' => $issue->message, @@ -57,8 +57,7 @@ function (IssueData $issue): array { 'end' => $issue->line_to, ], ], - ]; - }, + ], $this->issues_data ); diff --git a/src/Psalm/Report/XmlReport.php b/src/Psalm/Report/XmlReport.php index eea9c0561ad..0c4cfe9b86f 100644 --- a/src/Psalm/Report/XmlReport.php +++ b/src/Psalm/Report/XmlReport.php @@ -24,18 +24,14 @@ function (IssueData $issue_data): array { if (null !== $issue_data['taint_trace']) { $issue_data['taint_trace'] = array_map( - function ($trace): array { - return (array) $trace; - }, + fn($trace): array => (array) $trace, $issue_data['taint_trace'] ); } if (null !== $issue_data['other_references']) { $issue_data['other_references'] = array_map( - function (DataFlowNodeData $reference): array { - return (array) $reference; - }, + fn(DataFlowNodeData $reference): array => (array) $reference, $issue_data['other_references'] ); } diff --git a/src/Psalm/Storage/Assertion.php b/src/Psalm/Storage/Assertion.php index 53f13b1205f..0cfa0255f22 100644 --- a/src/Psalm/Storage/Assertion.php +++ b/src/Psalm/Storage/Assertion.php @@ -87,9 +87,7 @@ function (array $rules) use ($inferred_lower_bounds, $codebase): array { return [implode( '', array_map( - function ($f) { - return $f[0]; - }, + fn($f) => $f[0], $rule_tokens ) )]; diff --git a/src/Psalm/Storage/FunctionLikeStorage.php b/src/Psalm/Storage/FunctionLikeStorage.php index e9928ce565a..c06a43dea24 100644 --- a/src/Psalm/Storage/FunctionLikeStorage.php +++ b/src/Psalm/Storage/FunctionLikeStorage.php @@ -243,15 +243,21 @@ public function getSignature(bool $allow_newlines): string { $newlines = $allow_newlines && !empty($this->params); - $symbol_text = 'function ' . $this->cased_name . '(' . ($newlines ? "\n" : '') . implode( - ',' . ($newlines ? "\n" : ' '), - array_map( - function (FunctionLikeParameter $param) use ($newlines): string { - return ($newlines ? ' ' : '') . ($param->type ?: 'mixed') . ' $' . $param->name; - }, - $this->params + $symbol_text = 'function ' . $this->cased_name . '(' + . ($newlines ? "\n" : '') + . implode( + ',' . ($newlines ? "\n" : ' '), + array_map( + fn(FunctionLikeParameter $param): string => + ($newlines ? ' ' : '') + . ($param->type ?: 'mixed') + . ' $' . $param->name, + $this->params + ) ) - ) . ($newlines ? "\n" : '') . ') : ' . ($this->return_type ?: 'mixed'); + . ($newlines ? "\n" : '') + . ') : ' + . ($this->return_type ?: 'mixed'); if (!$this instanceof MethodStorage) { return $symbol_text; diff --git a/src/Psalm/Type.php b/src/Psalm/Type.php index 6d0c9e0fb45..e6e988f842a 100644 --- a/src/Psalm/Type.php +++ b/src/Psalm/Type.php @@ -17,7 +17,6 @@ use Psalm\Type\Atomic\TBool; use Psalm\Type\Atomic\TClassString; use Psalm\Type\Atomic\TClosure; -use Psalm\Type\Atomic\TEmpty; use Psalm\Type\Atomic\TFalse; use Psalm\Type\Atomic\TFloat; use Psalm\Type\Atomic\TInt; @@ -75,14 +74,14 @@ abstract class Type */ public static function parseString( string $type_string, - ?array $php_version = null, + ?int $analysis_php_version_id = null, array $template_type_map = [] ): Union { return TypeParser::parseTokens( TypeTokenizer::tokenize( $type_string ), - $php_version, + $analysis_php_version_id, $template_type_map ); } @@ -128,10 +127,10 @@ public static function getStringFromFQCLN( array $aliased_classes, ?string $this_class, bool $allow_self = false, - bool $was_static = false + bool $is_static = false ): string { if ($allow_self && $value === $this_class) { - if ($was_static) { + if ($is_static) { return 'static'; } return 'self'; @@ -306,16 +305,6 @@ public static function getScalar(): Union return new Union([$type]); } - /** - * @deprecated will be removed in Psalm 5. See getNever to retrieve a TNever that replaces TEmpty - */ - public static function getEmpty(): Union - { - $type = new TEmpty(); - - return new Union([$type]); - } - public static function getNever(): Union { $type = new TNever(); @@ -378,8 +367,8 @@ public static function getEmptyArray(): Union { $array_type = new TArray( [ - new Union([new TEmpty]), - new Union([new TEmpty]), + new Union([new TNever()]), + new Union([new TNever()]), ] ); @@ -596,91 +585,13 @@ public static function intersectUnionTypes( $combined_type = null; foreach ($type_1->getAtomicTypes() as $type_1_atomic) { foreach ($type_2->getAtomicTypes() as $type_2_atomic) { - $intersection_atomic = null; - $wider_type = null; - if ($type_1_atomic instanceof TNamedObject - && $type_2_atomic instanceof TNamedObject - ) { - if (($type_1_atomic->value === $type_2_atomic->value - && get_class($type_1_atomic) === TNamedObject::class - && get_class($type_2_atomic) !== TNamedObject::class) - ) { - $intersection_atomic = clone $type_2_atomic; - $wider_type = $type_1_atomic; - $intersection_performed = true; - } elseif (($type_1_atomic->value === $type_2_atomic->value - && get_class($type_2_atomic) === TNamedObject::class - && get_class($type_1_atomic) !== TNamedObject::class) - ) { - $intersection_atomic = clone $type_1_atomic; - $wider_type = $type_2_atomic; - $intersection_performed = true; - } - } - - if (null === $intersection_atomic) { - if (AtomicTypeComparator::isContainedBy( - $codebase, - $type_2_atomic, - $type_1_atomic - )) { - $intersection_atomic = clone $type_2_atomic; - $wider_type = $type_1_atomic; - $intersection_performed = true; - } elseif (AtomicTypeComparator::isContainedBy( - $codebase, - $type_1_atomic, - $type_2_atomic - )) { - $intersection_atomic = clone $type_1_atomic; - $wider_type = $type_2_atomic; - $intersection_performed = true; - } - } + $intersection_atomic = self::intersectAtomicTypes( + $type_1_atomic, + $type_2_atomic, + $codebase, + $intersection_performed + ); - if (static::mayHaveIntersection($type_1_atomic) - && static::mayHaveIntersection($type_2_atomic) - ) { - if ($intersection_atomic === null && $wider_type === null) { - $intersection_atomic = clone $type_1_atomic; - $wider_type = $type_2_atomic; - } - if ($intersection_atomic === null || $wider_type === null) { - throw new LogicException( - '$intersection_atomic and $wider_type should be both set or null.' - .' Check the preceding code for errors.' - .' Did you forget to assign one of the variables?' - ); - } - if (!static::mayHaveIntersection($intersection_atomic) - || !static::mayHaveIntersection($wider_type) - ) { - throw new LogicException( - '$intersection_atomic and $wider_type should be both support intersection.' - .' Check the preceding code for errors.' - ); - } - if (!$intersection_atomic->extra_types) { - $intersection_atomic->extra_types = []; - } - - $intersection_performed = true; - - $wider_type_clone = clone $wider_type; - - $wider_type_clone->extra_types = []; - - $intersection_atomic->extra_types[$wider_type_clone->getKey()] = $wider_type_clone; - - $wider_type_intersection_types = $wider_type->getIntersectionTypes(); - - if ($wider_type_intersection_types !== null) { - foreach ($wider_type_intersection_types as $wider_type_intersection_type) { - $intersection_atomic->extra_types[$wider_type_intersection_type->getKey()] - = clone $wider_type_intersection_type; - } - } - } if (null !== $intersection_atomic) { if (null === $combined_type) { $combined_type = new Union([$intersection_atomic]); @@ -747,6 +658,108 @@ public static function intersectUnionTypes( return $combined_type; } + private static function intersectAtomicTypes( + Atomic $type_1_atomic, + Atomic $type_2_atomic, + Codebase $codebase, + bool &$intersection_performed + ): ?Atomic { + $intersection_atomic = null; + $wider_type = null; + if ($type_1_atomic instanceof TNamedObject + && $type_2_atomic instanceof TNamedObject + ) { + if (($type_1_atomic->value === $type_2_atomic->value + && get_class($type_1_atomic) === TNamedObject::class + && get_class($type_2_atomic) !== TNamedObject::class) + ) { + $intersection_atomic = clone $type_2_atomic; + $wider_type = $type_1_atomic; + $intersection_performed = true; + } elseif (($type_1_atomic->value === $type_2_atomic->value + && get_class($type_2_atomic) === TNamedObject::class + && get_class($type_1_atomic) !== TNamedObject::class) + ) { + $intersection_atomic = clone $type_1_atomic; + $wider_type = $type_2_atomic; + $intersection_performed = true; + } + } + + if (null === $intersection_atomic) { + if (AtomicTypeComparator::isContainedBy( + $codebase, + $type_2_atomic, + $type_1_atomic + )) { + $intersection_atomic = clone $type_2_atomic; + $wider_type = $type_1_atomic; + $intersection_performed = true; + } elseif (AtomicTypeComparator::isContainedBy( + $codebase, + $type_1_atomic, + $type_2_atomic + )) { + $intersection_atomic = clone $type_1_atomic; + $wider_type = $type_2_atomic; + $intersection_performed = true; + } + + if ($intersection_atomic + && !self::hasIntersection($type_1_atomic) + && !self::hasIntersection($type_2_atomic) + ) { + return $intersection_atomic; + } + } + + if (static::mayHaveIntersection($type_1_atomic) + && static::mayHaveIntersection($type_2_atomic) + ) { + if ($intersection_atomic === null && $wider_type === null) { + $intersection_atomic = clone $type_1_atomic; + $wider_type = $type_2_atomic; + } + if ($intersection_atomic === null || $wider_type === null) { + throw new LogicException( + '$intersection_atomic and $wider_type should be both set or null.' + .' Check the preceding code for errors.' + .' Did you forget to assign one of the variables?' + ); + } + if (!static::mayHaveIntersection($intersection_atomic) + || !static::mayHaveIntersection($wider_type) + ) { + throw new LogicException( + '$intersection_atomic and $wider_type should be both support intersection.' + .' Check the preceding code for errors.' + ); + } + if (!$intersection_atomic->extra_types) { + $intersection_atomic->extra_types = []; + } + + $intersection_performed = true; + + $wider_type_clone = clone $wider_type; + + $wider_type_clone->extra_types = []; + + $intersection_atomic->extra_types[$wider_type_clone->getKey()] = $wider_type_clone; + + $wider_type_intersection_types = $wider_type->getIntersectionTypes(); + + if ($wider_type_intersection_types !== null) { + foreach ($wider_type_intersection_types as $wider_type_intersection_type) { + $intersection_atomic->extra_types[$wider_type_intersection_type->getKey()] + = clone $wider_type_intersection_type; + } + } + } + + return $intersection_atomic; + } + /** * @psalm-assert-if-true TIterable|TNamedObject|TTemplateParam|TObjectWithProperties $type */ @@ -757,4 +770,13 @@ private static function mayHaveIntersection(Atomic $type): bool || $type instanceof TTemplateParam || $type instanceof TObjectWithProperties; } + + private static function hasIntersection(Atomic $type): bool + { + return ($type instanceof TIterable + || $type instanceof TNamedObject + || $type instanceof TTemplateParam + || $type instanceof TObjectWithProperties + ) && $type->extra_types; + } } diff --git a/src/Psalm/Type/Atomic.php b/src/Psalm/Type/Atomic.php index 91378836e53..6ee69f475a6 100644 --- a/src/Psalm/Type/Atomic.php +++ b/src/Psalm/Type/Atomic.php @@ -9,9 +9,9 @@ use Psalm\Internal\Type\TypeAlias; use Psalm\Internal\Type\TypeAlias\LinkableTypeAlias; use Psalm\Type; -use Psalm\Type\Atomic; use Psalm\Type\Atomic\TArray; use Psalm\Type\Atomic\TArrayKey; +use Psalm\Type\Atomic\TAssertionEmpty; use Psalm\Type\Atomic\TAssertionFalsy; use Psalm\Type\Atomic\TBool; use Psalm\Type\Atomic\TCallable; @@ -26,14 +26,12 @@ use Psalm\Type\Atomic\TClosedResource; use Psalm\Type\Atomic\TClosure; use Psalm\Type\Atomic\TDependentGetClass; -use Psalm\Type\Atomic\TEmpty; use Psalm\Type\Atomic\TEmptyMixed; use Psalm\Type\Atomic\TEmptyNumeric; use Psalm\Type\Atomic\TEmptyScalar; use Psalm\Type\Atomic\TFalse; use Psalm\Type\Atomic\TFloat; use Psalm\Type\Atomic\TGenericObject; -use Psalm\Type\Atomic\THtmlEscapedString; use Psalm\Type\Atomic\TInt; use Psalm\Type\Atomic\TIntRange; use Psalm\Type\Atomic\TIterable; @@ -113,15 +111,15 @@ abstract class Atomic implements TypeNode public $text; /** - * @param array{int,int}|null $php_version contains php version when the type comes from signature + * @param int $analysis_php_version_id contains php version when the type comes from signature * @param array> $template_type_map * @param array $type_aliases */ public static function create( string $value, - ?array $php_version = null, - array $template_type_map = [], - array $type_aliases = [] + ?int $analysis_php_version_id = null, + array $template_type_map = [], + array $type_aliases = [] ): Atomic { switch ($value) { case 'int': @@ -137,10 +135,7 @@ public static function create( return new TBool(); case 'void': - if ($php_version === null - || ($php_version[0] > 7) - || ($php_version[0] === 7 && $php_version[1] >= 1) - ) { + if ($analysis_php_version_id === null || $analysis_php_version_id >= 7_01_00) { return new TVoid(); } @@ -150,20 +145,14 @@ public static function create( return new TArrayKey(); case 'iterable': - if ($php_version === null - || ($php_version[0] > 7) - || ($php_version[0] === 7 && $php_version[1] >= 1) - ) { + if ($analysis_php_version_id === null || $analysis_php_version_id >= 7_01_00) { return new TIterable(); } break; case 'never': - if ($php_version === null - || ($php_version[0] > 8) - || ($php_version[0] === 8 && $php_version[1] >= 1) - ) { + if ($analysis_php_version_id === null || $analysis_php_version_id >= 8_01_00) { return new TNever(); } @@ -175,10 +164,7 @@ public static function create( return new TNever(); case 'object': - if ($php_version === null - || ($php_version[0] > 7) - || ($php_version[0] === 7 && $php_version[1] >= 2) - ) { + if ($analysis_php_version_id === null || $analysis_php_version_id >= 7_02_00) { return new TObject(); } @@ -221,7 +207,7 @@ public static function create( return new TNonEmptyLowercaseString(); case 'resource': - return $php_version !== null ? new TNamedObject($value) : new TResource(); + return $analysis_php_version_id !== null ? new TNamedObject($value) : new TResource(); case 'resource (closed)': case 'closed-resource': @@ -231,33 +217,33 @@ public static function create( return new TPositiveInt(); case 'numeric': - return $php_version !== null ? new TNamedObject($value) : new TNumeric(); + return $analysis_php_version_id !== null ? new TNamedObject($value) : new TNumeric(); case 'true': - return $php_version !== null ? new TNamedObject($value) : new TTrue(); + return $analysis_php_version_id !== null ? new TNamedObject($value) : new TTrue(); case 'false': - if ($php_version === null || $php_version[0] >= 8) { + if ($analysis_php_version_id === null || $analysis_php_version_id >= 8_00_00) { return new TFalse(); } return new TNamedObject($value); case 'empty': - return $php_version !== null ? new TNamedObject($value) : new TEmpty(); + return $analysis_php_version_id !== null ? new TNamedObject($value) : new TAssertionEmpty(); case 'scalar': - return $php_version !== null ? new TNamedObject($value) : new TScalar(); + return $analysis_php_version_id !== null ? new TNamedObject($value) : new TScalar(); case 'null': - if ($php_version === null || $php_version[0] >= 8) { + if ($analysis_php_version_id === null || $analysis_php_version_id >= 8_00_00) { return new TNull(); } return new TNamedObject($value); case 'mixed': - if ($php_version === null || $php_version[0] >= 8) { + if ($analysis_php_version_id === null || $analysis_php_version_id >= 8_00_00) { return new TMixed(); } @@ -282,9 +268,6 @@ public static function create( case 'numeric-string': return new TNumericString(); - case 'html-escaped-string': - return new THtmlEscapedString(); - case 'literal-string': return new TNonspecificLiteralString(); @@ -367,9 +350,7 @@ public function isNamedObjectType(): bool && ($this->as->hasNamedObjectType() || array_filter( $this->extra_types ?: [], - function ($extra_type) { - return $extra_type->isNamedObjectType(); - } + fn($extra_type) => $extra_type->isNamedObjectType() ) ) ); @@ -420,9 +401,7 @@ public function hasTraversableInterface(Codebase $codebase): bool $this->extra_types && array_filter( $this->extra_types, - function (Atomic $a) use ($codebase): bool { - return $a->hasTraversableInterface($codebase); - } + fn(Atomic $a): bool => $a->hasTraversableInterface($codebase) ) ) ); @@ -445,9 +424,7 @@ public function hasCountableInterface(Codebase $codebase): bool $this->extra_types && array_filter( $this->extra_types, - function (Atomic $a) use ($codebase): bool { - return $a->hasCountableInterface($codebase); - } + fn(Atomic $a): bool => $a->hasCountableInterface($codebase) ) ) ); @@ -486,9 +463,7 @@ public function hasArrayAccessInterface(Codebase $codebase): bool $this->extra_types && array_filter( $this->extra_types, - function (Atomic $a) use ($codebase): bool { - return $a->hasArrayAccessInterface($codebase); - } + fn(Atomic $a): bool => $a->hasArrayAccessInterface($codebase) ) ) ); @@ -625,11 +600,10 @@ abstract public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $php_major_version, - int $php_minor_version + int $analysis_php_version_id ): ?string; - abstract public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool; + abstract public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool; public function replaceTemplateTypesWithStandins( TemplateResult $template_result, @@ -803,7 +777,7 @@ public function isFalsy(): bool return true; } - if ($this instanceof TArray && $this->getId() === 'array') { + if ($this instanceof TArray && $this->isEmptyArray()) { return true; } diff --git a/src/Psalm/Type/Atomic/CallableTrait.php b/src/Psalm/Type/Atomic/CallableTrait.php index cd0c026d0c1..062cd647187 100644 --- a/src/Psalm/Type/Atomic/CallableTrait.php +++ b/src/Psalm/Type/Atomic/CallableTrait.php @@ -141,8 +141,7 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $php_major_version, - int $php_minor_version + int $analysis_php_version_id ): string { if ($this instanceof TNamedObject) { return parent::toNamespacedString($namespace, $aliased_classes, $this_class, true); diff --git a/src/Psalm/Type/Atomic/GenericTrait.php b/src/Psalm/Type/Atomic/GenericTrait.php index cbeb416155e..b633bbee642 100644 --- a/src/Psalm/Type/Atomic/GenericTrait.php +++ b/src/Psalm/Type/Atomic/GenericTrait.php @@ -58,15 +58,13 @@ public function getId(bool $nested = false): string $extra_types = '&' . implode( '&', array_map( - function ($type) { - return $type->getId(true); - }, + fn($type) => $type->getId(true), $this->extra_types ) ); } - if ($this->was_static) { + if ($this->is_static) { $extra_types .= '&static'; } } @@ -99,7 +97,7 @@ public function toNamespacedString( $value_type = $this->type_params[1]; - if ($value_type->isMixed() || $value_type->isEmpty()) { + if ($value_type->isMixed() || $value_type->isNever()) { return $base_value; } @@ -147,9 +145,8 @@ public function toNamespacedString( /** * @return string */ - function (Atomic $extra_type) use ($namespace, $aliased_classes, $this_class): string { - return $extra_type->toNamespacedString($namespace, $aliased_classes, $this_class, false); - }, + fn(Atomic $extra_type): string => + $extra_type->toNamespacedString($namespace, $aliased_classes, $this_class, false), $this->extra_types ) ); @@ -163,9 +160,8 @@ function (Atomic $extra_type) use ($namespace, $aliased_classes, $this_class): s /** * @return string */ - function (Union $type_param) use ($namespace, $aliased_classes, $this_class): string { - return $type_param->toNamespacedString($namespace, $aliased_classes, $this_class, false); - }, + fn(Union $type_param): string => + $type_param->toNamespacedString($namespace, $aliased_classes, $this_class, false), $type_params ) ) . diff --git a/src/Psalm/Type/Atomic/HasIntersectionTrait.php b/src/Psalm/Type/Atomic/HasIntersectionTrait.php index 41743192c91..368ebfef191 100644 --- a/src/Psalm/Type/Atomic/HasIntersectionTrait.php +++ b/src/Psalm/Type/Atomic/HasIntersectionTrait.php @@ -38,19 +38,12 @@ private function getNamespacedIntersectionTypes( * * @return string */ - function (Atomic $extra_type) use ( + fn(Atomic $extra_type): string => $extra_type->toNamespacedString( $namespace, $aliased_classes, $this_class, $use_phpdoc_format - ): string { - return $extra_type->toNamespacedString( - $namespace, - $aliased_classes, - $this_class, - $use_phpdoc_format - ); - }, + ), $this->extra_types ) ); diff --git a/src/Psalm/Type/Atomic/Scalar.php b/src/Psalm/Type/Atomic/Scalar.php index f37518cd289..dbc2f1a4691 100644 --- a/src/Psalm/Type/Atomic/Scalar.php +++ b/src/Psalm/Type/Atomic/Scalar.php @@ -6,7 +6,7 @@ abstract class Scalar extends Atomic { - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return true; } diff --git a/src/Psalm/Type/Atomic/TAnonymousClassInstance.php b/src/Psalm/Type/Atomic/TAnonymousClassInstance.php index 1d8583e41f8..5c066a03d8f 100644 --- a/src/Psalm/Type/Atomic/TAnonymousClassInstance.php +++ b/src/Psalm/Type/Atomic/TAnonymousClassInstance.php @@ -15,9 +15,9 @@ class TAnonymousClassInstance extends TNamedObject /** * @param string $value the name of the object */ - public function __construct(string $value, bool $was_static = false, ?string $extends = null) + public function __construct(string $value, bool $is_static = false, ?string $extends = null) { - parent::__construct($value, $was_static); + parent::__construct($value, $is_static); $this->extends = $extends; } @@ -26,12 +26,9 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $php_major_version, - int $php_minor_version + int $analysis_php_version_id ): ?string { - return $php_major_version > 7 - || ($php_major_version === 7 && $php_minor_version >= 2) - ? ($this->extends ?? 'object') : null; + return $analysis_php_version_id >= 7_02_00 ? ($this->extends ?? 'object') : null; } /** diff --git a/src/Psalm/Type/Atomic/TArray.php b/src/Psalm/Type/Atomic/TArray.php index 05e31a4762f..cf8ac756c7b 100644 --- a/src/Psalm/Type/Atomic/TArray.php +++ b/src/Psalm/Type/Atomic/TArray.php @@ -47,13 +47,12 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $php_major_version, - int $php_minor_version + int $analysis_php_version_id ): string { return $this->getKey(); } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return $this->type_params[0]->isArrayKey() && $this->type_params[1]->isMixed(); } @@ -92,4 +91,9 @@ public function getAssertionString(bool $exact = false): string return $this->toNamespacedString(null, [], null, false); } + + public function isEmptyArray(): bool + { + return $this->type_params[1]->isNever(); + } } diff --git a/src/Psalm/Type/Atomic/TArrayKey.php b/src/Psalm/Type/Atomic/TArrayKey.php index 038b85cfc73..7c53bd1dc6c 100644 --- a/src/Psalm/Type/Atomic/TArrayKey.php +++ b/src/Psalm/Type/Atomic/TArrayKey.php @@ -24,13 +24,12 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $php_major_version, - int $php_minor_version + int $analysis_php_version_id ): ?string { return null; } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return false; } diff --git a/src/Psalm/Type/Atomic/TEmpty.php b/src/Psalm/Type/Atomic/TAssertionEmpty.php similarity index 57% rename from src/Psalm/Type/Atomic/TEmpty.php rename to src/Psalm/Type/Atomic/TAssertionEmpty.php index 2cfa456f779..c6a090836f0 100644 --- a/src/Psalm/Type/Atomic/TEmpty.php +++ b/src/Psalm/Type/Atomic/TAssertionEmpty.php @@ -2,12 +2,12 @@ namespace Psalm\Type\Atomic; +use Psalm\Type\Atomic; + /** - * Denotes the `empty` type, used to describe a type corresponding to no value whatsoever. - * Empty arrays `[]` have the type `array`. - * @deprecated Will be replaced by TNever when in type context and TAssertionEmpty for assertion context in Psalm 5 + * Represents any value that returns true to empty(). This is used for assertions */ -class TEmpty extends Scalar +class TAssertionEmpty extends Atomic { public function __toString(): string { @@ -26,9 +26,13 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $php_major_version, - int $php_minor_version + int $analysis_php_version_id ): ?string { return null; } + + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool + { + return false; + } } diff --git a/src/Psalm/Type/Atomic/TAssertionFalsy.php b/src/Psalm/Type/Atomic/TAssertionFalsy.php index db43e12bf75..01ca6cc429c 100644 --- a/src/Psalm/Type/Atomic/TAssertionFalsy.php +++ b/src/Psalm/Type/Atomic/TAssertionFalsy.php @@ -31,13 +31,12 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $php_major_version, - int $php_minor_version + int $analysis_php_version_id ): ?string { return null; } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return false; } diff --git a/src/Psalm/Type/Atomic/TBool.php b/src/Psalm/Type/Atomic/TBool.php index adae27f9d58..936085be193 100644 --- a/src/Psalm/Type/Atomic/TBool.php +++ b/src/Psalm/Type/Atomic/TBool.php @@ -24,9 +24,8 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $php_major_version, - int $php_minor_version + int $analysis_php_version_id ): ?string { - return $php_major_version >= 7 ? 'bool' : null; + return $analysis_php_version_id >= 7_00_00 ? 'bool' : null; } } diff --git a/src/Psalm/Type/Atomic/TCallable.php b/src/Psalm/Type/Atomic/TCallable.php index fb92d7e6786..217c717fe00 100644 --- a/src/Psalm/Type/Atomic/TCallable.php +++ b/src/Psalm/Type/Atomic/TCallable.php @@ -23,13 +23,12 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $php_major_version, - int $php_minor_version + int $analysis_php_version_id ): string { return 'callable'; } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return $this->params === null && $this->return_type === null; } diff --git a/src/Psalm/Type/Atomic/TCallableObject.php b/src/Psalm/Type/Atomic/TCallableObject.php index 3ba0a8a7b56..086fc4140c5 100644 --- a/src/Psalm/Type/Atomic/TCallableObject.php +++ b/src/Psalm/Type/Atomic/TCallableObject.php @@ -24,15 +24,12 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $php_major_version, - int $php_minor_version + int $analysis_php_version_id ): ?string { - return $php_major_version > 7 - || ($php_major_version === 7 && $php_minor_version >= 2) - ? 'object' : null; + return $analysis_php_version_id >= 7_02_00 ? 'object' : null; } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return false; } diff --git a/src/Psalm/Type/Atomic/TCallableString.php b/src/Psalm/Type/Atomic/TCallableString.php index 803120df672..c4cdeb42662 100644 --- a/src/Psalm/Type/Atomic/TCallableString.php +++ b/src/Psalm/Type/Atomic/TCallableString.php @@ -18,7 +18,7 @@ public function getId(bool $nested = false): string return $this->getKey(); } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return false; } diff --git a/src/Psalm/Type/Atomic/TClassConstant.php b/src/Psalm/Type/Atomic/TClassConstant.php index 7790806d32a..2b71c1ae33d 100644 --- a/src/Psalm/Type/Atomic/TClassConstant.php +++ b/src/Psalm/Type/Atomic/TClassConstant.php @@ -49,13 +49,12 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $php_major_version, - int $php_minor_version + int $analysis_php_version_id ): ?string { return null; } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return false; } diff --git a/src/Psalm/Type/Atomic/TClassString.php b/src/Psalm/Type/Atomic/TClassString.php index 745778c7369..33658d0e203 100644 --- a/src/Psalm/Type/Atomic/TClassString.php +++ b/src/Psalm/Type/Atomic/TClassString.php @@ -66,8 +66,7 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $php_major_version, - int $php_minor_version + int $analysis_php_version_id ): ?string { return 'string'; } @@ -104,7 +103,7 @@ public function toNamespacedString( return 'class-string<\\' . $this->as . '>'; } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return false; } diff --git a/src/Psalm/Type/Atomic/TClassStringMap.php b/src/Psalm/Type/Atomic/TClassStringMap.php index edef7eec388..6c13a7acd6a 100644 --- a/src/Psalm/Type/Atomic/TClassStringMap.php +++ b/src/Psalm/Type/Atomic/TClassStringMap.php @@ -126,13 +126,12 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $php_major_version, - int $php_minor_version + int $analysis_php_version_id ): string { return 'array'; } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return false; } diff --git a/src/Psalm/Type/Atomic/TClosedResource.php b/src/Psalm/Type/Atomic/TClosedResource.php index c8391b4c532..8ebbbfaeffd 100644 --- a/src/Psalm/Type/Atomic/TClosedResource.php +++ b/src/Psalm/Type/Atomic/TClosedResource.php @@ -31,13 +31,12 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $php_major_version, - int $php_minor_version + int $analysis_php_version_id ): ?string { return null; } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return false; } diff --git a/src/Psalm/Type/Atomic/TClosure.php b/src/Psalm/Type/Atomic/TClosure.php index 5e3a9dfe08f..7703aa82cb7 100644 --- a/src/Psalm/Type/Atomic/TClosure.php +++ b/src/Psalm/Type/Atomic/TClosure.php @@ -12,7 +12,7 @@ class TClosure extends TNamedObject /** @var array */ public $byref_uses = []; - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return false; } diff --git a/src/Psalm/Type/Atomic/TConditional.php b/src/Psalm/Type/Atomic/TConditional.php index 2c12fea6078..496e1b3d492 100644 --- a/src/Psalm/Type/Atomic/TConditional.php +++ b/src/Psalm/Type/Atomic/TConditional.php @@ -106,8 +106,7 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $php_major_version, - int $php_minor_version + int $analysis_php_version_id ): ?string { return null; } @@ -130,7 +129,7 @@ public function getChildNodes(): array return [$this->conditional_type, $this->if_type, $this->else_type]; } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return false; } diff --git a/src/Psalm/Type/Atomic/TDependentGetClass.php b/src/Psalm/Type/Atomic/TDependentGetClass.php index d53e53b2b6c..a22e8476132 100644 --- a/src/Psalm/Type/Atomic/TDependentGetClass.php +++ b/src/Psalm/Type/Atomic/TDependentGetClass.php @@ -56,7 +56,7 @@ public function getReplacement(): Atomic return new TClassString(); } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return false; } diff --git a/src/Psalm/Type/Atomic/TDependentGetDebugType.php b/src/Psalm/Type/Atomic/TDependentGetDebugType.php index 1ca555270f5..1bdf96486eb 100644 --- a/src/Psalm/Type/Atomic/TDependentGetDebugType.php +++ b/src/Psalm/Type/Atomic/TDependentGetDebugType.php @@ -39,7 +39,7 @@ public function getReplacement(): Atomic return new TString(); } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return false; } diff --git a/src/Psalm/Type/Atomic/TDependentGetType.php b/src/Psalm/Type/Atomic/TDependentGetType.php index 1c06fcda5df..4b91601c33b 100644 --- a/src/Psalm/Type/Atomic/TDependentGetType.php +++ b/src/Psalm/Type/Atomic/TDependentGetType.php @@ -22,7 +22,7 @@ public function __construct(string $typeof) $this->typeof = $typeof; } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return false; } diff --git a/src/Psalm/Type/Atomic/TDependentListKey.php b/src/Psalm/Type/Atomic/TDependentListKey.php index c5bbd991e76..74f59173330 100644 --- a/src/Psalm/Type/Atomic/TDependentListKey.php +++ b/src/Psalm/Type/Atomic/TDependentListKey.php @@ -44,7 +44,7 @@ public function getReplacement(): Atomic return new TInt(); } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return false; } diff --git a/src/Psalm/Type/Atomic/TEnumCase.php b/src/Psalm/Type/Atomic/TEnumCase.php index a6242306f8d..de6f0a52fe9 100644 --- a/src/Psalm/Type/Atomic/TEnumCase.php +++ b/src/Psalm/Type/Atomic/TEnumCase.php @@ -33,13 +33,12 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $php_major_version, - int $php_minor_version + int $analysis_php_version_id ): ?string { return $this->value; } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return false; } diff --git a/src/Psalm/Type/Atomic/TFalse.php b/src/Psalm/Type/Atomic/TFalse.php index 5ffd3112f39..c52fa09bed0 100644 --- a/src/Psalm/Type/Atomic/TFalse.php +++ b/src/Psalm/Type/Atomic/TFalse.php @@ -17,7 +17,7 @@ public function getKey(bool $include_extra = true): string return 'false'; } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return false; } diff --git a/src/Psalm/Type/Atomic/TFloat.php b/src/Psalm/Type/Atomic/TFloat.php index 76a7666a2ff..0acdbff07d0 100644 --- a/src/Psalm/Type/Atomic/TFloat.php +++ b/src/Psalm/Type/Atomic/TFloat.php @@ -24,9 +24,8 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $php_major_version, - int $php_minor_version + int $analysis_php_version_id ): ?string { - return $php_major_version >= 7 ? 'float' : null; + return $analysis_php_version_id >= 7_00_00 ? 'float' : null; } } diff --git a/src/Psalm/Type/Atomic/TGenericObject.php b/src/Psalm/Type/Atomic/TGenericObject.php index e0f77432e19..8b05040a4b9 100644 --- a/src/Psalm/Type/Atomic/TGenericObject.php +++ b/src/Psalm/Type/Atomic/TGenericObject.php @@ -57,7 +57,7 @@ public function getKey(bool $include_extra = true): string return $this->value . '<' . substr($s, 0, -2) . '>' . $extra_types; } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return false; } @@ -69,16 +69,11 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $php_major_version, - int $php_minor_version + int $analysis_php_version_id ): ?string { $result = $this->toNamespacedString($namespace, $aliased_classes, $this_class, true); $intersection = strrpos($result, '&'); - if ($intersection === false || ( - ($php_major_version === 8 && $php_minor_version >= 1) || - ($php_major_version >= 9) - ) - ) { + if ($intersection === false || $analysis_php_version_id >= 8_01_00) { return $result; } return substr($result, $intersection+1); diff --git a/src/Psalm/Type/Atomic/THtmlEscapedString.php b/src/Psalm/Type/Atomic/THtmlEscapedString.php deleted file mode 100644 index da315ece4b8..00000000000 --- a/src/Psalm/Type/Atomic/THtmlEscapedString.php +++ /dev/null @@ -1,25 +0,0 @@ -getKey(); - } - - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool - { - return false; - } -} diff --git a/src/Psalm/Type/Atomic/TInt.php b/src/Psalm/Type/Atomic/TInt.php index 27191b81866..5cdccd63a38 100644 --- a/src/Psalm/Type/Atomic/TInt.php +++ b/src/Psalm/Type/Atomic/TInt.php @@ -24,9 +24,8 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $php_major_version, - int $php_minor_version + int $analysis_php_version_id ): ?string { - return $php_major_version >= 7 ? 'int' : null; + return $analysis_php_version_id >= 7_00_00 ? 'int' : null; } } diff --git a/src/Psalm/Type/Atomic/TIntMask.php b/src/Psalm/Type/Atomic/TIntMask.php index cf6cd4c61b9..86a4b5fccfe 100644 --- a/src/Psalm/Type/Atomic/TIntMask.php +++ b/src/Psalm/Type/Atomic/TIntMask.php @@ -64,7 +64,7 @@ public function toNamespacedString( return 'int-mask<' . substr($s, 0, -2) . '>'; } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return false; } diff --git a/src/Psalm/Type/Atomic/TIntMaskOf.php b/src/Psalm/Type/Atomic/TIntMaskOf.php index 70e5645239e..512dd42bc3b 100644 --- a/src/Psalm/Type/Atomic/TIntMaskOf.php +++ b/src/Psalm/Type/Atomic/TIntMaskOf.php @@ -50,7 +50,7 @@ public function toNamespacedString( . '>'; } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return false; } diff --git a/src/Psalm/Type/Atomic/TIntRange.php b/src/Psalm/Type/Atomic/TIntRange.php index 5b2adcddf52..64269371319 100644 --- a/src/Psalm/Type/Atomic/TIntRange.php +++ b/src/Psalm/Type/Atomic/TIntRange.php @@ -38,7 +38,7 @@ public function getKey(bool $include_extra = true): string return 'int<' . ($this->min_bound ?? 'min') . ', ' . ($this->max_bound ?? 'max') . '>'; } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return false; } diff --git a/src/Psalm/Type/Atomic/TIterable.php b/src/Psalm/Type/Atomic/TIterable.php index b7aea22cf56..c0c1970a614 100644 --- a/src/Psalm/Type/Atomic/TIterable.php +++ b/src/Psalm/Type/Atomic/TIterable.php @@ -89,16 +89,12 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $php_major_version, - int $php_minor_version + int $analysis_php_version_id ): ?string { - return $php_major_version > 7 - || ($php_major_version === 7 && $php_minor_version >= 1) - ? 'iterable' - : null; + return $analysis_php_version_id >= 7_01_00 ? 'iterable' : null; } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return $this->type_params[0]->isMixed() && $this->type_params[1]->isMixed(); } diff --git a/src/Psalm/Type/Atomic/TKeyOfClassConstant.php b/src/Psalm/Type/Atomic/TKeyOfClassConstant.php index 05d2e7101a0..189cda0df2a 100644 --- a/src/Psalm/Type/Atomic/TKeyOfClassConstant.php +++ b/src/Psalm/Type/Atomic/TKeyOfClassConstant.php @@ -47,13 +47,12 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $php_major_version, - int $php_minor_version + int $analysis_php_version_id ): ?string { return null; } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return false; } diff --git a/src/Psalm/Type/Atomic/TKeyedArray.php b/src/Psalm/Type/Atomic/TKeyedArray.php index c37c07d5fd0..31b63573c94 100644 --- a/src/Psalm/Type/Atomic/TKeyedArray.php +++ b/src/Psalm/Type/Atomic/TKeyedArray.php @@ -213,13 +213,12 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $php_major_version, - int $php_minor_version + int $analysis_php_version_id ): string { return $this->getKey(); } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return false; } diff --git a/src/Psalm/Type/Atomic/TList.php b/src/Psalm/Type/Atomic/TList.php index 3d5136530d4..75919c8b44d 100644 --- a/src/Psalm/Type/Atomic/TList.php +++ b/src/Psalm/Type/Atomic/TList.php @@ -97,13 +97,12 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $php_major_version, - int $php_minor_version + int $analysis_php_version_id ): string { return 'array'; } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return false; } diff --git a/src/Psalm/Type/Atomic/TLiteralClassString.php b/src/Psalm/Type/Atomic/TLiteralClassString.php index a36429d3cf7..5cdb2125109 100644 --- a/src/Psalm/Type/Atomic/TLiteralClassString.php +++ b/src/Psalm/Type/Atomic/TLiteralClassString.php @@ -42,13 +42,12 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $php_major_version, - int $php_minor_version + int $analysis_php_version_id ): string { return 'string'; } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return false; } diff --git a/src/Psalm/Type/Atomic/TLowercaseString.php b/src/Psalm/Type/Atomic/TLowercaseString.php index b1b34cd4a43..5fc884fe570 100644 --- a/src/Psalm/Type/Atomic/TLowercaseString.php +++ b/src/Psalm/Type/Atomic/TLowercaseString.php @@ -9,7 +9,7 @@ public function getId(bool $nested = false): string return 'lowercase-string'; } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return false; } diff --git a/src/Psalm/Type/Atomic/TMixed.php b/src/Psalm/Type/Atomic/TMixed.php index 00aaa08ade7..b31dfe21e72 100644 --- a/src/Psalm/Type/Atomic/TMixed.php +++ b/src/Psalm/Type/Atomic/TMixed.php @@ -34,15 +34,14 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $php_major_version, - int $php_minor_version + int $analysis_php_version_id ): ?string { - return $php_major_version >= 8 ? 'mixed' : null; + return $analysis_php_version_id >= 8_00_00 ? 'mixed' : null; } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { - return $php_major_version >= 8; + return $analysis_php_version_id >= 8_00_00; } public function getAssertionString(bool $exact = false): string diff --git a/src/Psalm/Type/Atomic/TNamedObject.php b/src/Psalm/Type/Atomic/TNamedObject.php index e9eebc94656..07e725f86ac 100644 --- a/src/Psalm/Type/Atomic/TNamedObject.php +++ b/src/Psalm/Type/Atomic/TNamedObject.php @@ -27,7 +27,7 @@ class TNamedObject extends Atomic /** * @var bool */ - public $was_static = false; + public $is_static = false; /** * Whether or not this type can represent a child of the class named in $value @@ -38,14 +38,14 @@ class TNamedObject extends Atomic /** * @param string $value the name of the object */ - public function __construct(string $value, bool $was_static = false, bool $definite_class = false) + public function __construct(string $value, bool $is_static = false, bool $definite_class = false) { if ($value[0] === '\\') { $value = substr($value, 1); } $this->value = $value; - $this->was_static = $was_static; + $this->is_static = $is_static; $this->definite_class = $definite_class; } @@ -69,15 +69,13 @@ public function getId(bool $nested = false): string return $this->value . '&' . implode( '&', array_map( - function ($type) { - return $type->getId(true); - }, + fn($type) => $type->getId(true), $this->extra_types ) ); } - return $this->was_static ? $this->value . '&static' : $this->value; + return $this->is_static ? $this->value . '&static' : $this->value; } /** @@ -107,7 +105,7 @@ public function toNamespacedString( $aliased_classes, $this_class, true, - $this->was_static + $this->is_static ) . $intersection_types; } @@ -118,32 +116,27 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $php_major_version, - int $php_minor_version + int $analysis_php_version_id ): ?string { if ($this->value === 'static') { - return $php_major_version >= 8 ? 'static' : null; + return $analysis_php_version_id >= 8_00_00 ? 'static' : null; } - if ($this->was_static && $this->value === $this_class) { - return $php_major_version >= 8 ? 'static' : 'self'; + if ($this->is_static && $this->value === $this_class) { + return $analysis_php_version_id >= 8_00_00 ? 'static' : 'self'; } $result = $this->toNamespacedString($namespace, $aliased_classes, $this_class, false); $intersection = strrpos($result, '&'); - if ($intersection === false || ( - ($php_major_version === 8 && $php_minor_version >= 1) || - ($php_major_version >= 9) - ) - ) { + if ($intersection === false || $analysis_php_version_id >= 8_01_00) { return $result; } return substr($result, $intersection+1); } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { - return ($this->value !== 'static' && $this->was_static === false) || $php_major_version >= 8; + return ($this->value !== 'static' && $this->is_static === false) || $analysis_php_version_id >= 8_00_00; } public function replaceTemplateTypesWithArgTypes( diff --git a/src/Psalm/Type/Atomic/TNever.php b/src/Psalm/Type/Atomic/TNever.php index 1b972d0b3ea..5e624c0ab49 100644 --- a/src/Psalm/Type/Atomic/TNever.php +++ b/src/Psalm/Type/Atomic/TNever.php @@ -27,13 +27,12 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $php_major_version, - int $php_minor_version + int $analysis_php_version_id ): ?string { return null; } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return false; } diff --git a/src/Psalm/Type/Atomic/TNonEmptyLowercaseString.php b/src/Psalm/Type/Atomic/TNonEmptyLowercaseString.php index 1d331e8d138..9769e7f605b 100644 --- a/src/Psalm/Type/Atomic/TNonEmptyLowercaseString.php +++ b/src/Psalm/Type/Atomic/TNonEmptyLowercaseString.php @@ -15,7 +15,7 @@ public function getId(bool $nested = false): string /** * @return false */ - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return false; } diff --git a/src/Psalm/Type/Atomic/TNonspecificLiteralInt.php b/src/Psalm/Type/Atomic/TNonspecificLiteralInt.php index 8a04903969a..ac920d5a68c 100644 --- a/src/Psalm/Type/Atomic/TNonspecificLiteralInt.php +++ b/src/Psalm/Type/Atomic/TNonspecificLiteralInt.php @@ -13,7 +13,7 @@ public function __toString(): string return 'literal-int'; } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return false; } diff --git a/src/Psalm/Type/Atomic/TNonspecificLiteralString.php b/src/Psalm/Type/Atomic/TNonspecificLiteralString.php index 30d6ac97c1d..fe4c46b57f0 100644 --- a/src/Psalm/Type/Atomic/TNonspecificLiteralString.php +++ b/src/Psalm/Type/Atomic/TNonspecificLiteralString.php @@ -13,7 +13,7 @@ public function __toString(): string return 'literal-string'; } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return false; } diff --git a/src/Psalm/Type/Atomic/TNull.php b/src/Psalm/Type/Atomic/TNull.php index d95759e63d9..2efc36d2417 100644 --- a/src/Psalm/Type/Atomic/TNull.php +++ b/src/Psalm/Type/Atomic/TNull.php @@ -26,13 +26,12 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $php_major_version, - int $php_minor_version + int $analysis_php_version_id ): ?string { return null; } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return false; } diff --git a/src/Psalm/Type/Atomic/TNumeric.php b/src/Psalm/Type/Atomic/TNumeric.php index 8651ff1c998..6b988210a6d 100644 --- a/src/Psalm/Type/Atomic/TNumeric.php +++ b/src/Psalm/Type/Atomic/TNumeric.php @@ -24,13 +24,12 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $php_major_version, - int $php_minor_version + int $analysis_php_version_id ): ?string { return null; } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return false; } diff --git a/src/Psalm/Type/Atomic/TNumericString.php b/src/Psalm/Type/Atomic/TNumericString.php index c86027a442f..001eed685d0 100644 --- a/src/Psalm/Type/Atomic/TNumericString.php +++ b/src/Psalm/Type/Atomic/TNumericString.php @@ -22,7 +22,7 @@ public function getId(bool $nested = false): string return $this->getKey(); } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return false; } diff --git a/src/Psalm/Type/Atomic/TObject.php b/src/Psalm/Type/Atomic/TObject.php index e2a87219457..5b8e81743ed 100644 --- a/src/Psalm/Type/Atomic/TObject.php +++ b/src/Psalm/Type/Atomic/TObject.php @@ -26,16 +26,12 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $php_major_version, - int $php_minor_version + int $analysis_php_version_id ): ?string { - return $php_major_version > 7 - || ($php_major_version === 7 && $php_minor_version >= 2) - ? $this->getKey() - : null; + return $analysis_php_version_id >= 7_02_00 ? $this->getKey() : null; } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return true; } diff --git a/src/Psalm/Type/Atomic/TObjectWithProperties.php b/src/Psalm/Type/Atomic/TObjectWithProperties.php index fa68b797275..308a61d7401 100644 --- a/src/Psalm/Type/Atomic/TObjectWithProperties.php +++ b/src/Psalm/Type/Atomic/TObjectWithProperties.php @@ -62,9 +62,7 @@ public function __toString(): string /** * @param string|int $name */ - function ($name, Union $type): string { - return $name . ($type->possibly_undefined ? '?' : '') . ':' . $type; - }, + fn($name, Union $type): string => $name . ($type->possibly_undefined ? '?' : '') . ':' . $type, array_keys($this->properties), $this->properties ) @@ -73,9 +71,7 @@ function ($name, Union $type): string { $methods_string = implode( ', ', array_map( - function (string $name): string { - return $name . '()'; - }, + fn(string $name): string => $name . '()', array_keys($this->methods) ) ); @@ -100,9 +96,7 @@ public function getId(bool $nested = false): string /** * @param string|int $name */ - function ($name, Union $type): string { - return $name . ($type->possibly_undefined ? '?' : '') . ':' . $type->getId(); - }, + fn($name, Union $type): string => $name . ($type->possibly_undefined ? '?' : '') . ':' . $type->getId(), array_keys($this->properties), $this->properties ) @@ -111,9 +105,7 @@ function ($name, Union $type): string { $methods_string = implode( ', ', array_map( - function (string $name): string { - return $name . '()'; - }, + fn(string $name): string => $name . '()', array_keys($this->methods) ) ); @@ -145,22 +137,16 @@ public function toNamespacedString( /** * @param string|int $name */ - function ( - $name, - Union $type - ) use ( - $namespace, - $aliased_classes, - $this_class, - $use_phpdoc_format - ): string { - return $name . ($type->possibly_undefined ? '?' : '') . ':' . $type->toNamespacedString( + fn($name, Union $type): string => + $name . + ($type->possibly_undefined ? '?' : '') + . ':' + . $type->toNamespacedString( $namespace, $aliased_classes, $this_class, $use_phpdoc_format - ); - }, + ), array_keys($this->properties), $this->properties ) @@ -175,13 +161,12 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $php_major_version, - int $php_minor_version + int $analysis_php_version_id ): string { return $this->getKey(); } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return false; } diff --git a/src/Psalm/Type/Atomic/TPositiveInt.php b/src/Psalm/Type/Atomic/TPositiveInt.php index eb345a0ba1a..b8335df859a 100644 --- a/src/Psalm/Type/Atomic/TPositiveInt.php +++ b/src/Psalm/Type/Atomic/TPositiveInt.php @@ -20,7 +20,7 @@ public function __toString(): string /** * @return false */ - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return false; } diff --git a/src/Psalm/Type/Atomic/TResource.php b/src/Psalm/Type/Atomic/TResource.php index 49ee650902b..47bde1d24aa 100644 --- a/src/Psalm/Type/Atomic/TResource.php +++ b/src/Psalm/Type/Atomic/TResource.php @@ -26,13 +26,12 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $php_major_version, - int $php_minor_version + int $analysis_php_version_id ): ?string { return null; } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return false; } diff --git a/src/Psalm/Type/Atomic/TScalar.php b/src/Psalm/Type/Atomic/TScalar.php index 0cb6f0b80c1..5954a75e7f0 100644 --- a/src/Psalm/Type/Atomic/TScalar.php +++ b/src/Psalm/Type/Atomic/TScalar.php @@ -25,13 +25,12 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $php_major_version, - int $php_minor_version + int $analysis_php_version_id ): ?string { return null; } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return false; } diff --git a/src/Psalm/Type/Atomic/TString.php b/src/Psalm/Type/Atomic/TString.php index 7312b16f43f..88ddebd0a6f 100644 --- a/src/Psalm/Type/Atomic/TString.php +++ b/src/Psalm/Type/Atomic/TString.php @@ -14,10 +14,9 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $php_major_version, - int $php_minor_version + int $analysis_php_version_id ): ?string { - return $php_major_version >= 7 ? 'string' : null; + return $analysis_php_version_id >= 7_00_00 ? 'string' : null; } public function __toString(): string diff --git a/src/Psalm/Type/Atomic/TTemplateIndexedAccess.php b/src/Psalm/Type/Atomic/TTemplateIndexedAccess.php index 72e72c38f7c..69de28a65d6 100644 --- a/src/Psalm/Type/Atomic/TTemplateIndexedAccess.php +++ b/src/Psalm/Type/Atomic/TTemplateIndexedAccess.php @@ -53,13 +53,12 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $php_major_version, - int $php_minor_version + int $analysis_php_version_id ): ?string { return null; } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return false; } diff --git a/src/Psalm/Type/Atomic/TTemplateParam.php b/src/Psalm/Type/Atomic/TTemplateParam.php index 66c0af836c5..593a8ff3a6d 100644 --- a/src/Psalm/Type/Atomic/TTemplateParam.php +++ b/src/Psalm/Type/Atomic/TTemplateParam.php @@ -62,9 +62,7 @@ public function getId(bool $nested = false): string { if ($this->extra_types) { return '(' . $this->param_name . ':' . $this->defining_class . ' as ' . $this->as->getId() - . ')&' . implode('&', array_map(function ($type) { - return $type->getId(true); - }, $this->extra_types)); + . ')&' . implode('&', array_map(fn($type) => $type->getId(true), $this->extra_types)); } return ($nested ? '(' : '') . $this->param_name @@ -81,8 +79,7 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $php_major_version, - int $php_minor_version + int $analysis_php_version_id ): ?string { return null; } @@ -121,7 +118,7 @@ public function getChildNodes(): array return [$this->as]; } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return false; } diff --git a/src/Psalm/Type/Atomic/TTraitString.php b/src/Psalm/Type/Atomic/TTraitString.php index 65008a4d061..d06a057ec34 100644 --- a/src/Psalm/Type/Atomic/TTraitString.php +++ b/src/Psalm/Type/Atomic/TTraitString.php @@ -29,8 +29,7 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $php_major_version, - int $php_minor_version + int $analysis_php_version_id ): ?string { return 'string'; } @@ -48,7 +47,7 @@ public function toNamespacedString( return 'trait-string'; } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return false; } diff --git a/src/Psalm/Type/Atomic/TTrue.php b/src/Psalm/Type/Atomic/TTrue.php index 29313a0d457..637e52ba24b 100644 --- a/src/Psalm/Type/Atomic/TTrue.php +++ b/src/Psalm/Type/Atomic/TTrue.php @@ -17,7 +17,7 @@ public function getKey(bool $include_extra = true): string return 'true'; } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return false; } diff --git a/src/Psalm/Type/Atomic/TTypeAlias.php b/src/Psalm/Type/Atomic/TTypeAlias.php index 788669128b7..c3ff90378f9 100644 --- a/src/Psalm/Type/Atomic/TTypeAlias.php +++ b/src/Psalm/Type/Atomic/TTypeAlias.php @@ -52,9 +52,7 @@ public function getId(bool $nested = false): string return $this->getKey() . '&' . implode( '&', array_map( - function ($type) { - return $type->getId(true); - }, + fn($type) => $type->getId(true), $this->extra_types ) ); @@ -70,13 +68,12 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $php_major_version, - int $php_minor_version + int $analysis_php_version_id ): ?string { return null; } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return false; } diff --git a/src/Psalm/Type/Atomic/TValueOfClassConstant.php b/src/Psalm/Type/Atomic/TValueOfClassConstant.php index cbeb6b4c241..6821650bae1 100644 --- a/src/Psalm/Type/Atomic/TValueOfClassConstant.php +++ b/src/Psalm/Type/Atomic/TValueOfClassConstant.php @@ -44,13 +44,12 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $php_major_version, - int $php_minor_version + int $analysis_php_version_id ): ?string { return null; } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return false; } diff --git a/src/Psalm/Type/Atomic/TVoid.php b/src/Psalm/Type/Atomic/TVoid.php index cec58cd4e99..9c5eb290f9c 100644 --- a/src/Psalm/Type/Atomic/TVoid.php +++ b/src/Psalm/Type/Atomic/TVoid.php @@ -26,15 +26,12 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $php_major_version, - int $php_minor_version + int $analysis_php_version_id ): ?string { - return $php_major_version > 7 - || ($php_major_version === 7 && $php_minor_version >= 1) - ? $this->getKey() : null; + return $analysis_php_version_id >= 7_01_00 ? $this->getKey() : null; } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return true; } diff --git a/src/Psalm/Type/Reconciler.php b/src/Psalm/Type/Reconciler.php index 60e042b7583..069484d39f9 100644 --- a/src/Psalm/Type/Reconciler.php +++ b/src/Psalm/Type/Reconciler.php @@ -23,13 +23,13 @@ use Psalm\Type\Atomic\TArray; use Psalm\Type\Atomic\TArrayKey; use Psalm\Type\Atomic\TClassStringMap; -use Psalm\Type\Atomic\TEmpty; use Psalm\Type\Atomic\TFalse; use Psalm\Type\Atomic\TInt; use Psalm\Type\Atomic\TKeyedArray; use Psalm\Type\Atomic\TList; use Psalm\Type\Atomic\TMixed; use Psalm\Type\Atomic\TNamedObject; +use Psalm\Type\Atomic\TNever; use Psalm\Type\Atomic\TNull; use Psalm\Type\Atomic\TObject; use Psalm\Type\Atomic\TScalar; @@ -57,6 +57,8 @@ use function strtolower; use function substr; +use const JSON_THROW_ON_ERROR; + class Reconciler { public const RECONCILIATION_OK = 0; @@ -190,11 +192,11 @@ public static function reconcileKeyedTypes( $nested_negated = !$negated; /** @var array>> */ - $data = json_decode(substr($new_type_part_part, 2), true); + $data = json_decode(substr($new_type_part_part, 2), true, 512, JSON_THROW_ON_ERROR); } else { $nested_negated = $negated; /** @var array>> */ - $data = json_decode(substr($new_type_part_part, 1), true); + $data = json_decode(substr($new_type_part_part, 1), true, 512, JSON_THROW_ON_ERROR); } $existing_types = self::reconcileKeyedTypes( @@ -231,7 +233,7 @@ public static function reconcileKeyedTypes( ); if ($result_type_candidate->isUnionEmpty()) { - $result_type_candidate->addType(new TEmpty); + $result_type_candidate->addType(new TNever); } $orred_type = Type::combineUnionTypes( @@ -248,7 +250,7 @@ public static function reconcileKeyedTypes( throw new UnexpectedValueException('$result_type should not be null'); } - if (!$did_type_exist && $result_type->isEmpty()) { + if (!$did_type_exist && $result_type->isNever()) { continue; } @@ -653,7 +655,7 @@ private static function getValueForKey( } } elseif ($existing_key_type_part instanceof TClassStringMap) { return Type::getMixed(); - } elseif ($existing_key_type_part instanceof TEmpty + } elseif ($existing_key_type_part instanceof TNever || ($existing_key_type_part instanceof TMixed && $existing_key_type_part->from_loop_isset) ) { @@ -1024,7 +1026,7 @@ private static function adjustTKeyedArrayType( foreach ($existing_types[$base_key]->getAtomicTypes() as $base_atomic_type) { if ($base_atomic_type instanceof TKeyedArray || ($base_atomic_type instanceof TArray - && !$base_atomic_type->type_params[1]->isEmpty()) + && !$base_atomic_type->isEmptyArray()) || $base_atomic_type instanceof TList || $base_atomic_type instanceof TClassStringMap ) { @@ -1041,7 +1043,7 @@ private static function adjustTKeyedArrayType( null ); - if (!$previous_key_type->isEmpty()) { + if (!$previous_key_type->isNever()) { $base_atomic_type->previous_key_type = $previous_key_type; } $base_atomic_type->previous_value_type = $previous_value_type; diff --git a/src/Psalm/Type/Union.php b/src/Psalm/Type/Union.php index 16d1682ae61..0388ca6e077 100644 --- a/src/Psalm/Type/Union.php +++ b/src/Psalm/Type/Union.php @@ -511,17 +511,16 @@ public function toNamespacedString( */ public function toPhpString( ?string $namespace, - array $aliased_classes, + array $aliased_classes, ?string $this_class, - int $php_major_version, - int $php_minor_version + int $analysis_php_version_id ): ?string { if (!$this->isSingleAndMaybeNullable()) { - if ($php_major_version < 8) { + if ($analysis_php_version_id < 8_00_00) { return null; } - } elseif ($php_major_version < 7 - || (isset($this->types['null']) && $php_major_version === 7 && $php_minor_version < 1) + } elseif ($analysis_php_version_id < 7_00_00 + || (isset($this->types['null']) && $analysis_php_version_id < 7_01_00) ) { return null; } @@ -551,8 +550,7 @@ public function toPhpString( $namespace, $aliased_classes, $this_class, - $php_major_version, - $php_minor_version + $analysis_php_version_id ); if (!$php_type) { @@ -571,7 +569,7 @@ public function toPhpString( return implode('|', array_unique($php_types)); } - if ($php_major_version < 8) { + if ($analysis_php_version_id < 8_00_00) { return ($nullable ? '?' : '') . implode('|', array_unique($php_types)); } if ($nullable) { @@ -580,9 +578,9 @@ public function toPhpString( return implode('|', array_unique($php_types)); } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { - if (!$this->isSingleAndMaybeNullable() && $php_major_version < 8) { + if (!$this->isSingleAndMaybeNullable() && $analysis_php_version_id < 8_00_00) { return false; } @@ -598,9 +596,7 @@ public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_ return !array_filter( $types, - function ($atomic_type) use ($php_major_version, $php_minor_version) { - return !$atomic_type->canBeFullyExpressedInPhp($php_major_version, $php_minor_version); - } + fn($atomic_type) => !$atomic_type->canBeFullyExpressedInPhp($analysis_php_version_id) ); } @@ -689,9 +685,7 @@ public function isTemplatedClassString(): bool && count( array_filter( $this->types, - function ($type): bool { - return $type instanceof TTemplateParamClass; - } + fn($type): bool => $type instanceof TTemplateParamClass ) ) === 1; } @@ -700,9 +694,7 @@ public function hasArrayAccessInterface(Codebase $codebase): bool { return (bool)array_filter( $this->types, - function ($type) use ($codebase) { - return $type->hasArrayAccessInterface($codebase); - } + fn($type) => $type->hasArrayAccessInterface($codebase) ); } @@ -718,9 +710,7 @@ public function getCallableTypes(): array { return array_filter( $this->types, - function ($type): bool { - return $type instanceof TCallable; - } + fn($type): bool => $type instanceof TCallable ); } @@ -731,9 +721,7 @@ public function getClosureTypes(): array { return array_filter( $this->types, - function ($type): bool { - return $type instanceof TClosure; - } + fn($type): bool => $type instanceof TClosure ); } @@ -775,11 +763,11 @@ public function hasNamedObjectType(): bool return false; } - public function isFormerStaticObject(): bool + public function isStaticObject(): bool { foreach ($this->types as $type) { if (!$type instanceof TNamedObject - || !$type->was_static + || !$type->is_static ) { return false; } @@ -788,11 +776,11 @@ public function isFormerStaticObject(): bool return true; } - public function hasFormerStaticObject(): bool + public function hasStaticObject(): bool { foreach ($this->types as $type) { if ($type instanceof TNamedObject - && $type->was_static + && $type->is_static ) { return true; } @@ -863,9 +851,7 @@ public function hasLiteralClassString(): bool public function hasInt(): bool { return isset($this->types['int']) || isset($this->types['array-key']) || $this->literal_int_types - || array_filter($this->types, function (Atomic $type) { - return $type instanceof TIntRange; - }); + || array_filter($this->types, fn(Atomic $type) => $type instanceof TIntRange); } public function hasPositiveInt(): bool @@ -915,18 +901,14 @@ public function hasTemplate(): bool { return (bool) array_filter( $this->types, - function (Atomic $type): bool { - return $type instanceof TTemplateParam - || ($type instanceof TNamedObject - && $type->extra_types - && array_filter( - $type->extra_types, - function ($t): bool { - return $t instanceof TTemplateParam; - } - ) - ); - } + fn(Atomic $type): bool => $type instanceof TTemplateParam + || ($type instanceof TNamedObject + && $type->extra_types + && array_filter( + $type->extra_types, + fn($t): bool => $t instanceof TTemplateParam + ) + ) ); } @@ -934,9 +916,7 @@ public function hasConditional(): bool { return (bool) array_filter( $this->types, - function (Atomic $type): bool { - return $type instanceof TConditional; - } + fn(Atomic $type): bool => $type instanceof TConditional ); } @@ -944,21 +924,17 @@ public function hasTemplateOrStatic(): bool { return (bool) array_filter( $this->types, - function (Atomic $type): bool { - return $type instanceof TTemplateParam - || ($type instanceof TNamedObject - && ($type->was_static - || ($type->extra_types - && array_filter( - $type->extra_types, - function ($t): bool { - return $t instanceof TTemplateParam; - } - ) + fn(Atomic $type): bool => $type instanceof TTemplateParam + || ($type instanceof TNamedObject + && ($type->is_static + || ($type->extra_types + && array_filter( + $type->extra_types, + fn($t): bool => $t instanceof TTemplateParam ) ) - ); - } + ) + ) ); } @@ -1050,11 +1026,6 @@ public function isGenerator(): bool && ($single_type->value === 'Generator'); } - public function isEmpty(): bool - { - return isset($this->types['empty']) && count($this->types) === 1; - } - public function substitute(Union $old_type, ?Union $new_type = null): void { if ($this->hasMixed() && !$this->isEmptyMixed()) { @@ -1192,13 +1163,11 @@ public function isInt(bool $check_templates = false): bool return count( array_filter( $this->types, - function ($type) use ($check_templates): bool { - return $type instanceof TInt - || ($check_templates - && $type instanceof TTemplateParam - && $type->as->isInt() - ); - } + fn($type): bool => $type instanceof TInt + || ($check_templates + && $type instanceof TTemplateParam + && $type->as->isInt() + ) ) ) === count($this->types); } @@ -1223,13 +1192,11 @@ public function isString(bool $check_templates = false): bool return count( array_filter( $this->types, - function ($type) use ($check_templates): bool { - return $type instanceof TString - || ($check_templates - && $type instanceof TTemplateParam - && $type->as->isString() - ); - } + fn($type): bool => $type instanceof TString + || ($check_templates + && $type instanceof TTemplateParam + && $type->as->isString() + ) ) ) === count($this->types); } @@ -1606,6 +1573,14 @@ public function getSingleAtomic(): Atomic return reset($this->types); } + public function isEmptyArray(): bool + { + return count($this->types) === 1 + && isset($this->types['array']) + && $this->types['array'] instanceof TArray + && $this->types['array']->isEmptyArray(); + } + public function isUnionEmpty(): bool { return $this->types === []; diff --git a/src/command_functions.php b/src/command_functions.php deleted file mode 100644 index 4a797d8ab7e..00000000000 --- a/src/command_functions.php +++ /dev/null @@ -1,88 +0,0 @@ - - * @deprecated going to be removed in Psalm 5 - */ -function getArguments(): array -{ - return CliUtils::getArguments(); -} - -/** - * @param string|array|null|false $f_paths - * - * @return list|null - * @deprecated going to be removed in Psalm 5 - */ -function getPathsToCheck($f_paths): ?array -{ - return CliUtils::getPathsToCheck($f_paths); -} - -/** - * @psalm-pure - * @deprecated going to be removed in Psalm 5 - */ -function getPsalmHelpText(): string -{ - return CliUtils::getPsalmHelpText(); -} - -/** @deprecated going to be removed in Psalm 5 */ -function initialiseConfig( - ?string $path_to_config, - string $current_dir, - string $output_format, - ?ClassLoader $first_autoloader, - bool $create_if_non_existent = false -): Config { - return CliUtils::initializeConfig( - $path_to_config, - $current_dir, - $output_format, - $first_autoloader, - $create_if_non_existent - ); -} - -/** @deprecated going to be removed in Psalm 5 */ -function update_config_file(Config $config, string $config_file_path, string $baseline_path): void -{ - CliUtils::updateConfigFile($config, $config_file_path, $baseline_path); -} - -/** @deprecated going to be removed in Psalm 5 */ -function get_path_to_config(array $options): ?string -{ - return CliUtils::getPathToConfig($options); -} - -/** - * @psalm-pure - * @deprecated going to be removed in Psalm 5 - */ -function getMemoryLimitInBytes(): int -{ - return CliUtils::getMemoryLimitInBytes(); -} diff --git a/src/functions.php b/src/functions.php deleted file mode 100644 index a44e53cc7e1..00000000000 --- a/src/functions.php +++ /dev/null @@ -1,13 +0,0 @@ -isUserDefined()) { - /** - * See https://github.com/runkit7/runkit_object_id for a faster native version for php <= 7.1 - * - * @param object $object - * @return int The object id - */ - function spl_object_id($object): int - { - return runkit_object_id($object); - } - } elseif (PHP_INT_SIZE === 8) { - /** - * See https://github.com/runkit7/runkit_object_id for a faster native version for php <= 7.1 - * - * @param object $object - * @return int (The object id, XORed with a random number) - */ - function spl_object_id($object): int - { - $hash = spl_object_hash($object); - // Fit this into a php long (32-bit or 64-bit signed int). - // The first 16 hex digits (64 bytes) vary, the last 16 don't. - // Values are usually padded with 0s at the front. - return intval(substr($hash, 1, 15), 16); - } - } else { - /** - * See https://github.com/runkit7/runkit_object_id for a faster native version for php <= 7.1 - * - * @param object $object - * @return int (The object id, XORed with a random number) - */ - function spl_object_id($object): int - { - $hash = spl_object_hash($object); - // Fit this into a php long (32-bit or 64-bit signed int). - // The first 16 hex digits (64 bytes) vary, the last 16 don't. - // Values are usually padded with 0s at the front. - return intval(substr($hash, 9, 7), 16); - } - } -} diff --git a/stubs/CoreGenericFunctions.phpstub b/stubs/CoreGenericFunctions.phpstub index e629e571fd9..24352529233 100644 --- a/stubs/CoreGenericFunctions.phpstub +++ b/stubs/CoreGenericFunctions.phpstub @@ -136,7 +136,7 @@ function array_flip(array $array) * * @param TArray $array * - * @return (TArray is array ? null : TKey|null) + * @return (TArray is array ? null : TKey|null) * @psalm-pure * @psalm-ignore-nullable-return */ @@ -150,7 +150,7 @@ function key($array) * * @param TArray $array * - * @return (TArray is array ? null : (TArray is non-empty-array ? TKey : TKey|null)) + * @return (TArray is array ? null : (TArray is non-empty-array ? TKey : TKey|null)) * @psalm-pure */ function array_key_first($array) @@ -163,7 +163,7 @@ function array_key_first($array) * * @param TArray $array * - * @return (TArray is array ? null : (TArray is non-empty-array ? TKey : TKey|null)) + * @return (TArray is array ? null : (TArray is non-empty-array ? TKey : TKey|null)) * @psalm-pure */ function array_key_last($array) diff --git a/stubs/CoreGenericIterators.phpstub b/stubs/CoreGenericIterators.phpstub index c20e54de882..34ad308d324 100644 --- a/stubs/CoreGenericIterators.phpstub +++ b/stubs/CoreGenericIterators.phpstub @@ -442,15 +442,15 @@ class DirectoryIterator extends SplFileInfo implements SeekableIterator { } /** - * @template-implements Iterator + * @template-implements Iterator */ class EmptyIterator implements Iterator { /** - * @return empty + * @return never */ public function current() {} /** - * @return empty + * @return never */ public function key() {} /** diff --git a/tests/AlgebraTest.php b/tests/AlgebraTest.php index ac5c42f0f79..401029f2aa4 100644 --- a/tests/AlgebraTest.php +++ b/tests/AlgebraTest.php @@ -85,7 +85,7 @@ public function testCombinatorialExpansion(): void $has_errors = false; - $dnf_stmt = StatementsProvider::parseStatements($dnf, '7.4', $has_errors)[0]; + $dnf_stmt = StatementsProvider::parseStatements($dnf, 7_04_00, $has_errors)[0]; $this->assertInstanceOf(PhpParser\Node\Stmt\Expression::class, $dnf_stmt); @@ -101,7 +101,7 @@ public function testCombinatorialExpansion(): void $statements_analyzer ); - $this->assertCount(6561, $dnf_clauses); + $this->assertCount(6_561, $dnf_clauses); $simplified_dnf_clauses = Algebra::simplifyCNF($dnf_clauses); diff --git a/tests/AnnotationTest.php b/tests/AnnotationTest.php index 9c65da93b99..506524aa215 100644 --- a/tests/AnnotationTest.php +++ b/tests/AnnotationTest.php @@ -22,149 +22,6 @@ public function setUp(): void $codebase->reportUnusedVariables(); } - public function testPhpStormGenericsWithValidArrayIteratorArgument(): void - { - Config::getInstance()->allow_phpstorm_generics = true; - - $this->addFile( - 'somefile.php', - 'offsetGet("a"); - takesString($s); - - foreach ($i as $s2) { - takesString($s2); - } - }' - ); - - $this->analyzeFile('somefile.php', new Context()); - } - - public function testPhpStormGenericsWithTypeInSignature(): void - { - Config::getInstance()->allow_phpstorm_generics = true; - - $this->addFile( - 'somefile.php', - 'analyzeFile('somefile.php', new Context()); - } - - public function testPhpStormGenericsWithValidTraversableArgument(): void - { - Config::getInstance()->allow_phpstorm_generics = true; - - $this->addFile( - 'somefile.php', - 'analyzeFile('somefile.php', new Context()); - } - - public function testPhpStormGenericsWithClassProperty(): void - { - Config::getInstance()->allow_phpstorm_generics = true; - - $this->addFile( - 'somefile.php', - 'bar; - } - }' - ); - - $this->analyzeFile('somefile.php', new Context()); - } - - public function testPhpStormGenericsWithGeneratorArray(): void - { - Config::getInstance()->allow_phpstorm_generics = true; - - $this->addFile( - 'somefile.php', - 'analyzeFile('somefile.php', new Context()); - } - - public function testPhpStormGenericsWithValidIterableArgument(): void - { - Config::getInstance()->allow_phpstorm_generics = true; - - $this->addFile( - 'somefile.php', - 'analyzeFile('somefile.php', new Context()); - } - - public function testPhpStormGenericsInvalidArgument(): void - { - $this->expectException(CodeException::class); - $this->expectExceptionMessage('InvalidScalarArgument'); - - Config::getInstance()->allow_phpstorm_generics = true; - - $this->addFile( - 'somefile.php', - 'offsetGet("a"); - takesInt($s); - }' - ); - - $this->analyzeFile('somefile.php', new Context()); - } - public function testLessSpecificImplementedReturnTypeWithDocblockOnMultipleLines(): void { $this->expectException(CodeException::class); @@ -236,25 +93,6 @@ class WTF extends \DateTime { }' $this->analyzeFile('somefile.php', new Context()); } - public function testPhpStormGenericsNoTypehint(): void - { - $this->expectException(CodeException::class); - $this->expectExceptionMessage('PossiblyInvalidMethodCall'); - - Config::getInstance()->allow_phpstorm_generics = true; - - $this->addFile( - 'somefile.php', - 'offsetGet("a"); - }' - ); - - $this->analyzeFile('somefile.php', new Context()); - } - public function testInvalidParamDefault(): void { $this->expectException(CodeException::class); @@ -1652,14 +1490,6 @@ public function bar() { ', 'error_message' => 'UndefinedDocblockClass', ], - 'preventBadTKeyedArrayFormat' => [ - ' 'InvalidDocblock', - ], 'noPhpStormAnnotationsThankYou' => [ ' 'InvalidScalarArgument', + 'error_message' => 'InvalidArgument', ], 'spreadOperatorByRefAnnotationBadCall2' => [ ' 'InvalidScalarArgument', + 'error_message' => 'InvalidArgument', ], 'spreadOperatorByRefAnnotationBadCall3' => [ ' 'InvalidScalarArgument', + 'error_message' => 'InvalidArgument', ], 'identifyReturnType' => [ ' [ + 'allowArrayIntScalarForArrayStringWithArgumentTypeCoercionIgnored' => [ ' $arr */ + /** @param array $arr */ function foo(array $arr) : void { } @@ -171,10 +171,10 @@ function bar() : array { return []; } - /** @psalm-suppress InvalidScalarArgument */ + /** @psalm-suppress ArgumentTypeCoercion */ foo(bar());', ], - 'allowArrayScalarForArrayStringWithScalarIgnored' => [ + 'allowArrayScalarForArrayStringWithArgumentTypeCoercionIgnored' => [ ' $arr */ function foo(array $arr) : void {} @@ -184,7 +184,7 @@ function bar() : array { return []; } - /** @psalm-suppress InvalidScalarArgument */ + /** @psalm-suppress ArgumentTypeCoercion */ foo(bar());', ], 'unpackObjectlikeListArgs' => [ diff --git a/tests/ArrayAssignmentTest.php b/tests/ArrayAssignmentTest.php index 5a7c7f6c1ec..49d79f42625 100644 --- a/tests/ArrayAssignmentTest.php +++ b/tests/ArrayAssignmentTest.php @@ -159,7 +159,7 @@ class B {} $foo = []; $foo[][] = "hello";', 'assertions' => [ - '$foo' => 'non-empty-list>', + '$foo' => 'non-empty-list>', ], ], 'implicit3dIntArrayCreation' => [ @@ -167,7 +167,7 @@ class B {} $foo = []; $foo[][][] = "hello";', 'assertions' => [ - '$foo' => 'non-empty-list>>', + '$foo' => 'non-empty-list>>', ], ], 'implicit4dIntArrayCreation' => [ @@ -175,7 +175,7 @@ class B {} $foo = []; $foo[][][][] = "hello";', 'assertions' => [ - '$foo' => 'non-empty-list>>>', + '$foo' => 'non-empty-list>>>', ], ], 'implicitIndexedIntArrayCreation' => [ @@ -1008,7 +1008,7 @@ function updateArray(array $arr) : array { $b = (array) null;', 'assertions' => [ '$a' => 'array{0?: int, 1?: string}', - '$b' => 'array', + '$b' => 'array', ], ], 'getOnCoercedArray' => [ @@ -1124,7 +1124,7 @@ function takesList(array $arr) : void {} takesList($a);', 'assertions' => [ - '$a' => 'array' + '$a' => 'array' ], ], 'listCreatedInSingleStatementUsedAsArray' => [ @@ -1887,7 +1887,7 @@ function takesArray(array $arr) : void {} $a[] = 2; takesArray($a);', - 'error_message' => 'InvalidScalarArgument', + 'error_message' => 'InvalidArgument', ], 'listUsedAsArrayWrongListType' => [ ' 'InvalidScalarArgument', + 'error_message' => 'InvalidArgument', ], 'nonEmptyAssignmentToListElementChangeType' => [ ' count($rightCount));', 'assertions' => [ - '$leftCount' => 'array', - '$rightCount' => 'array', + '$leftCount' => 'array', + '$rightCount' => 'array', ], ], 'arrayEmptyArrayAfterCountLessThanEqualToZero' => [ @@ -585,8 +585,8 @@ function foo(array $arr) { $rightCount = [1, 2, 3]; assert (0 >= count($rightCount));', 'assertions' => [ - '$leftCount' => 'array', - '$rightCount' => 'array', + '$leftCount' => 'array', + '$rightCount' => 'array', ], ], 'arrayNotNonEmptyArrayAfterCountGreaterThanEqualToZero' => [ @@ -774,7 +774,7 @@ function test(): void { } }', 'assertions' => [], - 'error_levels' => ['MixedAssignment', 'MixedArgument', 'MixedArgumentTypeCoercion'], + 'error_levels' => ['MixedAssignment', 'MixedArgument', 'MixedArgumentTypeCoercion', 'NoValue'], ], 'arrayPopNotNullable' => [ ' [ ' */ + /** @return list */ function makeArray(): array { return []; } $a = makeArray(); $b = reset($a);', @@ -1246,7 +1246,7 @@ function makeArray(): array { return [1, 3]; } ], 'arrayEndEmptyList' => [ ' */ + /** @return list */ function makeArray(): array { return []; } $a = makeArray(); $b = end($a);', @@ -2033,7 +2033,7 @@ function thing() { 'arrayShiftOnMixedOrEmptyArray' => [ ' $lengths + * @param mixed|array $lengths */ function doStuff($lengths): void { /** @psalm-suppress MixedArgument, MixedAssignment */ diff --git a/tests/CallableTest.php b/tests/CallableTest.php index 65fa422056c..add6331882a 100644 --- a/tests/CallableTest.php +++ b/tests/CallableTest.php @@ -1399,7 +1399,7 @@ function takesCallableReturningString(callable $c) : void { function foo(string $c) : void { takesCallableReturningString([$c, "bar"]); }', - 'error_message' => 'InvalidScalarArgument', + 'error_message' => 'InvalidArgument', ], 'inexistantCallableinCallableString' => [ ' [ + 'mirrorCallableParams' => [ ' [ + 'closureFromCallableInvokableNamedClass' => [ ' [ + 'closureFromCallableInvokableAnonymousClass' => [ ' [ + 'publicCallableFromInside' => [ ' [ + 'protectedCallableFromInside' => [ ' [ + 'closureFromCallableNamedFunction' => [ ' 'TypeDoesNotContainType', ], - 'PHP71-closureFromCallableInvokableNamedClassWrongArgs' => [ + 'closureFromCallableInvokableNamedClassWrongArgs' => [ ' 'DuplicateParam', ], - 'PHP71-privateCallable' => [ + 'privateCallable' => [ 'extends->getAttribute('resolvedName') : ''; $storage->custom_metadata['implements'] = array_map( - function (Name $aspect): string { - return (string)$aspect->getAttribute('resolvedName'); - }, + fn(Name $aspect): string => (string)$aspect->getAttribute('resolvedName'), $stmt->implements ); $storage->custom_metadata['a'] = 'b'; diff --git a/tests/CommandFunctions/GetMemoryLimitInBytesTest.php b/tests/CommandFunctions/GetMemoryLimitInBytesTest.php index d2dee3f6ec0..b842143df06 100644 --- a/tests/CommandFunctions/GetMemoryLimitInBytesTest.php +++ b/tests/CommandFunctions/GetMemoryLimitInBytesTest.php @@ -18,21 +18,21 @@ public function memoryLimitSettingProvider(): array // byte values [1, 1], [512, 512], - [2048, 2048], + [2_048, 2_048], // uppercase units - ['1K', 1024], - ['24K', 24576], - ['1M', 1048576], - ['24M', 25165824], - ['1G', 1073741824], - ['24G', 25769803776], + ['1K', 1_024], + ['24K', 24_576], + ['1M', 1_048_576], + ['24M', 25_165_824], + ['1G', 1_073_741_824], + ['24G', 25_769_803_776], // lowercase units - ['1k', 1024], - ['24k', 24576], - ['1m', 1048576], - ['24m', 25165824], - ['1g', 1073741824], - ['24g', 25769803776], + ['1k', 1_024], + ['24k', 24_576], + ['1m', 1_048_576], + ['24m', 25_165_824], + ['1g', 1_073_741_824], + ['24g', 25_769_803_776], ]; } diff --git a/tests/ComposerLockTest.php b/tests/ComposerLockTest.php index cf856d4a206..fc46ccefe33 100644 --- a/tests/ComposerLockTest.php +++ b/tests/ComposerLockTest.php @@ -7,6 +7,8 @@ use function json_encode; +use const JSON_THROW_ON_ERROR; + /** @group PluginManager */ class ComposerLockTest extends TestCase { @@ -216,6 +218,6 @@ private function pluginEntry(string $package_name, string $package_class): array */ private function jsonFile($data): string { - return 'data:application/json,' . json_encode($data); + return 'data:application/json,' . json_encode($data, JSON_THROW_ON_ERROR); } } diff --git a/tests/Config/ConfigTest.php b/tests/Config/ConfigTest.php index e5f85694f4d..b6be450a72c 100644 --- a/tests/Config/ConfigTest.php +++ b/tests/Config/ConfigTest.php @@ -188,7 +188,7 @@ public function testIgnoreSymlinkedProjectDirectory(): void - + ' @@ -799,9 +799,7 @@ public function testAllPossibleIssues(): void * * @return string */ - function ($issue_name): string { - return '<' . $issue_name . ' errorLevel="suppress" />' . "\n"; - }, + fn($issue_name): string => '<' . $issue_name . ' errorLevel="suppress" />' . "\n", IssueHandler::getAllIssueTypes() ) ); @@ -876,73 +874,6 @@ class MyMockClass {} $this->analyzeFile($file_path, new Context()); } - public function testExitFunctions(): void - { - $this->project_analyzer = $this->getProjectAnalyzerWithConfig( - TestConfig::loadFromXML( - dirname(__DIR__, 2), - ' - - - - - - - ' - ) - ); - - $file_path = getcwd() . '/src/somefile.php'; - - $this->addFile( - $file_path, - 'analyzeFile($file_path, new Context()); - } - public function testValidThrowInvalidCatch(): void { $this->expectExceptionMessage('InvalidCatch'); @@ -1429,10 +1360,10 @@ public function pluginRegistersScannerAndAnalyzerDataProvider(): array { return [ 'regular' => [0, null], // flags, expected exception code - 'invalid scanner class' => [FileTypeSelfRegisteringPlugin::FLAG_SCANNER_INVALID, 1622727271], - 'invalid analyzer class' => [FileTypeSelfRegisteringPlugin::FLAG_ANALYZER_INVALID, 1622727281], - 'override scanner' => [FileTypeSelfRegisteringPlugin::FLAG_SCANNER_TWICE, 1622727272], - 'override analyzer' => [FileTypeSelfRegisteringPlugin::FLAG_ANALYZER_TWICE, 1622727282], + 'invalid scanner class' => [FileTypeSelfRegisteringPlugin::FLAG_SCANNER_INVALID, 1_622_727_271], + 'invalid analyzer class' => [FileTypeSelfRegisteringPlugin::FLAG_ANALYZER_INVALID, 1_622_727_281], + 'override scanner' => [FileTypeSelfRegisteringPlugin::FLAG_SCANNER_TWICE, 1_622_727_272], + 'override analyzer' => [FileTypeSelfRegisteringPlugin::FLAG_ANALYZER_TWICE, 1_622_727_282], ]; } diff --git a/tests/Config/Plugin/Hook/StringProvider/TSqlSelectString.php b/tests/Config/Plugin/Hook/StringProvider/TSqlSelectString.php index 8540f3bd097..4be059215ef 100644 --- a/tests/Config/Plugin/Hook/StringProvider/TSqlSelectString.php +++ b/tests/Config/Plugin/Hook/StringProvider/TSqlSelectString.php @@ -19,7 +19,7 @@ public function getId(bool $nested = true): string return 'sql-select-string(' . $this->value . ')'; } - public function canBeFullyExpressedInPhp(int $php_major_version, int $php_minor_version): bool + public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool { return false; } diff --git a/tests/Config/PluginTest.php b/tests/Config/PluginTest.php index 0734f7e0f06..2476d9be4ea 100644 --- a/tests/Config/PluginTest.php +++ b/tests/Config/PluginTest.php @@ -4,13 +4,9 @@ use InvalidArgumentException; use PHPUnit\Framework\MockObject\MockObject; -use PhpParser\Node\Expr; -use PhpParser\Node\Stmt\ClassLike; -use Psalm\Codebase; use Psalm\Config; use Psalm\Context; use Psalm\Exception\CodeException; -use Psalm\FileSource; use Psalm\Internal\Analyzer\ProjectAnalyzer; use Psalm\Internal\IncludeCollector; use Psalm\Internal\Provider\FakeFileProvider; @@ -21,18 +17,13 @@ use Psalm\Plugin\EventHandler\AfterEveryFunctionCallAnalysisInterface; use Psalm\Plugin\EventHandler\Event\AfterCodebasePopulatedEvent; use Psalm\Plugin\EventHandler\Event\AfterEveryFunctionCallAnalysisEvent; -use Psalm\Plugin\Hook\AfterClassLikeVisitInterface as LegacyAfterClassLikeVisitInterface; -use Psalm\Plugin\Hook\AfterMethodCallAnalysisInterface as LegacyAfterMethodCallAnalysisInterface; use Psalm\PluginRegistrationSocket; use Psalm\Report; use Psalm\Report\ReportOptions; -use Psalm\StatementsSource; -use Psalm\Storage\ClassLikeStorage; use Psalm\Test\Config\Plugin\Hook\StringProvider\TSqlSelectString; use Psalm\Tests\Internal\Provider\FakeParserCacheProvider; use Psalm\Tests\TestCase; use Psalm\Tests\TestConfig; -use Psalm\Type\Union; use stdClass; use function define; @@ -302,55 +293,6 @@ public function testEchoAnalyzerPluginWithUnescapedString(): void $this->analyzeFile($file_path, new Context()); } - public function testEchoAnalyzerPluginWithEscapedString(): void - { - $this->project_analyzer = $this->getProjectAnalyzerWithConfig( - TestConfig::loadFromXML( - dirname(__DIR__, 2) . DIRECTORY_SEPARATOR, - ' - - - - - - - - - - - - ' - ) - ); - - $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); - - $file_path = getcwd() . '/src/somefile.php'; - - $this->addFile( - $file_path, - ' - Some text - ' - ); - - $this->analyzeFile($file_path, new Context()); - } - public function testFileAnalyzerPlugin(): void { require_once __DIR__ . '/Plugin/FilePlugin.php'; @@ -572,86 +514,6 @@ public static function afterCodebasePopulated(AfterCodebasePopulatedEvent $event ); } - public function testAfterMethodCallAnalysisLegacyHookIsLoaded(): void - { - $this->project_analyzer = $this->getProjectAnalyzerWithConfig( - TestConfig::loadFromXML( - dirname(__DIR__, 2) . DIRECTORY_SEPARATOR, - ' - - - - - ' - ) - ); - - $hook = new class implements LegacyAfterMethodCallAnalysisInterface { - public static function afterMethodCallAnalysis( - Expr $expr, - string $method_id, - string $appearing_method_id, - string $declaring_method_id, - Context $context, - StatementsSource $statements_source, - Codebase $codebase, - array &$file_replacements = [], - Union &$return_type_candidate = null - ): void { - } - }; - - $codebase = $this->project_analyzer->getCodebase(); - - $config = $codebase->config; - - (new PluginRegistrationSocket($config, $codebase))->registerHooksFromClass(get_class($hook)); - - $this->assertTrue($this->project_analyzer->getCodebase()->config->eventDispatcher->hasAfterMethodCallAnalysisHandlers()); - } - - public function testAfterClassLikeAnalysisLegacyHookIsLoaded(): void - { - $this->project_analyzer = $this->getProjectAnalyzerWithConfig( - TestConfig::loadFromXML( - dirname(__DIR__, 2) . DIRECTORY_SEPARATOR, - ' - - - - - ' - ) - ); - - $hook = new class implements LegacyAfterClassLikeVisitInterface { - /** - * @return void - * @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint - */ - public static function afterClassLikeVisit( - ClassLike $stmt, - ClassLikeStorage $storage, - FileSource $statements_source, - Codebase $codebase, - array &$file_replacements = [] - ) { - } - }; - - $codebase = $this->project_analyzer->getCodebase(); - - $config = $codebase->config; - - (new PluginRegistrationSocket($config, $codebase))->registerHooksFromClass(get_class($hook)); - - $this->assertTrue($this->project_analyzer->getCodebase()->config->eventDispatcher->hasAfterClassLikeVisitHandlers()); - } - public function testPropertyProviderHooks(): void { require_once __DIR__ . '/Plugin/PropertyPlugin.php'; diff --git a/tests/ConstantTest.php b/tests/ConstantTest.php index 22250503f13..97957d4e220 100644 --- a/tests/ConstantTest.php +++ b/tests/ConstantTest.php @@ -303,7 +303,7 @@ class C extends B { public const ARR = [...parent::ARR]; } - /** @param array $arg */ + /** @param array $arg */ function foo(array $arg): void {} foo(C::ARR); ', diff --git a/tests/DocblockInheritanceTest.php b/tests/DocblockInheritanceTest.php index 054244becc7..6d293087f0a 100644 --- a/tests/DocblockInheritanceTest.php +++ b/tests/DocblockInheritanceTest.php @@ -173,7 +173,7 @@ public function boo(array $arr) : void {} } (new X())->boo([1, 2]);', - 'error_message' => 'InvalidScalarArgument', + 'error_message' => 'InvalidArgument', ], ]; } diff --git a/tests/DocumentationTest.php b/tests/DocumentationTest.php index 61e9fdf5450..0cdd6fb75ca 100644 --- a/tests/DocumentationTest.php +++ b/tests/DocumentationTest.php @@ -347,9 +347,7 @@ public function testShortcodesAreUnique(): void $duplicate_shortcodes = array_filter( $all_shortcodes, - function ($issues): bool { - return count($issues) > 1; - } + fn($issues): bool => count($issues) > 1 ); $this->assertEquals( diff --git a/tests/EndToEnd/PsalmEndToEndTest.php b/tests/EndToEnd/PsalmEndToEndTest.php index efc0d396c0b..59498a274df 100644 --- a/tests/EndToEnd/PsalmEndToEndTest.php +++ b/tests/EndToEnd/PsalmEndToEndTest.php @@ -24,8 +24,6 @@ use function tempnam; use function unlink; -use const PHP_VERSION_ID; - /** * Tests some of the most important use cases of the psalm and psalter commands, by launching a new * process as if invoked by a real user. @@ -152,10 +150,6 @@ public function testPsalmWithPHPVersionFromConfig(): void public function testPsalmDiff(): void { - if (PHP_VERSION_ID < 70400) { - $this->markTestSkipped('Only works on 7.4+'); - } - copy(__DIR__ . '/../fixtures/DummyProjectWithErrors/diff_composer.lock', self::$tmpDir . '/composer.lock'); $this->runPsalmInit(1); diff --git a/tests/EnumTest.php b/tests/EnumTest.php index 38b58bc204a..4cf234557a0 100644 --- a/tests/EnumTest.php +++ b/tests/EnumTest.php @@ -274,7 +274,7 @@ enum Status: int {} $_z = Status::cases(); ', 'assertions' => [ - '$_z===' => 'array', + '$_z===' => 'array', ], [], '8.1', diff --git a/tests/FileDiffTest.php b/tests/FileDiffTest.php index 86b8e0ee105..3b18fdb6900 100644 --- a/tests/FileDiffTest.php +++ b/tests/FileDiffTest.php @@ -37,8 +37,8 @@ public function testCode( $has_errors = false; - $a_stmts = StatementsProvider::parseStatements($a, '7.4', $has_errors); - $b_stmts = StatementsProvider::parseStatements($b, '7.4', $has_errors); + $a_stmts = StatementsProvider::parseStatements($a, 7_04_00, $has_errors); + $b_stmts = StatementsProvider::parseStatements($b, 7_04_00, $has_errors); $diff = FileStatementsDiffer::diff($a_stmts, $b_stmts, $a, $b); @@ -65,9 +65,7 @@ public function testCode( * * @return array{0: int, 1: int} */ - function (array $arr): array { - return [$arr[2], $arr[3]]; - }, + fn(array $arr): array => [$arr[2], $arr[3]], $diff[3] ); @@ -101,7 +99,7 @@ public function testPartialAstDiff( $has_errors = false; - $a_stmts = StatementsProvider::parseStatements($a, '7.4', $has_errors); + $a_stmts = StatementsProvider::parseStatements($a, 7_04_00, $has_errors); $traverser = new PhpParser\NodeTraverser; $traverser->addVisitor(new CloningVisitor); @@ -111,8 +109,8 @@ public function testPartialAstDiff( $this->assertTreesEqual($a_stmts, $a_stmts_copy); - $b_stmts = StatementsProvider::parseStatements($b, '7.4', $has_errors, null, $a, $a_stmts_copy, $file_changes); - $b_clean_stmts = StatementsProvider::parseStatements($b, '7.4', $has_errors); + $b_stmts = StatementsProvider::parseStatements($b, 7_04_00, $has_errors, null, $a, $a_stmts_copy, $file_changes); + $b_clean_stmts = StatementsProvider::parseStatements($b, 7_04_00, $has_errors); $this->assertTreesEqual($b_clean_stmts, $b_stmts); @@ -141,9 +139,7 @@ public function testPartialAstDiff( * * @return array{0: int, 1: int} */ - function (array $arr): array { - return [$arr[2], $arr[3]]; - }, + fn(array $arr): array => [$arr[2], $arr[3]], $diff[3] ); @@ -1643,7 +1639,7 @@ public function bar() { [], ['c\a::foo', 'c\a::bar', 'c\a::zap', 'c\a::top', 'c\a::rot', 'c\a::bar'], [], - [[124, 405], [432, 711], [738, 1016], [1043, 1284]], + [[124, 405], [432, 711], [738, 1_016], [1_043, 1_284]], ], 'noUseChange' => [ ' + * @psalm-return array */ function foo(): array { return []; diff --git a/tests/FileManipulation/ReturnTypeManipulationTest.php b/tests/FileManipulation/ReturnTypeManipulationTest.php index 94bacfb1706..415da9b55d5 100644 --- a/tests/FileManipulation/ReturnTypeManipulationTest.php +++ b/tests/FileManipulation/ReturnTypeManipulationTest.php @@ -651,7 +651,7 @@ function get_form_fields(string $a) { /** * @param string $a * - * @psalm-return array + * @psalm-return array */ function get_form_fields(string $a): array { switch($a){ diff --git a/tests/FunctionCallTest.php b/tests/FunctionCallTest.php index dc0185bc60c..d39455877e7 100644 --- a/tests/FunctionCallTest.php +++ b/tests/FunctionCallTest.php @@ -188,7 +188,7 @@ class B extends A { exit; }', 'assertions' => [ - '$a' => 'array', + '$a' => 'array', ], ], 'byRefAfterCallable' => [ @@ -1045,7 +1045,7 @@ class Props { '$c' => 'int', ], ], - 'PHP73-hrtime' => [ + 'hrtime' => [ ' 'array{0: int, 1: int}', ], ], - 'PHP73-hrtimeCanBeFloat' => [ + 'hrtimeCanBeFloat' => [ ' $x + * @param array $x * @return 0 */ function example($x) : int { @@ -1252,7 +1252,7 @@ function baz(string $s) : void { /** @psalm-suppress TooFewArguments */ min(0);', ], - 'PHP73-allowIsCountableToInformType' => [ + 'allowIsCountableToInformType' => [ ' 'array', ], ], - 'PHP72-pregMatchWithFlagUnmatchedAsNull' => [ + 'pregMatchWithFlagUnmatchedAsNull' => [ ' [ @@ -1366,7 +1366,7 @@ function takesInt(int $i) : void {} '$matches===' => 'array', ], ], - 'PHP72-pregMatchWithFlagOffsetCaptureAndUnmatchedAsNull' => [ + 'pregMatchWithFlagOffsetCaptureAndUnmatchedAsNull' => [ ' [ @@ -1412,7 +1412,7 @@ function foo(int $a, string $b, bool $c) : array { return compact("a", "b", "c"); }', ], - 'PHP73-setCookiePhp73' => [ + 'setCookiePhp73' => [ ' "hello"]);', - 'error_message' => 'InvalidScalarArgument', + 'error_message' => 'InvalidArgument', ], 'objectLikeKeyChecksAgainstDifferentTKeyedArray' => [ ' [ getcwd() . DIRECTORY_SEPARATOR . 'file2.php', ], - 'error_message' => 'InvalidScalarArgument', + 'error_message' => 'InvalidArgument', ], 'namespacedRequireFunction' => [ 'files' => [ diff --git a/tests/IntRangeTest.php b/tests/IntRangeTest.php index f5eb50bb8d3..da52aa9d289 100644 --- a/tests/IntRangeTest.php +++ b/tests/IntRangeTest.php @@ -203,21 +203,27 @@ function getInt(): int{return 0;} $h = $d % $e; $i = -3 % $a; $j = -3 % $b; + /** @psalm-suppress NoValue */ $k = -3 % $c; $l = -3 % $d; $m = 3 % $a; $n = 3 % $b; + /** @psalm-suppress NoValue */ $o = 3 % $c; $p = 3 % $d; + /** @psalm-suppress NoValue */ $q = $a % 0; $r = $a % 3; $s = $a % -3; + /** @psalm-suppress NoValue */ $t = $b % 0; $u = $b % 3; $v = $b % -3; + /** @psalm-suppress NoValue */ $w = $c % 0; $x = $c % 3; $y = $c % -3; + /** @psalm-suppress NoValue */ $z = $d % 0; $aa = $d % 3; $ab = $d % -3; @@ -228,22 +234,22 @@ function getInt(): int{return 0;} '$h===' => 'int<-4, 4>', '$i===' => 'int', '$j===' => 'int', - '$k===' => 'empty', + '$k===' => 'never', '$l===' => 'int', '$m===' => 'int<0, max>', '$n===' => 'int', - '$o===' => 'empty', + '$o===' => 'never', '$p===' => 'int', - '$q===' => 'empty', + '$q===' => 'never', '$r===' => 'int<0, 2>', '$s===' => 'int<-2, 0>', - '$t===' => 'empty', + '$t===' => 'never', '$u===' => 'int<-2, 0>', '$v===' => 'int<2, 0>', - '$w===' => 'empty', + '$w===' => 'never', '$x===' => 'int<0, 2>', '$y===' => 'int<-2, 0>', - '$z===' => 'empty', + '$z===' => 'never', '$aa===' => 'int<-2, 2>', '$ab===' => 'int<-2, 2>', ] @@ -285,9 +291,9 @@ function getInt(): int{return 0;} ', 'assertions' => [ '$e===' => '0', - '$f===' => 'empty', + '$f===' => 'float', '$g===' => '1', - '$h===' => 'empty', + '$h===' => '0|1|float', '$i===' => 'int', '$j===' => 'float', '$k===' => '-1', @@ -304,7 +310,7 @@ function getInt(): int{return 0;} '$v===' => 'float', '$w===' => '1', '$x===' => '0', - '$y===' => 'empty', + '$y===' => 'float', '$z===' => '1', '$aa===' => 'int<1, max>', '$ab===' => 'float', diff --git a/tests/MagicMethodAnnotationTest.php b/tests/MagicMethodAnnotationTest.php index f961664c170..11c5fdf53c9 100644 --- a/tests/MagicMethodAnnotationTest.php +++ b/tests/MagicMethodAnnotationTest.php @@ -813,7 +813,7 @@ class Child extends ParentClass {} $child = new Child(); $child->setString("five");', - 'error_message' => 'InvalidScalarArgument', + 'error_message' => 'InvalidArgument', ], 'unionAnnotationInvalidArg' => [ 'setBool("hello", 5);', - 'error_message' => 'InvalidScalarArgument', + 'error_message' => 'InvalidArgument', ], 'validAnnotationWithInvalidVariadicCall' => [ ' 'SplObjectStorage', + '$a' => 'SplObjectStorage', ] ], 'allowIteratorToBeNull' => [ diff --git a/tests/ReportOutputTest.php b/tests/ReportOutputTest.php index 5fd7b59bc47..5fb82b594f4 100644 --- a/tests/ReportOutputTest.php +++ b/tests/ReportOutputTest.php @@ -24,6 +24,8 @@ use function preg_replace; use function unlink; +use const JSON_THROW_ON_ERROR; + class ReportOutputTest extends TestCase { public function setUp(): void @@ -666,7 +668,7 @@ public function testSarifReport(): void $this->assertSame( $issue_data, - json_decode(IssueBuffer::getOutput(IssueBuffer::getIssuesData(), $sarif_report_options), true) + json_decode(IssueBuffer::getOutput(IssueBuffer::getIssuesData(), $sarif_report_options), true, 512, JSON_THROW_ON_ERROR) ); } @@ -819,7 +821,7 @@ public function testJsonReport(): void $this->assertSame( $issue_data, - json_decode(IssueBuffer::getOutput(IssueBuffer::getIssuesData(), $json_report_options), true) + json_decode(IssueBuffer::getOutput(IssueBuffer::getIssuesData(), $json_report_options), true, 512, JSON_THROW_ON_ERROR) ); } @@ -853,7 +855,7 @@ public function testFilteredJsonReportIsStillArray(): void $fixable_issue_counts, $report_options ); - $this->assertIsArray(json_decode($report->create())); + $this->assertIsArray(json_decode($report->create(), null, 512, JSON_THROW_ON_ERROR)); } public function testSonarqubeReport(): void @@ -950,7 +952,7 @@ public function testSonarqubeReport(): void $this->assertSame( $issue_data, - json_decode(IssueBuffer::getOutput(IssueBuffer::getIssuesData(), $sonarqube_report_options), true) + json_decode(IssueBuffer::getOutput(IssueBuffer::getIssuesData(), $sonarqube_report_options), true, 512, JSON_THROW_ON_ERROR) ); } diff --git a/tests/ReturnTypeProvider/GetObjectVarsTest.php b/tests/ReturnTypeProvider/GetObjectVarsTest.php index 9e8d8500e47..3389bbd4de2 100644 --- a/tests/ReturnTypeProvider/GetObjectVarsTest.php +++ b/tests/ReturnTypeProvider/GetObjectVarsTest.php @@ -33,7 +33,7 @@ class C { } $ret = get_object_vars(new C); ', - ['$ret' => 'array'], + ['$ret' => 'array'], ]; yield 'includesPrivateAndProtectedPropertiesWhenCalledInsideClassScope' => [ diff --git a/tests/TaintTest.php b/tests/TaintTest.php index cfae0743390..b4859519899 100644 --- a/tests/TaintTest.php +++ b/tests/TaintTest.php @@ -2309,9 +2309,7 @@ public function multipleTaintIssuesAreDetected(string $code, array $expectedIssu $this->analyzeFile($filePath, new Context(), false); $actualIssueTypes = array_map( - function (IssueData $issue): string { - return $issue->type . '{ ' . trim($issue->snippet) . ' }'; - }, + fn(IssueData $issue): string => $issue->type . '{ ' . trim($issue->snippet) . ' }', IssueBuffer::getIssuesDataForFile($filePath) ); self::assertSame($expectedIssuesTypes, $actualIssueTypes); diff --git a/tests/Template/ClassTemplateExtendsTest.php b/tests/Template/ClassTemplateExtendsTest.php index 8fcba0c578b..0c15853c82f 100644 --- a/tests/Template/ClassTemplateExtendsTest.php +++ b/tests/Template/ClassTemplateExtendsTest.php @@ -4626,7 +4626,7 @@ public function getID() class AppUser extends User {} $au = new AppUser("string");', - 'error_message' => 'InvalidScalarArgument', + 'error_message' => 'InvalidArgument', ], 'extendsTwiceDifferentNameBrokenChain' => [ ' 'ArrayCollection' + '$a' => 'ArrayCollection' ] ], 'newGenericBecomesPropertyTypeValidArg' => [ @@ -3822,7 +3822,7 @@ function takesIntCollection(Collection $c): void {} takesStringCollection($collection); takesIntCollection($collection);', - 'error_message' => 'InvalidScalarArgument', + 'error_message' => 'InvalidArgument', ], 'argumentExpectsFleshOutTIndexedAccess' => [ ' 'InvalidScalarArgument', + 'error_message' => 'InvalidArgument', ], 'multipleArgConstraintWithMoreRestrictiveFirstArg' => [ ' 'InvalidReturnStatement', + 'error_message' => 'LessSpecificReturnStatement', ], 'returnIntersectionWhenTemplateIsExpectedBackward' => [ ' 'InvalidReturnStatement', + 'error_message' => 'LessSpecificReturnStatement', ], 'bottomTypeInClosureShouldClash' => [ ' $key === 0 || is_string($key); $validKeys = array_filter($array, $isZeroOrString, ARRAY_FILTER_USE_KEY); self::assertTrue(count($array) === count($validKeys), $message); } diff --git a/tests/Traits/InvalidCodeAnalysisTestTrait.php b/tests/Traits/InvalidCodeAnalysisTestTrait.php index 0bbd2f46348..478e54b2cc5 100644 --- a/tests/Traits/InvalidCodeAnalysisTestTrait.php +++ b/tests/Traits/InvalidCodeAnalysisTestTrait.php @@ -42,11 +42,7 @@ public function testInvalidCode( string $php_version = '7.3' ): void { $test_name = $this->getTestName(); - if (strpos($test_name, 'PHP71-') !== false) { - if (version_compare(PHP_VERSION, '7.1.0', '<')) { - $this->markTestSkipped('Test case requires PHP 7.1.'); - } - } elseif (strpos($test_name, 'PHP80-') !== false) { + if (strpos($test_name, 'PHP80-') !== false) { if (version_compare(PHP_VERSION, '8.0.0', '<')) { $this->markTestSkipped('Test case requires PHP 8.0.'); } diff --git a/tests/Traits/ValidCodeAnalysisTestTrait.php b/tests/Traits/ValidCodeAnalysisTestTrait.php index 46a57ac4a22..0bef93d31b8 100644 --- a/tests/Traits/ValidCodeAnalysisTestTrait.php +++ b/tests/Traits/ValidCodeAnalysisTestTrait.php @@ -39,19 +39,7 @@ public function testValidCode( string $php_version = '7.3' ): void { $test_name = $this->getTestName(); - if (strpos($test_name, 'PHP71-') !== false) { - if (version_compare(PHP_VERSION, '7.1.0', '<')) { - $this->markTestSkipped('Test case requires PHP 7.1.'); - } - } elseif (strpos($test_name, 'PHP72-') !== false) { - if (version_compare(PHP_VERSION, '7.2.0', '<')) { - $this->markTestSkipped('Test case requires PHP 7.2.'); - } - } elseif (strpos($test_name, 'PHP73-') !== false) { - if (version_compare(PHP_VERSION, '7.3.0', '<')) { - $this->markTestSkipped('Test case requires PHP 7.3.'); - } - } elseif (strpos($test_name, 'PHP80-') !== false) { + if (strpos($test_name, 'PHP80-') !== false) { if (version_compare(PHP_VERSION, '8.0.0', '<')) { $this->markTestSkipped('Test case requires PHP 8.0.'); } diff --git a/tests/TypeCombinationTest.php b/tests/TypeCombinationTest.php index 1f3350f3bfb..03737402753 100644 --- a/tests/TypeCombinationTest.php +++ b/tests/TypeCombinationTest.php @@ -109,10 +109,10 @@ public function providerTestValidTypeCombination(): array 'null', ], ], - 'mixedOrEmpty' => [ + 'mixedOrNever' => [ 'mixed', [ - 'empty', + 'never', 'mixed', ], ], @@ -124,10 +124,10 @@ public function providerTestValidTypeCombination(): array ], ], 'mixedOrEmptyArray' => [ - 'array|mixed', + 'array|mixed', [ 'mixed', - 'array', + 'array', ], ], 'falseTrueToBool' => [ @@ -197,16 +197,16 @@ public function providerTestValidTypeCombination(): array ], ], 'emptyArrays' => [ - 'array', + 'array', [ - 'array', - 'array', + 'array', + 'array', ], ], 'arrayStringOrEmptyArray' => [ 'array', [ - 'array', + 'array', 'array', ], ], @@ -227,7 +227,7 @@ public function providerTestValidTypeCombination(): array 'arrayMixedOrEmpty' => [ 'array', [ - 'array', + 'array', 'array', ], ], @@ -309,16 +309,16 @@ public function providerTestValidTypeCombination(): array ], ], 'arrayObjectAndParamsWithEmptyArray' => [ - 'ArrayObject|array', + 'ArrayObject|array', [ 'ArrayObject', - 'array', + 'array', ], ], 'emptyArrayWithArrayObjectAndParams' => [ - 'ArrayObject|array', + 'ArrayObject|array', [ - 'array', + 'array', 'ArrayObject', ], ], diff --git a/tests/TypeComparatorTest.php b/tests/TypeComparatorTest.php index 714a786b32e..b051d8c60ec 100644 --- a/tests/TypeComparatorTest.php +++ b/tests/TypeComparatorTest.php @@ -73,14 +73,11 @@ public function getAllBasicTypes(): array $basic_generic_types, [ 'open-resource' => true, // unverifiable - 'mysql-escaped-string' => true, // deprecated 'non-empty-countable' => true, // bit weird, maybe a bug? ] ); return array_map( - function ($type) { - return [$type]; - }, + fn($type) => [$type], array_keys($basic_types) ); } @@ -114,19 +111,19 @@ public function getAllowedChildTypes(): array ], 'listAcceptsEmptyArray' => [ 'list', - 'array', + 'array', ], 'arrayAcceptsEmptyArray' => [ 'array', - 'array', + 'array', ], 'arrayOptionalKeyed1AcceptsEmptyArray' => [ 'array{foo?: string}', - 'array', + 'array', ], 'arrayOptionalKeyed2AcceptsEmptyArray' => [ 'array{foo?: string}&array', - 'array', + 'array', ], 'Lowercase-stringAndCallable-string' => [ 'lowercase-string', diff --git a/tests/TypeParseTest.php b/tests/TypeParseTest.php index 30ea08a39a0..d2f29297ecc 100644 --- a/tests/TypeParseTest.php +++ b/tests/TypeParseTest.php @@ -893,6 +893,14 @@ public function testSingleLiteralString(): void ); } + public function testEmptyArrayShape(): void + { + $this->assertSame( + 'array', + (string)Type::parseString('array{}') + ); + } + public function testSingleLiteralInt(): void { $this->assertSame( diff --git a/tests/TypeReconciliation/ConditionalTest.php b/tests/TypeReconciliation/ConditionalTest.php index 670c3bb138f..a786b2ca64c 100644 --- a/tests/TypeReconciliation/ConditionalTest.php +++ b/tests/TypeReconciliation/ConditionalTest.php @@ -1006,7 +1006,7 @@ function f() { return rand(0,1) ? "f" : 1.1; } atan($a); atan($b);', ], - 'PHP71-removeNonCallable' => [ + 'removeNonCallable' => [ ' ['null', 'null', 'SomeClass|null'], 'nullWithMixed' => ['null', 'null', 'mixed'], - 'falsyWithSomeClass' => ['empty', 'falsy', 'SomeClass'], + 'falsyWithSomeClass' => ['never', 'falsy', 'SomeClass'], 'falsyWithSomeClassPipeFalse' => ['false', 'falsy', 'SomeClass|false'], 'falsyWithSomeClassPipeBool' => ['false', 'falsy', 'SomeClass|bool'], 'falsyWithMixed' => ['empty-mixed', 'falsy', 'mixed'], @@ -146,6 +146,7 @@ public function providerTestReconcilation(): array 'iterableAndNotObject' => ['array', '!object', 'iterable'], 'boolNotEmptyIsTrue' => ['true', '!empty', 'bool'], 'interfaceAssertionOnClassInterfaceUnion' => ['SomeInterface|SomeInterface&SomeClass', 'SomeInterface', 'SomeClass|SomeInterface'], + 'classAssertionOnClassInterfaceUnion' => ['SomeClass|SomeClass&SomeInterface', 'SomeClass', 'SomeClass|SomeInterface'], 'stringToNumericStringWithInt' => ['numeric-string', '~int', 'string'], 'stringToNumericStringWithFloat' => ['numeric-string', '~float', 'string'], 'filterKeyedArrayWithIterable' => ['array{some: string}', 'iterable', 'array{some: mixed}'], @@ -176,7 +177,7 @@ public function providerTestTypeIsContainedBy(): array 'array>', ], 'objectLikeTypeWithPossiblyUndefinedToEmpty' => [ - 'array', + 'array', 'array{a?: string, b?: string}', ], 'literalNumericStringInt' => [ diff --git a/tests/TypeReconciliation/TypeTest.php b/tests/TypeReconciliation/TypeTest.php index 6908b92dd48..d6470487776 100644 --- a/tests/TypeReconciliation/TypeTest.php +++ b/tests/TypeReconciliation/TypeTest.php @@ -592,7 +592,7 @@ public function barBar(One $one = null) { $ids = []; }', 'assertions' => [ - '$ids' => 'array', + '$ids' => 'array', ], ], 'arrayUnionTypeAssertionWithIsArray' => [ @@ -603,7 +603,7 @@ public function barBar(One $one = null) { $ids = []; }', 'assertions' => [ - '$ids' => 'array', + '$ids' => 'array', ], ], '2dArrayUnionTypeAssertionWithIsArray' => [